├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── Makefile ├── README.md └── contracts ├── lp_token ├── .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 │ ├── batch.rs │ ├── contract.rs │ ├── lib.rs │ ├── msg.rs │ ├── rand.rs │ ├── receiver.rs │ ├── state.rs │ ├── transaction_history.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 └── stable_pool ├── .cargo └── config ├── Cargo.toml ├── README.md └── src ├── contract.rs ├── lib.rs ├── math.rs ├── msg.rs ├── querier.rs ├── state.rs └── u256_math.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # macOS 2 | .DS_Store 3 | 4 | # Text file backups 5 | **/*.rs.bk 6 | 7 | # Build results 8 | target/ 9 | 10 | # IDEs 11 | .vscode/ 12 | .idea/ 13 | *.iml 14 | 15 | # Auto-gen 16 | .cargo-ok 17 | 18 | build/ -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["contracts/*"] 3 | 4 | [profile.release.package.secretswap] 5 | opt-level = 3 6 | debug = false 7 | debug-assertions = false 8 | codegen-units = 1 9 | incremental = false 10 | 11 | [profile.release] 12 | opt-level = 3 13 | debug = false 14 | rpath = false 15 | lto = true 16 | debug-assertions = false 17 | codegen-units = 1 18 | panic = 'abort' 19 | incremental = false 20 | overflow-checks = true 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Terraform Labs, PTE. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /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 52 | _compile-optimized: 53 | RUSTFLAGS='-C link-arg=-s' cargo +nightly 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 . 56 | cp ./target/wasm32-unknown-unknown/release/*.wasm ./build/ 57 | 58 | .PHONY: compile-w-debug-print _compile-w-debug-print 59 | compile-w-debug-print: _compile-w-debug-print 60 | _compile-w-debug-print: 61 | RUSTFLAGS='-C link-arg=-s' cargo +nightly build --release --target wasm32-unknown-unknown --locked 62 | cd contracts/secretswap_pair && RUSTFLAGS='-C link-arg=-s' cargo build --release --features debug-print --target wasm32-unknown-unknown --locked 63 | cd contracts/dummy_swap_data_receiver && RUSTFLAGS='-C link-arg=-s' cargo build --release --features debug-print --target wasm32-unknown-unknown --locked 64 | cp ./target/wasm32-unknown-unknown/release/*.wasm ./build/ 65 | 66 | .PHONY: compile-optimized-reproducible 67 | compile-optimized-reproducible: 68 | docker run --rm -v "$$(pwd)":/contract \ 69 | --mount type=volume,source="$$(basename "$$(pwd)")_cache",target=/code/target \ 70 | --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ 71 | enigmampc/secret-contract-optimizer:1.0.4 72 | 73 | # After make start-server is streaming blocks, this will setup the AMM and send you some SCRT and ETH 74 | # change the secret1x6my6xxxkladvsupcka7k092m50rdw8pk8dpq9 to your address 75 | # scripts/setup.sh && 76 | # docker exec -it secretdev secretcli tx send a secret1x6my6xxxkladvsupcka7k092m50rdw8pk8dpq9 100000000uscrt -y -b block && 77 | # docker exec -it secretdev secretcli tx compute execute secret18vd8fpwxzck93qlwghaj6arh4p7c5n8978vsyg '{"transfer":{"recipient":"secret1x6my6xxxkladvsupcka7k092m50rdw8pk8dpq9","amount":"100000000"}}' --from a -y -b block 78 | .PHONY: start-server 79 | start-server: # CTRL+C to stop 80 | docker run -it --rm \ 81 | -p 26657:26657 -p 26656:26656 -p 1337:1337 \ 82 | -v $$(pwd):/root/code \ 83 | --name secretdev enigmampc/secret-network-sw-dev:latest 84 | 85 | .PHONY: schema 86 | schema: 87 | cargo run --example schema 88 | 89 | .PHONY: clean 90 | clean: 91 | cargo clean 92 | rm -f ./contract.wasm ./contract.wasm.gz 93 | #$(MAKE) -C tests/example-receiver clean 94 | 95 | 96 | # token: 1 97 | # factory: 2 98 | # pair: 3 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WIP 2 | 3 | ## SecretStablePools 4 | 5 | A Curve-like AMM for Secret Network. Supports a varibale number of tokens with the same underlying value. 6 | 7 | TODOs: 8 | 9 | - Queries 10 | - Withdrawls 11 | - Bonuses for LPs who balance the pools 12 | - Figure out LP tokens (NFTs for privacy or SNIP20s for simplicity?) 13 | - Private entropy pool for differential privacy when reporting pools sizes 14 | -------------------------------------------------------------------------------- /contracts/lp_token/.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 | -------------------------------------------------------------------------------- /contracts/lp_token/.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 | -------------------------------------------------------------------------------- /contracts/lp_token/.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 | -------------------------------------------------------------------------------- /contracts/lp_token/.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 | -------------------------------------------------------------------------------- /contracts/lp_token/.gitignore: -------------------------------------------------------------------------------- 1 | # Build results 2 | /target 3 | 4 | # Cargo+Git helper file (https://github.com/rust-lang/cargo/blob/0.44.1/src/cargo/sources/git/utils.rs#L320-L327) 5 | .cargo-ok 6 | 7 | # Text file backups 8 | **/*.rs.bk 9 | 10 | # macOS 11 | .DS_Store 12 | 13 | # IDEs 14 | *.iml 15 | .idea 16 | -------------------------------------------------------------------------------- /contracts/lp_token/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lp-token" 3 | version = "0.1.0" 4 | authors = ["Itzik "] 5 | edition = "2018" 6 | exclude = [ 7 | # 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. 8 | "contract.wasm", 9 | "hash.txt", 10 | ] 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | [lib] 14 | crate-type = ["cdylib", "rlib"] 15 | 16 | [profile.release] 17 | opt-level = 3 18 | debug = false 19 | rpath = false 20 | lto = true 21 | debug-assertions = false 22 | codegen-units = 1 23 | panic = 'abort' 24 | incremental = false 25 | overflow-checks = true 26 | 27 | [features] 28 | # for quicker tests, cargo test --lib 29 | # for more explicit tests, cargo test --features=backtraces 30 | #default = ["debug-print"] 31 | backtraces = ["cosmwasm-std/backtraces", "cosmwasm-vm/backtraces"] 32 | debug-print = ["cosmwasm-std/debug-print"] 33 | 34 | [dependencies] 35 | cosmwasm-std = { git = "https://github.com/enigmampc/SecretNetwork", tag = "v1.0.4-debug-print" } 36 | cosmwasm-storage = { git = "https://github.com/enigmampc/SecretNetwork", tag = "v1.0.4-debug-print" } 37 | secret-toolkit = { git = "https://github.com/enigmampc/secret-toolkit", rev = "v0.1.1-debug-print" } 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 | rand_chacha = { version = "0.2.2", default-features = false } 46 | rand_core = { version = "0.5.1", default-features = false } 47 | sha2 = { version = "0.9.1", default-features = false } 48 | 49 | [dev-dependencies] 50 | cosmwasm-vm = { package = "cosmwasm-sgx-vm", git = "https://github.com/enigmampc/SecretNetwork", tag = "v1.0.0" } 51 | cosmwasm-schema = { version = "0.9.2" } 52 | -------------------------------------------------------------------------------- /contracts/lp_token/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 | -------------------------------------------------------------------------------- /contracts/lp_token/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 | -------------------------------------------------------------------------------- /contracts/lp_token/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 | -------------------------------------------------------------------------------- /contracts/lp_token/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 | RUST_BACKTRACE=1 cargo test 28 | 29 | .PHONY: unit-test-nocapture 30 | unit-test-nocapture: 31 | RUST_BACKTRACE=1 cargo test -- --nocapture 32 | 33 | .PHONY: unit-test-receiver 34 | unit-test-receiver: 35 | $(MAKE) -C tests/example-receiver unit-test 36 | 37 | .PHONY: integration-test 38 | integration-test: compile-optimized compile-optimized-receiver 39 | if tests/integration.sh; then echo -n '\a'; else echo -n '\a'; sleep 0.125; echo -n '\a'; fi 40 | 41 | compile-optimized-receiver: 42 | $(MAKE) -C tests/example-receiver compile-optimized 43 | 44 | .PHONY: list-code 45 | list-code: 46 | $(SECRETCLI) query compute list-code 47 | 48 | .PHONY: compile _compile 49 | compile: _compile contract.wasm.gz 50 | _compile: 51 | cargo build --target wasm32-unknown-unknown --locked 52 | cp ./target/wasm32-unknown-unknown/debug/*.wasm ./contract.wasm 53 | 54 | .PHONY: compile-optimized _compile-optimized 55 | compile-optimized: _compile-optimized contract.wasm.gz 56 | _compile-optimized: 57 | RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown --locked 58 | @# The following line is not necessary, may work only on linux (extra size optimization) 59 | wasm-opt -Os ./target/wasm32-unknown-unknown/release/*.wasm -o ./contract.wasm 60 | 61 | .PHONY: compile-optimized-reproducible 62 | compile-optimized-reproducible: 63 | docker run --rm -v "$$(pwd)":/contract \ 64 | --mount type=volume,source="$$(basename "$$(pwd)")_cache",target=/code/target \ 65 | --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ 66 | enigmampc/secret-contract-optimizer:1.0.3 67 | 68 | contract.wasm.gz: contract.wasm 69 | cat ./contract.wasm | gzip -9 > ./contract.wasm.gz 70 | 71 | .PHONY: start-server 72 | start-server: # CTRL+C to stop 73 | docker run -it --rm \ 74 | -p 26657:26657 -p 26656:26656 -p 1317:1317 \ 75 | -v $$(pwd):/root/code \ 76 | --name secretdev enigmampc/secret-network-sw-dev:v1.0.4-3 77 | 78 | .PHONY: schema 79 | schema: 80 | cargo run --example schema 81 | 82 | .PHONY: clean 83 | clean: 84 | cargo clean 85 | rm -f ./contract.wasm ./contract.wasm.gz 86 | $(MAKE) -C tests/example-receiver clean 87 | -------------------------------------------------------------------------------- /contracts/lp_token/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 | -------------------------------------------------------------------------------- /contracts/lp_token/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 | -------------------------------------------------------------------------------- /contracts/lp_token/README.md: -------------------------------------------------------------------------------- 1 | # SNIP22 + AfterInitHook 2 | 3 | SNIP20 was taken at commit https://github.com/enigmampc/snip20-reference-impl/tree/ead6f74f05c9a784cca515a530185cd5357e396f 4 | -------------------------------------------------------------------------------- /contracts/lp_token/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 lp_token::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 | -------------------------------------------------------------------------------- /contracts/lp_token/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 | -------------------------------------------------------------------------------- /contracts/lp_token/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 | "transfer" 47 | ], 48 | "properties": { 49 | "transfer": { 50 | "type": "object", 51 | "required": [ 52 | "status" 53 | ], 54 | "properties": { 55 | "status": { 56 | "$ref": "#/definitions/ResponseStatus" 57 | } 58 | } 59 | } 60 | } 61 | }, 62 | { 63 | "type": "object", 64 | "required": [ 65 | "send" 66 | ], 67 | "properties": { 68 | "send": { 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 | "batch_transfer" 85 | ], 86 | "properties": { 87 | "batch_transfer": { 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 | "batch_send" 104 | ], 105 | "properties": { 106 | "batch_send": { 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 | "burn" 123 | ], 124 | "properties": { 125 | "burn": { 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 | "register_receive" 142 | ], 143 | "properties": { 144 | "register_receive": { 145 | "type": "object", 146 | "required": [ 147 | "status" 148 | ], 149 | "properties": { 150 | "status": { 151 | "$ref": "#/definitions/ResponseStatus" 152 | } 153 | } 154 | } 155 | } 156 | }, 157 | { 158 | "type": "object", 159 | "required": [ 160 | "create_viewing_key" 161 | ], 162 | "properties": { 163 | "create_viewing_key": { 164 | "type": "object", 165 | "required": [ 166 | "key" 167 | ], 168 | "properties": { 169 | "key": { 170 | "$ref": "#/definitions/ViewingKey" 171 | } 172 | } 173 | } 174 | } 175 | }, 176 | { 177 | "type": "object", 178 | "required": [ 179 | "set_viewing_key" 180 | ], 181 | "properties": { 182 | "set_viewing_key": { 183 | "type": "object", 184 | "required": [ 185 | "status" 186 | ], 187 | "properties": { 188 | "status": { 189 | "$ref": "#/definitions/ResponseStatus" 190 | } 191 | } 192 | } 193 | } 194 | }, 195 | { 196 | "type": "object", 197 | "required": [ 198 | "increase_allowance" 199 | ], 200 | "properties": { 201 | "increase_allowance": { 202 | "type": "object", 203 | "required": [ 204 | "allowance", 205 | "owner", 206 | "spender" 207 | ], 208 | "properties": { 209 | "allowance": { 210 | "$ref": "#/definitions/Uint128" 211 | }, 212 | "owner": { 213 | "$ref": "#/definitions/HumanAddr" 214 | }, 215 | "spender": { 216 | "$ref": "#/definitions/HumanAddr" 217 | } 218 | } 219 | } 220 | } 221 | }, 222 | { 223 | "type": "object", 224 | "required": [ 225 | "decrease_allowance" 226 | ], 227 | "properties": { 228 | "decrease_allowance": { 229 | "type": "object", 230 | "required": [ 231 | "allowance", 232 | "owner", 233 | "spender" 234 | ], 235 | "properties": { 236 | "allowance": { 237 | "$ref": "#/definitions/Uint128" 238 | }, 239 | "owner": { 240 | "$ref": "#/definitions/HumanAddr" 241 | }, 242 | "spender": { 243 | "$ref": "#/definitions/HumanAddr" 244 | } 245 | } 246 | } 247 | } 248 | }, 249 | { 250 | "type": "object", 251 | "required": [ 252 | "transfer_from" 253 | ], 254 | "properties": { 255 | "transfer_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 | "send_from" 272 | ], 273 | "properties": { 274 | "send_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 | "batch_transfer_from" 291 | ], 292 | "properties": { 293 | "batch_transfer_from": { 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 | "batch_send_from" 310 | ], 311 | "properties": { 312 | "batch_send_from": { 313 | "type": "object", 314 | "required": [ 315 | "status" 316 | ], 317 | "properties": { 318 | "status": { 319 | "$ref": "#/definitions/ResponseStatus" 320 | } 321 | } 322 | } 323 | } 324 | }, 325 | { 326 | "type": "object", 327 | "required": [ 328 | "burn_from" 329 | ], 330 | "properties": { 331 | "burn_from": { 332 | "type": "object", 333 | "required": [ 334 | "status" 335 | ], 336 | "properties": { 337 | "status": { 338 | "$ref": "#/definitions/ResponseStatus" 339 | } 340 | } 341 | } 342 | } 343 | }, 344 | { 345 | "type": "object", 346 | "required": [ 347 | "batch_burn_from" 348 | ], 349 | "properties": { 350 | "batch_burn_from": { 351 | "type": "object", 352 | "required": [ 353 | "status" 354 | ], 355 | "properties": { 356 | "status": { 357 | "$ref": "#/definitions/ResponseStatus" 358 | } 359 | } 360 | } 361 | } 362 | }, 363 | { 364 | "type": "object", 365 | "required": [ 366 | "mint" 367 | ], 368 | "properties": { 369 | "mint": { 370 | "type": "object", 371 | "required": [ 372 | "status" 373 | ], 374 | "properties": { 375 | "status": { 376 | "$ref": "#/definitions/ResponseStatus" 377 | } 378 | } 379 | } 380 | } 381 | }, 382 | { 383 | "type": "object", 384 | "required": [ 385 | "batch_mint" 386 | ], 387 | "properties": { 388 | "batch_mint": { 389 | "type": "object", 390 | "required": [ 391 | "status" 392 | ], 393 | "properties": { 394 | "status": { 395 | "$ref": "#/definitions/ResponseStatus" 396 | } 397 | } 398 | } 399 | } 400 | }, 401 | { 402 | "type": "object", 403 | "required": [ 404 | "add_minters" 405 | ], 406 | "properties": { 407 | "add_minters": { 408 | "type": "object", 409 | "required": [ 410 | "status" 411 | ], 412 | "properties": { 413 | "status": { 414 | "$ref": "#/definitions/ResponseStatus" 415 | } 416 | } 417 | } 418 | } 419 | }, 420 | { 421 | "type": "object", 422 | "required": [ 423 | "remove_minters" 424 | ], 425 | "properties": { 426 | "remove_minters": { 427 | "type": "object", 428 | "required": [ 429 | "status" 430 | ], 431 | "properties": { 432 | "status": { 433 | "$ref": "#/definitions/ResponseStatus" 434 | } 435 | } 436 | } 437 | } 438 | }, 439 | { 440 | "type": "object", 441 | "required": [ 442 | "set_minters" 443 | ], 444 | "properties": { 445 | "set_minters": { 446 | "type": "object", 447 | "required": [ 448 | "status" 449 | ], 450 | "properties": { 451 | "status": { 452 | "$ref": "#/definitions/ResponseStatus" 453 | } 454 | } 455 | } 456 | } 457 | }, 458 | { 459 | "type": "object", 460 | "required": [ 461 | "change_admin" 462 | ], 463 | "properties": { 464 | "change_admin": { 465 | "type": "object", 466 | "required": [ 467 | "status" 468 | ], 469 | "properties": { 470 | "status": { 471 | "$ref": "#/definitions/ResponseStatus" 472 | } 473 | } 474 | } 475 | } 476 | }, 477 | { 478 | "type": "object", 479 | "required": [ 480 | "set_contract_status" 481 | ], 482 | "properties": { 483 | "set_contract_status": { 484 | "type": "object", 485 | "required": [ 486 | "status" 487 | ], 488 | "properties": { 489 | "status": { 490 | "$ref": "#/definitions/ResponseStatus" 491 | } 492 | } 493 | } 494 | } 495 | } 496 | ], 497 | "definitions": { 498 | "HumanAddr": { 499 | "type": "string" 500 | }, 501 | "ResponseStatus": { 502 | "type": "string", 503 | "enum": [ 504 | "success", 505 | "failure" 506 | ] 507 | }, 508 | "Uint128": { 509 | "type": "string" 510 | }, 511 | "ViewingKey": { 512 | "type": "string" 513 | } 514 | } 515 | } 516 | -------------------------------------------------------------------------------- /contracts/lp_token/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 | "denom": { 21 | "type": [ 22 | "string", 23 | "null" 24 | ] 25 | }, 26 | "padding": { 27 | "type": [ 28 | "string", 29 | "null" 30 | ] 31 | } 32 | } 33 | } 34 | } 35 | }, 36 | { 37 | "type": "object", 38 | "required": [ 39 | "deposit" 40 | ], 41 | "properties": { 42 | "deposit": { 43 | "type": "object", 44 | "properties": { 45 | "padding": { 46 | "type": [ 47 | "string", 48 | "null" 49 | ] 50 | } 51 | } 52 | } 53 | } 54 | }, 55 | { 56 | "type": "object", 57 | "required": [ 58 | "transfer" 59 | ], 60 | "properties": { 61 | "transfer": { 62 | "type": "object", 63 | "required": [ 64 | "amount", 65 | "recipient" 66 | ], 67 | "properties": { 68 | "amount": { 69 | "$ref": "#/definitions/Uint128" 70 | }, 71 | "memo": { 72 | "type": [ 73 | "string", 74 | "null" 75 | ] 76 | }, 77 | "padding": { 78 | "type": [ 79 | "string", 80 | "null" 81 | ] 82 | }, 83 | "recipient": { 84 | "$ref": "#/definitions/HumanAddr" 85 | } 86 | } 87 | } 88 | } 89 | }, 90 | { 91 | "type": "object", 92 | "required": [ 93 | "send" 94 | ], 95 | "properties": { 96 | "send": { 97 | "type": "object", 98 | "required": [ 99 | "amount", 100 | "recipient" 101 | ], 102 | "properties": { 103 | "amount": { 104 | "$ref": "#/definitions/Uint128" 105 | }, 106 | "memo": { 107 | "type": [ 108 | "string", 109 | "null" 110 | ] 111 | }, 112 | "msg": { 113 | "anyOf": [ 114 | { 115 | "$ref": "#/definitions/Binary" 116 | }, 117 | { 118 | "type": "null" 119 | } 120 | ] 121 | }, 122 | "padding": { 123 | "type": [ 124 | "string", 125 | "null" 126 | ] 127 | }, 128 | "recipient": { 129 | "$ref": "#/definitions/HumanAddr" 130 | } 131 | } 132 | } 133 | } 134 | }, 135 | { 136 | "type": "object", 137 | "required": [ 138 | "batch_transfer" 139 | ], 140 | "properties": { 141 | "batch_transfer": { 142 | "type": "object", 143 | "required": [ 144 | "actions" 145 | ], 146 | "properties": { 147 | "actions": { 148 | "type": "array", 149 | "items": { 150 | "$ref": "#/definitions/TransferAction" 151 | } 152 | }, 153 | "padding": { 154 | "type": [ 155 | "string", 156 | "null" 157 | ] 158 | } 159 | } 160 | } 161 | } 162 | }, 163 | { 164 | "type": "object", 165 | "required": [ 166 | "batch_send" 167 | ], 168 | "properties": { 169 | "batch_send": { 170 | "type": "object", 171 | "required": [ 172 | "actions" 173 | ], 174 | "properties": { 175 | "actions": { 176 | "type": "array", 177 | "items": { 178 | "$ref": "#/definitions/SendAction" 179 | } 180 | }, 181 | "padding": { 182 | "type": [ 183 | "string", 184 | "null" 185 | ] 186 | } 187 | } 188 | } 189 | } 190 | }, 191 | { 192 | "type": "object", 193 | "required": [ 194 | "burn" 195 | ], 196 | "properties": { 197 | "burn": { 198 | "type": "object", 199 | "required": [ 200 | "amount" 201 | ], 202 | "properties": { 203 | "amount": { 204 | "$ref": "#/definitions/Uint128" 205 | }, 206 | "memo": { 207 | "type": [ 208 | "string", 209 | "null" 210 | ] 211 | }, 212 | "padding": { 213 | "type": [ 214 | "string", 215 | "null" 216 | ] 217 | } 218 | } 219 | } 220 | } 221 | }, 222 | { 223 | "type": "object", 224 | "required": [ 225 | "register_receive" 226 | ], 227 | "properties": { 228 | "register_receive": { 229 | "type": "object", 230 | "required": [ 231 | "code_hash" 232 | ], 233 | "properties": { 234 | "code_hash": { 235 | "type": "string" 236 | }, 237 | "padding": { 238 | "type": [ 239 | "string", 240 | "null" 241 | ] 242 | } 243 | } 244 | } 245 | } 246 | }, 247 | { 248 | "type": "object", 249 | "required": [ 250 | "create_viewing_key" 251 | ], 252 | "properties": { 253 | "create_viewing_key": { 254 | "type": "object", 255 | "required": [ 256 | "entropy" 257 | ], 258 | "properties": { 259 | "entropy": { 260 | "type": "string" 261 | }, 262 | "padding": { 263 | "type": [ 264 | "string", 265 | "null" 266 | ] 267 | } 268 | } 269 | } 270 | } 271 | }, 272 | { 273 | "type": "object", 274 | "required": [ 275 | "set_viewing_key" 276 | ], 277 | "properties": { 278 | "set_viewing_key": { 279 | "type": "object", 280 | "required": [ 281 | "key" 282 | ], 283 | "properties": { 284 | "key": { 285 | "type": "string" 286 | }, 287 | "padding": { 288 | "type": [ 289 | "string", 290 | "null" 291 | ] 292 | } 293 | } 294 | } 295 | } 296 | }, 297 | { 298 | "type": "object", 299 | "required": [ 300 | "increase_allowance" 301 | ], 302 | "properties": { 303 | "increase_allowance": { 304 | "type": "object", 305 | "required": [ 306 | "amount", 307 | "spender" 308 | ], 309 | "properties": { 310 | "amount": { 311 | "$ref": "#/definitions/Uint128" 312 | }, 313 | "expiration": { 314 | "type": [ 315 | "integer", 316 | "null" 317 | ], 318 | "format": "uint64", 319 | "minimum": 0.0 320 | }, 321 | "padding": { 322 | "type": [ 323 | "string", 324 | "null" 325 | ] 326 | }, 327 | "spender": { 328 | "$ref": "#/definitions/HumanAddr" 329 | } 330 | } 331 | } 332 | } 333 | }, 334 | { 335 | "type": "object", 336 | "required": [ 337 | "decrease_allowance" 338 | ], 339 | "properties": { 340 | "decrease_allowance": { 341 | "type": "object", 342 | "required": [ 343 | "amount", 344 | "spender" 345 | ], 346 | "properties": { 347 | "amount": { 348 | "$ref": "#/definitions/Uint128" 349 | }, 350 | "expiration": { 351 | "type": [ 352 | "integer", 353 | "null" 354 | ], 355 | "format": "uint64", 356 | "minimum": 0.0 357 | }, 358 | "padding": { 359 | "type": [ 360 | "string", 361 | "null" 362 | ] 363 | }, 364 | "spender": { 365 | "$ref": "#/definitions/HumanAddr" 366 | } 367 | } 368 | } 369 | } 370 | }, 371 | { 372 | "type": "object", 373 | "required": [ 374 | "transfer_from" 375 | ], 376 | "properties": { 377 | "transfer_from": { 378 | "type": "object", 379 | "required": [ 380 | "amount", 381 | "owner", 382 | "recipient" 383 | ], 384 | "properties": { 385 | "amount": { 386 | "$ref": "#/definitions/Uint128" 387 | }, 388 | "memo": { 389 | "type": [ 390 | "string", 391 | "null" 392 | ] 393 | }, 394 | "owner": { 395 | "$ref": "#/definitions/HumanAddr" 396 | }, 397 | "padding": { 398 | "type": [ 399 | "string", 400 | "null" 401 | ] 402 | }, 403 | "recipient": { 404 | "$ref": "#/definitions/HumanAddr" 405 | } 406 | } 407 | } 408 | } 409 | }, 410 | { 411 | "type": "object", 412 | "required": [ 413 | "send_from" 414 | ], 415 | "properties": { 416 | "send_from": { 417 | "type": "object", 418 | "required": [ 419 | "amount", 420 | "owner", 421 | "recipient" 422 | ], 423 | "properties": { 424 | "amount": { 425 | "$ref": "#/definitions/Uint128" 426 | }, 427 | "memo": { 428 | "type": [ 429 | "string", 430 | "null" 431 | ] 432 | }, 433 | "msg": { 434 | "anyOf": [ 435 | { 436 | "$ref": "#/definitions/Binary" 437 | }, 438 | { 439 | "type": "null" 440 | } 441 | ] 442 | }, 443 | "owner": { 444 | "$ref": "#/definitions/HumanAddr" 445 | }, 446 | "padding": { 447 | "type": [ 448 | "string", 449 | "null" 450 | ] 451 | }, 452 | "recipient": { 453 | "$ref": "#/definitions/HumanAddr" 454 | } 455 | } 456 | } 457 | } 458 | }, 459 | { 460 | "type": "object", 461 | "required": [ 462 | "batch_transfer_from" 463 | ], 464 | "properties": { 465 | "batch_transfer_from": { 466 | "type": "object", 467 | "required": [ 468 | "actions" 469 | ], 470 | "properties": { 471 | "actions": { 472 | "type": "array", 473 | "items": { 474 | "$ref": "#/definitions/TransferFromAction" 475 | } 476 | }, 477 | "padding": { 478 | "type": [ 479 | "string", 480 | "null" 481 | ] 482 | } 483 | } 484 | } 485 | } 486 | }, 487 | { 488 | "type": "object", 489 | "required": [ 490 | "batch_send_from" 491 | ], 492 | "properties": { 493 | "batch_send_from": { 494 | "type": "object", 495 | "required": [ 496 | "actions" 497 | ], 498 | "properties": { 499 | "actions": { 500 | "type": "array", 501 | "items": { 502 | "$ref": "#/definitions/SendFromAction" 503 | } 504 | }, 505 | "padding": { 506 | "type": [ 507 | "string", 508 | "null" 509 | ] 510 | } 511 | } 512 | } 513 | } 514 | }, 515 | { 516 | "type": "object", 517 | "required": [ 518 | "burn_from" 519 | ], 520 | "properties": { 521 | "burn_from": { 522 | "type": "object", 523 | "required": [ 524 | "amount", 525 | "owner" 526 | ], 527 | "properties": { 528 | "amount": { 529 | "$ref": "#/definitions/Uint128" 530 | }, 531 | "memo": { 532 | "type": [ 533 | "string", 534 | "null" 535 | ] 536 | }, 537 | "owner": { 538 | "$ref": "#/definitions/HumanAddr" 539 | }, 540 | "padding": { 541 | "type": [ 542 | "string", 543 | "null" 544 | ] 545 | } 546 | } 547 | } 548 | } 549 | }, 550 | { 551 | "type": "object", 552 | "required": [ 553 | "batch_burn_from" 554 | ], 555 | "properties": { 556 | "batch_burn_from": { 557 | "type": "object", 558 | "required": [ 559 | "actions" 560 | ], 561 | "properties": { 562 | "actions": { 563 | "type": "array", 564 | "items": { 565 | "$ref": "#/definitions/BurnFromAction" 566 | } 567 | }, 568 | "padding": { 569 | "type": [ 570 | "string", 571 | "null" 572 | ] 573 | } 574 | } 575 | } 576 | } 577 | }, 578 | { 579 | "type": "object", 580 | "required": [ 581 | "mint" 582 | ], 583 | "properties": { 584 | "mint": { 585 | "type": "object", 586 | "required": [ 587 | "amount", 588 | "recipient" 589 | ], 590 | "properties": { 591 | "amount": { 592 | "$ref": "#/definitions/Uint128" 593 | }, 594 | "memo": { 595 | "type": [ 596 | "string", 597 | "null" 598 | ] 599 | }, 600 | "padding": { 601 | "type": [ 602 | "string", 603 | "null" 604 | ] 605 | }, 606 | "recipient": { 607 | "$ref": "#/definitions/HumanAddr" 608 | } 609 | } 610 | } 611 | } 612 | }, 613 | { 614 | "type": "object", 615 | "required": [ 616 | "batch_mint" 617 | ], 618 | "properties": { 619 | "batch_mint": { 620 | "type": "object", 621 | "required": [ 622 | "actions" 623 | ], 624 | "properties": { 625 | "actions": { 626 | "type": "array", 627 | "items": { 628 | "$ref": "#/definitions/MintAction" 629 | } 630 | }, 631 | "padding": { 632 | "type": [ 633 | "string", 634 | "null" 635 | ] 636 | } 637 | } 638 | } 639 | } 640 | }, 641 | { 642 | "type": "object", 643 | "required": [ 644 | "add_minters" 645 | ], 646 | "properties": { 647 | "add_minters": { 648 | "type": "object", 649 | "required": [ 650 | "minters" 651 | ], 652 | "properties": { 653 | "minters": { 654 | "type": "array", 655 | "items": { 656 | "$ref": "#/definitions/HumanAddr" 657 | } 658 | }, 659 | "padding": { 660 | "type": [ 661 | "string", 662 | "null" 663 | ] 664 | } 665 | } 666 | } 667 | } 668 | }, 669 | { 670 | "type": "object", 671 | "required": [ 672 | "remove_minters" 673 | ], 674 | "properties": { 675 | "remove_minters": { 676 | "type": "object", 677 | "required": [ 678 | "minters" 679 | ], 680 | "properties": { 681 | "minters": { 682 | "type": "array", 683 | "items": { 684 | "$ref": "#/definitions/HumanAddr" 685 | } 686 | }, 687 | "padding": { 688 | "type": [ 689 | "string", 690 | "null" 691 | ] 692 | } 693 | } 694 | } 695 | } 696 | }, 697 | { 698 | "type": "object", 699 | "required": [ 700 | "set_minters" 701 | ], 702 | "properties": { 703 | "set_minters": { 704 | "type": "object", 705 | "required": [ 706 | "minters" 707 | ], 708 | "properties": { 709 | "minters": { 710 | "type": "array", 711 | "items": { 712 | "$ref": "#/definitions/HumanAddr" 713 | } 714 | }, 715 | "padding": { 716 | "type": [ 717 | "string", 718 | "null" 719 | ] 720 | } 721 | } 722 | } 723 | } 724 | }, 725 | { 726 | "type": "object", 727 | "required": [ 728 | "change_admin" 729 | ], 730 | "properties": { 731 | "change_admin": { 732 | "type": "object", 733 | "required": [ 734 | "address" 735 | ], 736 | "properties": { 737 | "address": { 738 | "$ref": "#/definitions/HumanAddr" 739 | }, 740 | "padding": { 741 | "type": [ 742 | "string", 743 | "null" 744 | ] 745 | } 746 | } 747 | } 748 | } 749 | }, 750 | { 751 | "type": "object", 752 | "required": [ 753 | "set_contract_status" 754 | ], 755 | "properties": { 756 | "set_contract_status": { 757 | "type": "object", 758 | "required": [ 759 | "level" 760 | ], 761 | "properties": { 762 | "level": { 763 | "$ref": "#/definitions/ContractStatusLevel" 764 | }, 765 | "padding": { 766 | "type": [ 767 | "string", 768 | "null" 769 | ] 770 | } 771 | } 772 | } 773 | } 774 | } 775 | ], 776 | "definitions": { 777 | "Binary": { 778 | "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", 779 | "type": "string" 780 | }, 781 | "BurnFromAction": { 782 | "type": "object", 783 | "required": [ 784 | "amount", 785 | "owner" 786 | ], 787 | "properties": { 788 | "amount": { 789 | "$ref": "#/definitions/Uint128" 790 | }, 791 | "memo": { 792 | "type": [ 793 | "string", 794 | "null" 795 | ] 796 | }, 797 | "owner": { 798 | "$ref": "#/definitions/HumanAddr" 799 | } 800 | } 801 | }, 802 | "ContractStatusLevel": { 803 | "type": "string", 804 | "enum": [ 805 | "normal_run", 806 | "stop_all_but_redeems", 807 | "stop_all" 808 | ] 809 | }, 810 | "HumanAddr": { 811 | "type": "string" 812 | }, 813 | "MintAction": { 814 | "type": "object", 815 | "required": [ 816 | "amount", 817 | "recipient" 818 | ], 819 | "properties": { 820 | "amount": { 821 | "$ref": "#/definitions/Uint128" 822 | }, 823 | "memo": { 824 | "type": [ 825 | "string", 826 | "null" 827 | ] 828 | }, 829 | "recipient": { 830 | "$ref": "#/definitions/HumanAddr" 831 | } 832 | } 833 | }, 834 | "SendAction": { 835 | "type": "object", 836 | "required": [ 837 | "amount", 838 | "recipient" 839 | ], 840 | "properties": { 841 | "amount": { 842 | "$ref": "#/definitions/Uint128" 843 | }, 844 | "memo": { 845 | "type": [ 846 | "string", 847 | "null" 848 | ] 849 | }, 850 | "msg": { 851 | "anyOf": [ 852 | { 853 | "$ref": "#/definitions/Binary" 854 | }, 855 | { 856 | "type": "null" 857 | } 858 | ] 859 | }, 860 | "recipient": { 861 | "$ref": "#/definitions/HumanAddr" 862 | } 863 | } 864 | }, 865 | "SendFromAction": { 866 | "type": "object", 867 | "required": [ 868 | "amount", 869 | "owner", 870 | "recipient" 871 | ], 872 | "properties": { 873 | "amount": { 874 | "$ref": "#/definitions/Uint128" 875 | }, 876 | "memo": { 877 | "type": [ 878 | "string", 879 | "null" 880 | ] 881 | }, 882 | "msg": { 883 | "anyOf": [ 884 | { 885 | "$ref": "#/definitions/Binary" 886 | }, 887 | { 888 | "type": "null" 889 | } 890 | ] 891 | }, 892 | "owner": { 893 | "$ref": "#/definitions/HumanAddr" 894 | }, 895 | "recipient": { 896 | "$ref": "#/definitions/HumanAddr" 897 | } 898 | } 899 | }, 900 | "TransferAction": { 901 | "type": "object", 902 | "required": [ 903 | "amount", 904 | "recipient" 905 | ], 906 | "properties": { 907 | "amount": { 908 | "$ref": "#/definitions/Uint128" 909 | }, 910 | "memo": { 911 | "type": [ 912 | "string", 913 | "null" 914 | ] 915 | }, 916 | "recipient": { 917 | "$ref": "#/definitions/HumanAddr" 918 | } 919 | } 920 | }, 921 | "TransferFromAction": { 922 | "type": "object", 923 | "required": [ 924 | "amount", 925 | "owner", 926 | "recipient" 927 | ], 928 | "properties": { 929 | "amount": { 930 | "$ref": "#/definitions/Uint128" 931 | }, 932 | "memo": { 933 | "type": [ 934 | "string", 935 | "null" 936 | ] 937 | }, 938 | "owner": { 939 | "$ref": "#/definitions/HumanAddr" 940 | }, 941 | "recipient": { 942 | "$ref": "#/definitions/HumanAddr" 943 | } 944 | } 945 | }, 946 | "Uint128": { 947 | "type": "string" 948 | } 949 | } 950 | } 951 | -------------------------------------------------------------------------------- /contracts/lp_token/schema/init_msg.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "InitMsg", 4 | "type": "object", 5 | "required": [ 6 | "decimals", 7 | "name", 8 | "prng_seed", 9 | "symbol" 10 | ], 11 | "properties": { 12 | "admin": { 13 | "anyOf": [ 14 | { 15 | "$ref": "#/definitions/HumanAddr" 16 | }, 17 | { 18 | "type": "null" 19 | } 20 | ] 21 | }, 22 | "config": { 23 | "anyOf": [ 24 | { 25 | "$ref": "#/definitions/InitConfig" 26 | }, 27 | { 28 | "type": "null" 29 | } 30 | ] 31 | }, 32 | "decimals": { 33 | "type": "integer", 34 | "format": "uint8", 35 | "minimum": 0.0 36 | }, 37 | "initial_balances": { 38 | "type": [ 39 | "array", 40 | "null" 41 | ], 42 | "items": { 43 | "$ref": "#/definitions/InitialBalance" 44 | } 45 | }, 46 | "name": { 47 | "type": "string" 48 | }, 49 | "prng_seed": { 50 | "$ref": "#/definitions/Binary" 51 | }, 52 | "symbol": { 53 | "type": "string" 54 | } 55 | }, 56 | "definitions": { 57 | "Binary": { 58 | "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", 59 | "type": "string" 60 | }, 61 | "HumanAddr": { 62 | "type": "string" 63 | }, 64 | "InitConfig": { 65 | "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", 66 | "type": "object", 67 | "properties": { 68 | "enable_burn": { 69 | "description": "Indicates whether burn functionality should be enabled default: False", 70 | "type": [ 71 | "boolean", 72 | "null" 73 | ] 74 | }, 75 | "enable_deposit": { 76 | "description": "Indicates whether deposit functionality should be enabled default: False", 77 | "type": [ 78 | "boolean", 79 | "null" 80 | ] 81 | }, 82 | "enable_mint": { 83 | "description": "Indicates whether mint functionality should be enabled default: False", 84 | "type": [ 85 | "boolean", 86 | "null" 87 | ] 88 | }, 89 | "enable_redeem": { 90 | "description": "Indicates whether redeem functionality should be enabled default: False", 91 | "type": [ 92 | "boolean", 93 | "null" 94 | ] 95 | }, 96 | "public_total_supply": { 97 | "description": "Indicates whether the total supply is public or should be kept secret. default: False", 98 | "type": [ 99 | "boolean", 100 | "null" 101 | ] 102 | } 103 | } 104 | }, 105 | "InitialBalance": { 106 | "type": "object", 107 | "required": [ 108 | "address", 109 | "amount" 110 | ], 111 | "properties": { 112 | "address": { 113 | "$ref": "#/definitions/HumanAddr" 114 | }, 115 | "amount": { 116 | "$ref": "#/definitions/Uint128" 117 | } 118 | } 119 | }, 120 | "Uint128": { 121 | "type": "string" 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /contracts/lp_token/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 | "anyOf": [ 32 | { 33 | "$ref": "#/definitions/Uint128" 34 | }, 35 | { 36 | "type": "null" 37 | } 38 | ] 39 | } 40 | } 41 | } 42 | } 43 | }, 44 | { 45 | "type": "object", 46 | "required": [ 47 | "token_config" 48 | ], 49 | "properties": { 50 | "token_config": { 51 | "type": "object", 52 | "required": [ 53 | "burn_enabled", 54 | "deposit_enabled", 55 | "mint_enabled", 56 | "public_total_supply", 57 | "redeem_enabled" 58 | ], 59 | "properties": { 60 | "burn_enabled": { 61 | "type": "boolean" 62 | }, 63 | "deposit_enabled": { 64 | "type": "boolean" 65 | }, 66 | "mint_enabled": { 67 | "type": "boolean" 68 | }, 69 | "public_total_supply": { 70 | "type": "boolean" 71 | }, 72 | "redeem_enabled": { 73 | "type": "boolean" 74 | } 75 | } 76 | } 77 | } 78 | }, 79 | { 80 | "type": "object", 81 | "required": [ 82 | "contract_status" 83 | ], 84 | "properties": { 85 | "contract_status": { 86 | "type": "object", 87 | "required": [ 88 | "status" 89 | ], 90 | "properties": { 91 | "status": { 92 | "$ref": "#/definitions/ContractStatusLevel" 93 | } 94 | } 95 | } 96 | } 97 | }, 98 | { 99 | "type": "object", 100 | "required": [ 101 | "exchange_rate" 102 | ], 103 | "properties": { 104 | "exchange_rate": { 105 | "type": "object", 106 | "required": [ 107 | "denom", 108 | "rate" 109 | ], 110 | "properties": { 111 | "denom": { 112 | "type": "string" 113 | }, 114 | "rate": { 115 | "$ref": "#/definitions/Uint128" 116 | } 117 | } 118 | } 119 | } 120 | }, 121 | { 122 | "type": "object", 123 | "required": [ 124 | "allowance" 125 | ], 126 | "properties": { 127 | "allowance": { 128 | "type": "object", 129 | "required": [ 130 | "allowance", 131 | "owner", 132 | "spender" 133 | ], 134 | "properties": { 135 | "allowance": { 136 | "$ref": "#/definitions/Uint128" 137 | }, 138 | "expiration": { 139 | "type": [ 140 | "integer", 141 | "null" 142 | ], 143 | "format": "uint64", 144 | "minimum": 0.0 145 | }, 146 | "owner": { 147 | "$ref": "#/definitions/HumanAddr" 148 | }, 149 | "spender": { 150 | "$ref": "#/definitions/HumanAddr" 151 | } 152 | } 153 | } 154 | } 155 | }, 156 | { 157 | "type": "object", 158 | "required": [ 159 | "balance" 160 | ], 161 | "properties": { 162 | "balance": { 163 | "type": "object", 164 | "required": [ 165 | "amount" 166 | ], 167 | "properties": { 168 | "amount": { 169 | "$ref": "#/definitions/Uint128" 170 | } 171 | } 172 | } 173 | } 174 | }, 175 | { 176 | "type": "object", 177 | "required": [ 178 | "transfer_history" 179 | ], 180 | "properties": { 181 | "transfer_history": { 182 | "type": "object", 183 | "required": [ 184 | "txs" 185 | ], 186 | "properties": { 187 | "total": { 188 | "type": [ 189 | "integer", 190 | "null" 191 | ], 192 | "format": "uint64", 193 | "minimum": 0.0 194 | }, 195 | "txs": { 196 | "type": "array", 197 | "items": { 198 | "$ref": "#/definitions/Tx" 199 | } 200 | } 201 | } 202 | } 203 | } 204 | }, 205 | { 206 | "type": "object", 207 | "required": [ 208 | "transaction_history" 209 | ], 210 | "properties": { 211 | "transaction_history": { 212 | "type": "object", 213 | "required": [ 214 | "txs" 215 | ], 216 | "properties": { 217 | "total": { 218 | "type": [ 219 | "integer", 220 | "null" 221 | ], 222 | "format": "uint64", 223 | "minimum": 0.0 224 | }, 225 | "txs": { 226 | "type": "array", 227 | "items": { 228 | "$ref": "#/definitions/RichTx" 229 | } 230 | } 231 | } 232 | } 233 | } 234 | }, 235 | { 236 | "type": "object", 237 | "required": [ 238 | "viewing_key_error" 239 | ], 240 | "properties": { 241 | "viewing_key_error": { 242 | "type": "object", 243 | "required": [ 244 | "msg" 245 | ], 246 | "properties": { 247 | "msg": { 248 | "type": "string" 249 | } 250 | } 251 | } 252 | } 253 | }, 254 | { 255 | "type": "object", 256 | "required": [ 257 | "minters" 258 | ], 259 | "properties": { 260 | "minters": { 261 | "type": "object", 262 | "required": [ 263 | "minters" 264 | ], 265 | "properties": { 266 | "minters": { 267 | "type": "array", 268 | "items": { 269 | "$ref": "#/definitions/HumanAddr" 270 | } 271 | } 272 | } 273 | } 274 | } 275 | } 276 | ], 277 | "definitions": { 278 | "Coin": { 279 | "type": "object", 280 | "required": [ 281 | "amount", 282 | "denom" 283 | ], 284 | "properties": { 285 | "amount": { 286 | "$ref": "#/definitions/Uint128" 287 | }, 288 | "denom": { 289 | "type": "string" 290 | } 291 | } 292 | }, 293 | "ContractStatusLevel": { 294 | "type": "string", 295 | "enum": [ 296 | "normal_run", 297 | "stop_all_but_redeems", 298 | "stop_all" 299 | ] 300 | }, 301 | "HumanAddr": { 302 | "type": "string" 303 | }, 304 | "RichTx": { 305 | "type": "object", 306 | "required": [ 307 | "action", 308 | "block_height", 309 | "block_time", 310 | "coins", 311 | "id" 312 | ], 313 | "properties": { 314 | "action": { 315 | "$ref": "#/definitions/TxAction" 316 | }, 317 | "block_height": { 318 | "type": "integer", 319 | "format": "uint64", 320 | "minimum": 0.0 321 | }, 322 | "block_time": { 323 | "type": "integer", 324 | "format": "uint64", 325 | "minimum": 0.0 326 | }, 327 | "coins": { 328 | "$ref": "#/definitions/Coin" 329 | }, 330 | "id": { 331 | "type": "integer", 332 | "format": "uint64", 333 | "minimum": 0.0 334 | }, 335 | "memo": { 336 | "type": [ 337 | "string", 338 | "null" 339 | ] 340 | } 341 | } 342 | }, 343 | "Tx": { 344 | "type": "object", 345 | "required": [ 346 | "coins", 347 | "from", 348 | "id", 349 | "receiver", 350 | "sender" 351 | ], 352 | "properties": { 353 | "block_height": { 354 | "type": [ 355 | "integer", 356 | "null" 357 | ], 358 | "format": "uint64", 359 | "minimum": 0.0 360 | }, 361 | "block_time": { 362 | "type": [ 363 | "integer", 364 | "null" 365 | ], 366 | "format": "uint64", 367 | "minimum": 0.0 368 | }, 369 | "coins": { 370 | "$ref": "#/definitions/Coin" 371 | }, 372 | "from": { 373 | "$ref": "#/definitions/HumanAddr" 374 | }, 375 | "id": { 376 | "type": "integer", 377 | "format": "uint64", 378 | "minimum": 0.0 379 | }, 380 | "memo": { 381 | "type": [ 382 | "string", 383 | "null" 384 | ] 385 | }, 386 | "receiver": { 387 | "$ref": "#/definitions/HumanAddr" 388 | }, 389 | "sender": { 390 | "$ref": "#/definitions/HumanAddr" 391 | } 392 | } 393 | }, 394 | "TxAction": { 395 | "anyOf": [ 396 | { 397 | "type": "object", 398 | "required": [ 399 | "transfer" 400 | ], 401 | "properties": { 402 | "transfer": { 403 | "type": "object", 404 | "required": [ 405 | "from", 406 | "recipient", 407 | "sender" 408 | ], 409 | "properties": { 410 | "from": { 411 | "$ref": "#/definitions/HumanAddr" 412 | }, 413 | "recipient": { 414 | "$ref": "#/definitions/HumanAddr" 415 | }, 416 | "sender": { 417 | "$ref": "#/definitions/HumanAddr" 418 | } 419 | } 420 | } 421 | } 422 | }, 423 | { 424 | "type": "object", 425 | "required": [ 426 | "mint" 427 | ], 428 | "properties": { 429 | "mint": { 430 | "type": "object", 431 | "required": [ 432 | "minter", 433 | "recipient" 434 | ], 435 | "properties": { 436 | "minter": { 437 | "$ref": "#/definitions/HumanAddr" 438 | }, 439 | "recipient": { 440 | "$ref": "#/definitions/HumanAddr" 441 | } 442 | } 443 | } 444 | } 445 | }, 446 | { 447 | "type": "object", 448 | "required": [ 449 | "burn" 450 | ], 451 | "properties": { 452 | "burn": { 453 | "type": "object", 454 | "required": [ 455 | "burner", 456 | "owner" 457 | ], 458 | "properties": { 459 | "burner": { 460 | "$ref": "#/definitions/HumanAddr" 461 | }, 462 | "owner": { 463 | "$ref": "#/definitions/HumanAddr" 464 | } 465 | } 466 | } 467 | } 468 | }, 469 | { 470 | "type": "object", 471 | "required": [ 472 | "deposit" 473 | ], 474 | "properties": { 475 | "deposit": { 476 | "type": "object" 477 | } 478 | } 479 | }, 480 | { 481 | "type": "object", 482 | "required": [ 483 | "redeem" 484 | ], 485 | "properties": { 486 | "redeem": { 487 | "type": "object" 488 | } 489 | } 490 | } 491 | ] 492 | }, 493 | "Uint128": { 494 | "type": "string" 495 | } 496 | } 497 | } 498 | -------------------------------------------------------------------------------- /contracts/lp_token/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 | "token_config" 20 | ], 21 | "properties": { 22 | "token_config": { 23 | "type": "object" 24 | } 25 | } 26 | }, 27 | { 28 | "type": "object", 29 | "required": [ 30 | "contract_status" 31 | ], 32 | "properties": { 33 | "contract_status": { 34 | "type": "object" 35 | } 36 | } 37 | }, 38 | { 39 | "type": "object", 40 | "required": [ 41 | "exchange_rate" 42 | ], 43 | "properties": { 44 | "exchange_rate": { 45 | "type": "object" 46 | } 47 | } 48 | }, 49 | { 50 | "type": "object", 51 | "required": [ 52 | "allowance" 53 | ], 54 | "properties": { 55 | "allowance": { 56 | "type": "object", 57 | "required": [ 58 | "key", 59 | "owner", 60 | "spender" 61 | ], 62 | "properties": { 63 | "key": { 64 | "type": "string" 65 | }, 66 | "owner": { 67 | "$ref": "#/definitions/HumanAddr" 68 | }, 69 | "spender": { 70 | "$ref": "#/definitions/HumanAddr" 71 | } 72 | } 73 | } 74 | } 75 | }, 76 | { 77 | "type": "object", 78 | "required": [ 79 | "balance" 80 | ], 81 | "properties": { 82 | "balance": { 83 | "type": "object", 84 | "required": [ 85 | "address", 86 | "key" 87 | ], 88 | "properties": { 89 | "address": { 90 | "$ref": "#/definitions/HumanAddr" 91 | }, 92 | "key": { 93 | "type": "string" 94 | } 95 | } 96 | } 97 | } 98 | }, 99 | { 100 | "type": "object", 101 | "required": [ 102 | "transfer_history" 103 | ], 104 | "properties": { 105 | "transfer_history": { 106 | "type": "object", 107 | "required": [ 108 | "address", 109 | "key", 110 | "page_size" 111 | ], 112 | "properties": { 113 | "address": { 114 | "$ref": "#/definitions/HumanAddr" 115 | }, 116 | "key": { 117 | "type": "string" 118 | }, 119 | "page": { 120 | "type": [ 121 | "integer", 122 | "null" 123 | ], 124 | "format": "uint32", 125 | "minimum": 0.0 126 | }, 127 | "page_size": { 128 | "type": "integer", 129 | "format": "uint32", 130 | "minimum": 0.0 131 | } 132 | } 133 | } 134 | } 135 | }, 136 | { 137 | "type": "object", 138 | "required": [ 139 | "transaction_history" 140 | ], 141 | "properties": { 142 | "transaction_history": { 143 | "type": "object", 144 | "required": [ 145 | "address", 146 | "key", 147 | "page_size" 148 | ], 149 | "properties": { 150 | "address": { 151 | "$ref": "#/definitions/HumanAddr" 152 | }, 153 | "key": { 154 | "type": "string" 155 | }, 156 | "page": { 157 | "type": [ 158 | "integer", 159 | "null" 160 | ], 161 | "format": "uint32", 162 | "minimum": 0.0 163 | }, 164 | "page_size": { 165 | "type": "integer", 166 | "format": "uint32", 167 | "minimum": 0.0 168 | } 169 | } 170 | } 171 | } 172 | }, 173 | { 174 | "type": "object", 175 | "required": [ 176 | "minters" 177 | ], 178 | "properties": { 179 | "minters": { 180 | "type": "object" 181 | } 182 | } 183 | } 184 | ], 185 | "definitions": { 186 | "HumanAddr": { 187 | "type": "string" 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /contracts/lp_token/src/batch.rs: -------------------------------------------------------------------------------- 1 | //! Types used in batch operations 2 | 3 | use schemars::JsonSchema; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | use cosmwasm_std::{Binary, HumanAddr, Uint128}; 7 | 8 | #[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)] 9 | #[serde(rename_all = "snake_case")] 10 | pub struct TransferAction { 11 | pub recipient: HumanAddr, 12 | pub amount: Uint128, 13 | pub memo: Option, 14 | } 15 | 16 | #[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)] 17 | #[serde(rename_all = "snake_case")] 18 | pub struct SendAction { 19 | pub recipient: HumanAddr, 20 | pub amount: Uint128, 21 | pub msg: Option, 22 | pub memo: Option, 23 | } 24 | 25 | #[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)] 26 | #[serde(rename_all = "snake_case")] 27 | pub struct TransferFromAction { 28 | pub owner: HumanAddr, 29 | pub recipient: HumanAddr, 30 | pub amount: Uint128, 31 | pub memo: Option, 32 | } 33 | 34 | #[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)] 35 | #[serde(rename_all = "snake_case")] 36 | pub struct SendFromAction { 37 | pub owner: HumanAddr, 38 | pub recipient: HumanAddr, 39 | pub amount: Uint128, 40 | pub msg: Option, 41 | pub memo: Option, 42 | } 43 | 44 | #[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)] 45 | #[serde(rename_all = "snake_case")] 46 | pub struct MintAction { 47 | pub recipient: HumanAddr, 48 | pub amount: Uint128, 49 | pub memo: Option, 50 | } 51 | 52 | #[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)] 53 | #[serde(rename_all = "snake_case")] 54 | pub struct BurnFromAction { 55 | pub owner: HumanAddr, 56 | pub amount: Uint128, 57 | pub memo: Option, 58 | } 59 | -------------------------------------------------------------------------------- /contracts/lp_token/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod batch; 2 | pub mod contract; 3 | pub mod msg; 4 | mod rand; 5 | pub mod receiver; 6 | pub mod state; 7 | mod transaction_history; 8 | mod utils; 9 | mod viewing_key; 10 | 11 | #[cfg(target_arch = "wasm32")] 12 | mod wasm { 13 | use super::contract; 14 | use cosmwasm_std::{ 15 | do_handle, do_init, do_query, ExternalApi, ExternalQuerier, ExternalStorage, 16 | }; 17 | 18 | #[no_mangle] 19 | extern "C" fn init(env_ptr: u32, msg_ptr: u32) -> u32 { 20 | do_init( 21 | &contract::init::, 22 | env_ptr, 23 | msg_ptr, 24 | ) 25 | } 26 | 27 | #[no_mangle] 28 | extern "C" fn handle(env_ptr: u32, msg_ptr: u32) -> u32 { 29 | do_handle( 30 | &contract::handle::, 31 | env_ptr, 32 | msg_ptr, 33 | ) 34 | } 35 | 36 | #[no_mangle] 37 | extern "C" fn query(msg_ptr: u32) -> u32 { 38 | do_query( 39 | &contract::query::, 40 | msg_ptr, 41 | ) 42 | } 43 | 44 | // Other C externs like cosmwasm_vm_version_1, allocate, deallocate are available 45 | // automatically because we `use cosmwasm_std`. 46 | } 47 | -------------------------------------------------------------------------------- /contracts/lp_token/src/msg.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::field_reassign_with_default)] // This is triggered in `#[derive(JsonSchema)]` 2 | 3 | use schemars::JsonSchema; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | use cosmwasm_std::{Binary, HumanAddr, StdError, StdResult, Uint128}; 7 | 8 | use crate::batch; 9 | use crate::transaction_history::{RichTx, Tx}; 10 | use crate::viewing_key::ViewingKey; 11 | 12 | #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema)] 13 | pub struct InitialBalance { 14 | pub address: HumanAddr, 15 | pub amount: Uint128, 16 | } 17 | 18 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] 19 | pub struct AfterInitHook { 20 | pub msg: Binary, 21 | pub contract_addr: HumanAddr, 22 | pub code_hash: String, 23 | } 24 | 25 | #[derive(Serialize, Deserialize, JsonSchema)] 26 | pub struct InitMsg { 27 | pub name: String, 28 | pub admin: Option, 29 | pub symbol: String, 30 | pub decimals: u8, 31 | pub initial_balances: Option>, 32 | pub prng_seed: Binary, 33 | pub config: Option, 34 | pub after_init_hook: Option, 35 | } 36 | 37 | impl InitMsg { 38 | pub fn config(&self) -> InitConfig { 39 | self.config.clone().unwrap_or_default() 40 | } 41 | } 42 | 43 | /// This type represents optional configuration values which can be overridden. 44 | /// All values are optional and have defaults which are more private by default, 45 | /// but can be overridden if necessary 46 | #[derive(Serialize, Deserialize, JsonSchema, Clone, Default, Debug)] 47 | #[serde(rename_all = "snake_case")] 48 | pub struct InitConfig { 49 | /// Indicates whether the total supply is public or should be kept secret. 50 | /// default: False 51 | public_total_supply: Option, 52 | /// Indicates whether deposit functionality should be enabled 53 | /// default: False 54 | enable_deposit: Option, 55 | /// Indicates whether redeem functionality should be enabled 56 | /// default: False 57 | enable_redeem: Option, 58 | /// Indicates whether mint functionality should be enabled 59 | /// default: False 60 | enable_mint: Option, 61 | /// Indicates whether burn functionality should be enabled 62 | /// default: False 63 | enable_burn: Option, 64 | } 65 | 66 | impl InitConfig { 67 | pub fn public_total_supply(&self) -> bool { 68 | self.public_total_supply.unwrap_or(false) 69 | } 70 | 71 | pub fn deposit_enabled(&self) -> bool { 72 | self.enable_deposit.unwrap_or(false) 73 | } 74 | 75 | pub fn redeem_enabled(&self) -> bool { 76 | self.enable_redeem.unwrap_or(false) 77 | } 78 | 79 | pub fn mint_enabled(&self) -> bool { 80 | self.enable_mint.unwrap_or(false) 81 | } 82 | 83 | pub fn burn_enabled(&self) -> bool { 84 | self.enable_burn.unwrap_or(false) 85 | } 86 | } 87 | 88 | #[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)] 89 | #[serde(rename_all = "snake_case")] 90 | pub enum HandleMsg { 91 | // Native coin interactions 92 | Redeem { 93 | amount: Uint128, 94 | denom: Option, 95 | padding: Option, 96 | }, 97 | Deposit { 98 | padding: Option, 99 | }, 100 | 101 | // Base ERC-20 stuff 102 | Transfer { 103 | recipient: HumanAddr, 104 | amount: Uint128, 105 | memo: Option, 106 | padding: Option, 107 | }, 108 | Send { 109 | recipient: HumanAddr, 110 | amount: Uint128, 111 | msg: Option, 112 | memo: Option, 113 | padding: Option, 114 | }, 115 | BatchTransfer { 116 | actions: Vec, 117 | padding: Option, 118 | }, 119 | BatchSend { 120 | actions: Vec, 121 | padding: Option, 122 | }, 123 | Burn { 124 | amount: Uint128, 125 | memo: Option, 126 | padding: Option, 127 | }, 128 | RegisterReceive { 129 | code_hash: String, 130 | padding: Option, 131 | }, 132 | CreateViewingKey { 133 | entropy: String, 134 | padding: Option, 135 | }, 136 | SetViewingKey { 137 | key: String, 138 | padding: Option, 139 | }, 140 | 141 | // Allowance 142 | IncreaseAllowance { 143 | spender: HumanAddr, 144 | amount: Uint128, 145 | expiration: Option, 146 | padding: Option, 147 | }, 148 | DecreaseAllowance { 149 | spender: HumanAddr, 150 | amount: Uint128, 151 | expiration: Option, 152 | padding: Option, 153 | }, 154 | TransferFrom { 155 | owner: HumanAddr, 156 | recipient: HumanAddr, 157 | amount: Uint128, 158 | memo: Option, 159 | padding: Option, 160 | }, 161 | SendFrom { 162 | owner: HumanAddr, 163 | recipient: HumanAddr, 164 | amount: Uint128, 165 | msg: Option, 166 | memo: Option, 167 | padding: Option, 168 | }, 169 | BatchTransferFrom { 170 | actions: Vec, 171 | padding: Option, 172 | }, 173 | BatchSendFrom { 174 | actions: Vec, 175 | padding: Option, 176 | }, 177 | BurnFrom { 178 | owner: HumanAddr, 179 | amount: Uint128, 180 | memo: Option, 181 | padding: Option, 182 | }, 183 | BatchBurnFrom { 184 | actions: Vec, 185 | padding: Option, 186 | }, 187 | 188 | // Mint 189 | Mint { 190 | recipient: HumanAddr, 191 | amount: Uint128, 192 | memo: Option, 193 | padding: Option, 194 | }, 195 | BatchMint { 196 | actions: Vec, 197 | padding: Option, 198 | }, 199 | AddMinters { 200 | minters: Vec, 201 | padding: Option, 202 | }, 203 | RemoveMinters { 204 | minters: Vec, 205 | padding: Option, 206 | }, 207 | SetMinters { 208 | minters: Vec, 209 | padding: Option, 210 | }, 211 | 212 | // Admin 213 | ChangeAdmin { 214 | address: HumanAddr, 215 | padding: Option, 216 | }, 217 | SetContractStatus { 218 | level: ContractStatusLevel, 219 | padding: Option, 220 | }, 221 | } 222 | 223 | #[derive(Serialize, Deserialize, JsonSchema, Debug)] 224 | #[serde(rename_all = "snake_case")] 225 | pub enum HandleAnswer { 226 | // Native 227 | Deposit { 228 | status: ResponseStatus, 229 | }, 230 | Redeem { 231 | status: ResponseStatus, 232 | }, 233 | 234 | // Base 235 | Transfer { 236 | status: ResponseStatus, 237 | }, 238 | Send { 239 | status: ResponseStatus, 240 | }, 241 | BatchTransfer { 242 | status: ResponseStatus, 243 | }, 244 | BatchSend { 245 | status: ResponseStatus, 246 | }, 247 | Burn { 248 | status: ResponseStatus, 249 | }, 250 | RegisterReceive { 251 | status: ResponseStatus, 252 | }, 253 | CreateViewingKey { 254 | key: ViewingKey, 255 | }, 256 | SetViewingKey { 257 | status: ResponseStatus, 258 | }, 259 | 260 | // Allowance 261 | IncreaseAllowance { 262 | spender: HumanAddr, 263 | owner: HumanAddr, 264 | allowance: Uint128, 265 | }, 266 | DecreaseAllowance { 267 | spender: HumanAddr, 268 | owner: HumanAddr, 269 | allowance: Uint128, 270 | }, 271 | TransferFrom { 272 | status: ResponseStatus, 273 | }, 274 | SendFrom { 275 | status: ResponseStatus, 276 | }, 277 | BatchTransferFrom { 278 | status: ResponseStatus, 279 | }, 280 | BatchSendFrom { 281 | status: ResponseStatus, 282 | }, 283 | BurnFrom { 284 | status: ResponseStatus, 285 | }, 286 | BatchBurnFrom { 287 | status: ResponseStatus, 288 | }, 289 | 290 | // Mint 291 | Mint { 292 | status: ResponseStatus, 293 | }, 294 | BatchMint { 295 | status: ResponseStatus, 296 | }, 297 | AddMinters { 298 | status: ResponseStatus, 299 | }, 300 | RemoveMinters { 301 | status: ResponseStatus, 302 | }, 303 | SetMinters { 304 | status: ResponseStatus, 305 | }, 306 | 307 | // Other 308 | ChangeAdmin { 309 | status: ResponseStatus, 310 | }, 311 | SetContractStatus { 312 | status: ResponseStatus, 313 | }, 314 | } 315 | 316 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] 317 | #[serde(rename_all = "snake_case")] 318 | pub enum QueryMsg { 319 | TokenInfo {}, 320 | TokenConfig {}, 321 | ContractStatus {}, 322 | ExchangeRate {}, 323 | Allowance { 324 | owner: HumanAddr, 325 | spender: HumanAddr, 326 | key: String, 327 | }, 328 | Balance { 329 | address: HumanAddr, 330 | key: String, 331 | }, 332 | TransferHistory { 333 | address: HumanAddr, 334 | key: String, 335 | page: Option, 336 | page_size: u32, 337 | }, 338 | TransactionHistory { 339 | address: HumanAddr, 340 | key: String, 341 | page: Option, 342 | page_size: u32, 343 | }, 344 | Minters {}, 345 | } 346 | 347 | impl QueryMsg { 348 | pub fn get_validation_params(&self) -> (Vec<&HumanAddr>, ViewingKey) { 349 | match self { 350 | Self::Balance { address, key } => (vec![address], ViewingKey(key.clone())), 351 | Self::TransferHistory { address, key, .. } => (vec![address], ViewingKey(key.clone())), 352 | Self::TransactionHistory { address, key, .. } => { 353 | (vec![address], ViewingKey(key.clone())) 354 | } 355 | Self::Allowance { 356 | owner, 357 | spender, 358 | key, 359 | .. 360 | } => (vec![owner, spender], ViewingKey(key.clone())), 361 | _ => panic!("This query type does not require authentication"), 362 | } 363 | } 364 | } 365 | 366 | #[derive(Serialize, Deserialize, JsonSchema, Debug)] 367 | #[serde(rename_all = "snake_case")] 368 | pub enum QueryAnswer { 369 | TokenInfo { 370 | name: String, 371 | symbol: String, 372 | decimals: u8, 373 | total_supply: Option, 374 | }, 375 | TokenConfig { 376 | public_total_supply: bool, 377 | deposit_enabled: bool, 378 | redeem_enabled: bool, 379 | mint_enabled: bool, 380 | burn_enabled: bool, 381 | }, 382 | ContractStatus { 383 | status: ContractStatusLevel, 384 | }, 385 | ExchangeRate { 386 | rate: Uint128, 387 | denom: String, 388 | }, 389 | Allowance { 390 | spender: HumanAddr, 391 | owner: HumanAddr, 392 | allowance: Uint128, 393 | expiration: Option, 394 | }, 395 | Balance { 396 | amount: Uint128, 397 | }, 398 | TransferHistory { 399 | txs: Vec, 400 | total: Option, 401 | }, 402 | TransactionHistory { 403 | txs: Vec, 404 | total: Option, 405 | }, 406 | ViewingKeyError { 407 | msg: String, 408 | }, 409 | Minters { 410 | minters: Vec, 411 | }, 412 | } 413 | 414 | #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema)] 415 | pub struct CreateViewingKeyResponse { 416 | pub key: String, 417 | } 418 | 419 | #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] 420 | #[serde(rename_all = "snake_case")] 421 | pub enum ResponseStatus { 422 | Success, 423 | Failure, 424 | } 425 | 426 | #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] 427 | #[serde(rename_all = "snake_case")] 428 | pub enum ContractStatusLevel { 429 | NormalRun, 430 | StopAllButRedeems, 431 | StopAll, 432 | } 433 | 434 | pub fn status_level_to_u8(status_level: ContractStatusLevel) -> u8 { 435 | match status_level { 436 | ContractStatusLevel::NormalRun => 0, 437 | ContractStatusLevel::StopAllButRedeems => 1, 438 | ContractStatusLevel::StopAll => 2, 439 | } 440 | } 441 | 442 | pub fn u8_to_status_level(status_level: u8) -> StdResult { 443 | match status_level { 444 | 0 => Ok(ContractStatusLevel::NormalRun), 445 | 1 => Ok(ContractStatusLevel::StopAllButRedeems), 446 | 2 => Ok(ContractStatusLevel::StopAll), 447 | _ => Err(StdError::generic_err("Invalid state level")), 448 | } 449 | } 450 | 451 | // Take a Vec and pad it up to a multiple of `block_size`, using spaces at the end. 452 | pub fn space_pad(block_size: usize, message: &mut Vec) -> &mut Vec { 453 | let len = message.len(); 454 | let surplus = len % block_size; 455 | if surplus == 0 { 456 | return message; 457 | } 458 | 459 | let missing = block_size - surplus; 460 | message.reserve(missing); 461 | message.extend(std::iter::repeat(b' ').take(missing)); 462 | message 463 | } 464 | 465 | #[cfg(test)] 466 | mod tests { 467 | use super::*; 468 | use cosmwasm_std::{from_slice, StdResult}; 469 | 470 | #[derive(Serialize, Deserialize, JsonSchema, Debug, PartialEq)] 471 | #[serde(rename_all = "snake_case")] 472 | pub enum Something { 473 | Var { padding: Option }, 474 | } 475 | 476 | #[test] 477 | fn test_deserialization_of_missing_option_fields() -> StdResult<()> { 478 | let input = b"{ \"var\": {} }"; 479 | let obj: Something = from_slice(input)?; 480 | assert_eq!( 481 | obj, 482 | Something::Var { padding: None }, 483 | "unexpected value: {:?}", 484 | obj 485 | ); 486 | Ok(()) 487 | } 488 | } 489 | -------------------------------------------------------------------------------- /contracts/lp_token/src/rand.rs: -------------------------------------------------------------------------------- 1 | use rand_chacha::ChaChaRng; 2 | use rand_core::{RngCore, SeedableRng}; 3 | 4 | use sha2::{Digest, Sha256}; 5 | 6 | pub fn sha_256(data: &[u8]) -> [u8; 32] { 7 | let mut hasher = Sha256::new(); 8 | hasher.update(data); 9 | let hash = hasher.finalize(); 10 | 11 | let mut result = [0u8; 32]; 12 | result.copy_from_slice(hash.as_slice()); 13 | result 14 | } 15 | 16 | pub struct Prng { 17 | rng: ChaChaRng, 18 | } 19 | 20 | impl Prng { 21 | pub fn new(seed: &[u8], entropy: &[u8]) -> Self { 22 | let mut hasher = Sha256::new(); 23 | 24 | // write input message 25 | hasher.update(&seed); 26 | hasher.update(&entropy); 27 | let hash = hasher.finalize(); 28 | 29 | let mut hash_bytes = [0u8; 32]; 30 | hash_bytes.copy_from_slice(hash.as_slice()); 31 | 32 | let rng: ChaChaRng = ChaChaRng::from_seed(hash_bytes); 33 | 34 | Self { rng } 35 | } 36 | 37 | pub fn rand_bytes(&mut self) -> [u8; 32] { 38 | let mut bytes = [0u8; 32]; 39 | self.rng.fill_bytes(&mut bytes); 40 | 41 | bytes 42 | } 43 | } 44 | 45 | #[cfg(test)] 46 | mod tests { 47 | use super::*; 48 | 49 | /// This test checks that the rng is stateful and generates 50 | /// different random bytes every time it is called. 51 | #[test] 52 | fn test_rng() { 53 | let mut rng = Prng::new(b"foo", b"bar!"); 54 | let r1: [u8; 32] = [ 55 | 155, 11, 21, 97, 252, 65, 160, 190, 100, 126, 85, 251, 47, 73, 160, 49, 216, 182, 93, 56 | 30, 185, 67, 166, 22, 34, 10, 213, 112, 21, 136, 49, 214, 57 | ]; 58 | let r2: [u8; 32] = [ 59 | 46, 135, 19, 242, 111, 125, 59, 215, 114, 130, 122, 155, 202, 23, 36, 118, 83, 11, 6, 60 | 180, 97, 165, 218, 136, 134, 243, 191, 191, 149, 178, 7, 149, 61 | ]; 62 | let r3: [u8; 32] = [ 63 | 9, 2, 131, 50, 199, 170, 6, 68, 168, 28, 242, 182, 35, 114, 15, 163, 65, 139, 101, 221, 64 | 207, 147, 119, 110, 81, 195, 6, 134, 14, 253, 245, 244, 65 | ]; 66 | let r4: [u8; 32] = [ 67 | 68, 196, 114, 205, 225, 64, 201, 179, 18, 77, 216, 197, 211, 13, 21, 196, 11, 102, 106, 68 | 195, 138, 250, 29, 185, 51, 38, 183, 0, 5, 169, 65, 190, 69 | ]; 70 | assert_eq!(r1, rng.rand_bytes()); 71 | assert_eq!(r2, rng.rand_bytes()); 72 | assert_eq!(r3, rng.rand_bytes()); 73 | assert_eq!(r4, rng.rand_bytes()); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /contracts/lp_token/src/receiver.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::field_reassign_with_default)] // This is triggered in `#[derive(JsonSchema)]` 2 | 3 | use schemars::JsonSchema; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | use cosmwasm_std::{to_binary, Binary, CosmosMsg, HumanAddr, StdResult, Uint128, WasmMsg}; 7 | 8 | use crate::{contract::RESPONSE_BLOCK_SIZE, msg::space_pad}; 9 | 10 | /// Snip20ReceiveMsg should be de/serialized under `Receive()` variant in a HandleMsg 11 | #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] 12 | #[serde(rename_all = "snake_case")] 13 | pub struct Snip20ReceiveMsg { 14 | pub sender: HumanAddr, 15 | pub from: HumanAddr, 16 | pub amount: Uint128, 17 | #[serde(skip_serializing_if = "Option::is_none")] 18 | pub memo: Option, 19 | pub msg: Option, 20 | } 21 | 22 | impl Snip20ReceiveMsg { 23 | pub fn new( 24 | sender: HumanAddr, 25 | from: HumanAddr, 26 | amount: Uint128, 27 | memo: Option, 28 | msg: Option, 29 | ) -> Self { 30 | Self { 31 | sender, 32 | from, 33 | amount, 34 | memo, 35 | msg, 36 | } 37 | } 38 | 39 | /// serializes the message, and pads it to 256 bytes 40 | pub fn into_binary(self) -> StdResult { 41 | let msg = ReceiverHandleMsg::Receive(self); 42 | let mut data = to_binary(&msg)?; 43 | space_pad(RESPONSE_BLOCK_SIZE, &mut data.0); 44 | Ok(data) 45 | } 46 | 47 | /// creates a cosmos_msg sending this struct to the named contract 48 | pub fn into_cosmos_msg( 49 | self, 50 | callback_code_hash: String, 51 | contract_addr: HumanAddr, 52 | ) -> StdResult { 53 | let msg = self.into_binary()?; 54 | let execute = WasmMsg::Execute { 55 | msg, 56 | callback_code_hash, 57 | contract_addr, 58 | send: vec![], 59 | }; 60 | Ok(execute.into()) 61 | } 62 | } 63 | 64 | // This is just a helper to properly serialize the above message 65 | #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] 66 | #[serde(rename_all = "snake_case")] 67 | enum ReceiverHandleMsg { 68 | Receive(Snip20ReceiveMsg), 69 | } 70 | -------------------------------------------------------------------------------- /contracts/lp_token/src/state.rs: -------------------------------------------------------------------------------- 1 | use std::any::type_name; 2 | use std::convert::TryFrom; 3 | 4 | use cosmwasm_std::{CanonicalAddr, HumanAddr, ReadonlyStorage, StdError, StdResult, Storage}; 5 | use cosmwasm_storage::{PrefixedStorage, ReadonlyPrefixedStorage}; 6 | 7 | use secret_toolkit::storage::{TypedStore, TypedStoreMut}; 8 | 9 | use schemars::JsonSchema; 10 | use serde::{Deserialize, Serialize}; 11 | 12 | use crate::msg::{status_level_to_u8, u8_to_status_level, ContractStatusLevel}; 13 | use crate::viewing_key::ViewingKey; 14 | use serde::de::DeserializeOwned; 15 | 16 | pub static CONFIG_KEY: &[u8] = b"config"; 17 | pub const PREFIX_TXS: &[u8] = b"transfers"; 18 | 19 | pub const KEY_CONSTANTS: &[u8] = b"constants"; 20 | pub const KEY_TOTAL_SUPPLY: &[u8] = b"total_supply"; 21 | pub const KEY_CONTRACT_STATUS: &[u8] = b"contract_status"; 22 | pub const KEY_MINTERS: &[u8] = b"minters"; 23 | pub const KEY_TX_COUNT: &[u8] = b"tx-count"; 24 | 25 | pub const PREFIX_CONFIG: &[u8] = b"config"; 26 | pub const PREFIX_BALANCES: &[u8] = b"balances"; 27 | pub const PREFIX_ALLOWANCES: &[u8] = b"allowances"; 28 | pub const PREFIX_VIEW_KEY: &[u8] = b"viewingkey"; 29 | pub const PREFIX_RECEIVERS: &[u8] = b"receivers"; 30 | 31 | // Config 32 | 33 | #[derive(Serialize, Debug, Deserialize, Clone, PartialEq, JsonSchema)] 34 | pub struct Constants { 35 | pub name: String, 36 | pub admin: HumanAddr, 37 | pub symbol: String, 38 | pub decimals: u8, 39 | pub prng_seed: Vec, 40 | // privacy configuration 41 | pub total_supply_is_public: bool, 42 | // is deposit enabled 43 | pub deposit_is_enabled: bool, 44 | // is redeem enabled 45 | pub redeem_is_enabled: bool, 46 | // is mint enabled 47 | pub mint_is_enabled: bool, 48 | // is burn enabled 49 | pub burn_is_enabled: bool, 50 | } 51 | 52 | pub struct ReadonlyConfig<'a, S: ReadonlyStorage> { 53 | storage: ReadonlyPrefixedStorage<'a, S>, 54 | } 55 | 56 | impl<'a, S: ReadonlyStorage> ReadonlyConfig<'a, S> { 57 | pub fn from_storage(storage: &'a S) -> Self { 58 | Self { 59 | storage: ReadonlyPrefixedStorage::new(PREFIX_CONFIG, storage), 60 | } 61 | } 62 | 63 | fn as_readonly(&self) -> ReadonlyConfigImpl> { 64 | ReadonlyConfigImpl(&self.storage) 65 | } 66 | 67 | pub fn constants(&self) -> StdResult { 68 | self.as_readonly().constants() 69 | } 70 | 71 | pub fn total_supply(&self) -> u128 { 72 | self.as_readonly().total_supply() 73 | } 74 | 75 | pub fn contract_status(&self) -> ContractStatusLevel { 76 | self.as_readonly().contract_status() 77 | } 78 | 79 | pub fn minters(&self) -> Vec { 80 | self.as_readonly().minters() 81 | } 82 | 83 | pub fn tx_count(&self) -> u64 { 84 | self.as_readonly().tx_count() 85 | } 86 | } 87 | 88 | fn ser_bin_data(obj: &T) -> StdResult> { 89 | bincode2::serialize(&obj).map_err(|e| StdError::serialize_err(type_name::(), e)) 90 | } 91 | 92 | fn deser_bin_data(data: &[u8]) -> StdResult { 93 | bincode2::deserialize::(&data).map_err(|e| StdError::serialize_err(type_name::(), e)) 94 | } 95 | 96 | fn set_bin_data(storage: &mut S, key: &[u8], data: &T) -> StdResult<()> { 97 | let bin_data = ser_bin_data(data)?; 98 | 99 | storage.set(key, &bin_data); 100 | Ok(()) 101 | } 102 | 103 | fn get_bin_data(storage: &S, key: &[u8]) -> StdResult { 104 | let bin_data = storage.get(key); 105 | 106 | match bin_data { 107 | None => Err(StdError::not_found("Key not found in storage")), 108 | Some(bin_data) => Ok(deser_bin_data(&bin_data)?), 109 | } 110 | } 111 | 112 | pub struct Config<'a, S: Storage> { 113 | storage: PrefixedStorage<'a, S>, 114 | } 115 | 116 | impl<'a, S: Storage> Config<'a, S> { 117 | pub fn from_storage(storage: &'a mut S) -> Self { 118 | Self { 119 | storage: PrefixedStorage::new(PREFIX_CONFIG, storage), 120 | } 121 | } 122 | 123 | fn as_readonly(&self) -> ReadonlyConfigImpl> { 124 | ReadonlyConfigImpl(&self.storage) 125 | } 126 | 127 | pub fn constants(&self) -> StdResult { 128 | self.as_readonly().constants() 129 | } 130 | 131 | pub fn set_constants(&mut self, constants: &Constants) -> StdResult<()> { 132 | set_bin_data(&mut self.storage, KEY_CONSTANTS, constants) 133 | } 134 | 135 | pub fn total_supply(&self) -> u128 { 136 | self.as_readonly().total_supply() 137 | } 138 | 139 | pub fn set_total_supply(&mut self, supply: u128) { 140 | self.storage.set(KEY_TOTAL_SUPPLY, &supply.to_be_bytes()); 141 | } 142 | 143 | pub fn contract_status(&self) -> ContractStatusLevel { 144 | self.as_readonly().contract_status() 145 | } 146 | 147 | pub fn set_contract_status(&mut self, status: ContractStatusLevel) { 148 | let status_u8 = status_level_to_u8(status); 149 | self.storage 150 | .set(KEY_CONTRACT_STATUS, &status_u8.to_be_bytes()); 151 | } 152 | 153 | pub fn set_minters(&mut self, minters_to_set: Vec) -> StdResult<()> { 154 | set_bin_data(&mut self.storage, KEY_MINTERS, &minters_to_set) 155 | } 156 | 157 | pub fn add_minters(&mut self, minters_to_add: Vec) -> StdResult<()> { 158 | let mut minters = self.minters(); 159 | minters.extend(minters_to_add); 160 | 161 | self.set_minters(minters) 162 | } 163 | 164 | pub fn remove_minters(&mut self, minters_to_remove: Vec) -> StdResult<()> { 165 | let mut minters = self.minters(); 166 | 167 | for minter in minters_to_remove { 168 | minters.retain(|x| x != &minter); 169 | } 170 | 171 | self.set_minters(minters) 172 | } 173 | 174 | pub fn minters(&mut self) -> Vec { 175 | self.as_readonly().minters() 176 | } 177 | 178 | pub fn tx_count(&self) -> u64 { 179 | self.as_readonly().tx_count() 180 | } 181 | 182 | pub fn set_tx_count(&mut self, count: u64) -> StdResult<()> { 183 | set_bin_data(&mut self.storage, KEY_TX_COUNT, &count) 184 | } 185 | } 186 | 187 | /// This struct refactors out the readonly methods that we need for `Config` and `ReadonlyConfig` 188 | /// in a way that is generic over their mutability. 189 | /// 190 | /// This was the only way to prevent code duplication of these methods because of the way 191 | /// that `ReadonlyPrefixedStorage` and `PrefixedStorage` are implemented in `cosmwasm-std` 192 | struct ReadonlyConfigImpl<'a, S: ReadonlyStorage>(&'a S); 193 | 194 | impl<'a, S: ReadonlyStorage> ReadonlyConfigImpl<'a, S> { 195 | fn constants(&self) -> StdResult { 196 | let consts_bytes = self 197 | .0 198 | .get(KEY_CONSTANTS) 199 | .ok_or_else(|| StdError::generic_err("no constants stored in configuration"))?; 200 | bincode2::deserialize::(&consts_bytes) 201 | .map_err(|e| StdError::serialize_err(type_name::(), e)) 202 | } 203 | 204 | fn total_supply(&self) -> u128 { 205 | let supply_bytes = self 206 | .0 207 | .get(KEY_TOTAL_SUPPLY) 208 | .expect("no total supply stored in config"); 209 | // This unwrap is ok because we know we stored things correctly 210 | slice_to_u128(&supply_bytes).unwrap() 211 | } 212 | 213 | fn contract_status(&self) -> ContractStatusLevel { 214 | let supply_bytes = self 215 | .0 216 | .get(KEY_CONTRACT_STATUS) 217 | .expect("no contract status stored in config"); 218 | 219 | // These unwraps are ok because we know we stored things correctly 220 | let status = slice_to_u8(&supply_bytes).unwrap(); 221 | u8_to_status_level(status).unwrap() 222 | } 223 | 224 | fn minters(&self) -> Vec { 225 | get_bin_data(self.0, KEY_MINTERS).unwrap() 226 | } 227 | 228 | pub fn tx_count(&self) -> u64 { 229 | get_bin_data(self.0, KEY_TX_COUNT).unwrap_or_default() 230 | } 231 | } 232 | 233 | // Balances 234 | 235 | pub struct ReadonlyBalances<'a, S: ReadonlyStorage> { 236 | storage: ReadonlyPrefixedStorage<'a, S>, 237 | } 238 | 239 | impl<'a, S: ReadonlyStorage> ReadonlyBalances<'a, S> { 240 | pub fn from_storage(storage: &'a S) -> Self { 241 | Self { 242 | storage: ReadonlyPrefixedStorage::new(PREFIX_BALANCES, storage), 243 | } 244 | } 245 | 246 | fn as_readonly(&self) -> ReadonlyBalancesImpl> { 247 | ReadonlyBalancesImpl(&self.storage) 248 | } 249 | 250 | pub fn account_amount(&self, account: &CanonicalAddr) -> u128 { 251 | self.as_readonly().account_amount(account) 252 | } 253 | } 254 | 255 | pub struct Balances<'a, S: Storage> { 256 | storage: PrefixedStorage<'a, S>, 257 | } 258 | 259 | impl<'a, S: Storage> Balances<'a, S> { 260 | pub fn from_storage(storage: &'a mut S) -> Self { 261 | Self { 262 | storage: PrefixedStorage::new(PREFIX_BALANCES, storage), 263 | } 264 | } 265 | 266 | fn as_readonly(&self) -> ReadonlyBalancesImpl> { 267 | ReadonlyBalancesImpl(&self.storage) 268 | } 269 | 270 | pub fn balance(&self, account: &CanonicalAddr) -> u128 { 271 | self.as_readonly().account_amount(account) 272 | } 273 | 274 | pub fn set_account_balance(&mut self, account: &CanonicalAddr, amount: u128) { 275 | self.storage.set(account.as_slice(), &amount.to_be_bytes()) 276 | } 277 | } 278 | 279 | /// This struct refactors out the readonly methods that we need for `Balances` and `ReadonlyBalances` 280 | /// in a way that is generic over their mutability. 281 | /// 282 | /// This was the only way to prevent code duplication of these methods because of the way 283 | /// that `ReadonlyPrefixedStorage` and `PrefixedStorage` are implemented in `cosmwasm-std` 284 | struct ReadonlyBalancesImpl<'a, S: ReadonlyStorage>(&'a S); 285 | 286 | impl<'a, S: ReadonlyStorage> ReadonlyBalancesImpl<'a, S> { 287 | pub fn account_amount(&self, account: &CanonicalAddr) -> u128 { 288 | let account_bytes = account.as_slice(); 289 | let result = self.0.get(account_bytes); 290 | match result { 291 | // This unwrap is ok because we know we stored things correctly 292 | Some(balance_bytes) => slice_to_u128(&balance_bytes).unwrap(), 293 | None => 0, 294 | } 295 | } 296 | } 297 | 298 | // Allowances 299 | 300 | #[derive(Serialize, Debug, Deserialize, Clone, PartialEq, Default, JsonSchema)] 301 | pub struct Allowance { 302 | pub amount: u128, 303 | pub expiration: Option, 304 | } 305 | 306 | impl Allowance { 307 | pub fn is_expired_at(&self, block: &cosmwasm_std::BlockInfo) -> bool { 308 | match self.expiration { 309 | Some(time) => block.time >= time, 310 | None => false, // allowance has no expiration 311 | } 312 | } 313 | } 314 | 315 | pub fn read_allowance( 316 | store: &S, 317 | owner: &CanonicalAddr, 318 | spender: &CanonicalAddr, 319 | ) -> StdResult { 320 | let owner_store = 321 | ReadonlyPrefixedStorage::multilevel(&[PREFIX_ALLOWANCES, owner.as_slice()], store); 322 | let owner_store = TypedStore::attach(&owner_store); 323 | let allowance = owner_store.may_load(spender.as_slice()); 324 | allowance.map(Option::unwrap_or_default) 325 | } 326 | 327 | pub fn write_allowance( 328 | store: &mut S, 329 | owner: &CanonicalAddr, 330 | spender: &CanonicalAddr, 331 | allowance: Allowance, 332 | ) -> StdResult<()> { 333 | let mut owner_store = 334 | PrefixedStorage::multilevel(&[PREFIX_ALLOWANCES, owner.as_slice()], store); 335 | let mut owner_store = TypedStoreMut::attach(&mut owner_store); 336 | 337 | owner_store.store(spender.as_slice(), &allowance) 338 | } 339 | 340 | // Viewing Keys 341 | 342 | pub fn write_viewing_key(store: &mut S, owner: &CanonicalAddr, key: &ViewingKey) { 343 | let mut balance_store = PrefixedStorage::new(PREFIX_VIEW_KEY, store); 344 | balance_store.set(owner.as_slice(), &key.to_hashed()); 345 | } 346 | 347 | pub fn read_viewing_key(store: &S, owner: &CanonicalAddr) -> Option> { 348 | let balance_store = ReadonlyPrefixedStorage::new(PREFIX_VIEW_KEY, store); 349 | balance_store.get(owner.as_slice()) 350 | } 351 | 352 | // Receiver Interface 353 | 354 | pub fn get_receiver_hash( 355 | store: &S, 356 | account: &HumanAddr, 357 | ) -> Option> { 358 | let store = ReadonlyPrefixedStorage::new(PREFIX_RECEIVERS, store); 359 | store.get(account.as_str().as_bytes()).map(|data| { 360 | String::from_utf8(data) 361 | .map_err(|_err| StdError::invalid_utf8("stored code hash was not a valid String")) 362 | }) 363 | } 364 | 365 | pub fn set_receiver_hash(store: &mut S, account: &HumanAddr, code_hash: String) { 366 | let mut store = PrefixedStorage::new(PREFIX_RECEIVERS, store); 367 | store.set(account.as_str().as_bytes(), code_hash.as_bytes()); 368 | } 369 | 370 | // Helpers 371 | 372 | /// Converts 16 bytes value into u128 373 | /// Errors if data found that is not 16 bytes 374 | fn slice_to_u128(data: &[u8]) -> StdResult { 375 | match <[u8; 16]>::try_from(data) { 376 | Ok(bytes) => Ok(u128::from_be_bytes(bytes)), 377 | Err(_) => Err(StdError::generic_err( 378 | "Corrupted data found. 16 byte expected.", 379 | )), 380 | } 381 | } 382 | 383 | /// Converts 1 byte value into u8 384 | /// Errors if data found that is not 1 byte 385 | fn slice_to_u8(data: &[u8]) -> StdResult { 386 | if data.len() == 1 { 387 | Ok(data[0]) 388 | } else { 389 | Err(StdError::generic_err( 390 | "Corrupted data found. 1 byte expected.", 391 | )) 392 | } 393 | } 394 | -------------------------------------------------------------------------------- /contracts/lp_token/src/transaction_history.rs: -------------------------------------------------------------------------------- 1 | use schemars::JsonSchema; 2 | use serde::{Deserialize, Serialize}; 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}; 10 | 11 | use crate::state::Config; 12 | 13 | const PREFIX_TXS: &[u8] = b"transactions"; 14 | const PREFIX_TRANSFERS: &[u8] = b"transfers"; 15 | 16 | // Note that id is a globally incrementing counter. 17 | // Since it's 64 bits long, even at 50 tx/s it would take 18 | // over 11 billion years for it to rollback. I'm pretty sure 19 | // we'll have bigger issues by then. 20 | #[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)] 21 | pub struct Tx { 22 | pub id: u64, 23 | pub from: HumanAddr, 24 | pub sender: HumanAddr, 25 | pub receiver: HumanAddr, 26 | pub coins: Coin, 27 | #[serde(skip_serializing_if = "Option::is_none")] 28 | pub memo: Option, 29 | // The block time and block height are optional so that the JSON schema 30 | // reflects that some SNIP-20 contracts may not include this info. 31 | pub block_time: Option, 32 | pub block_height: Option, 33 | } 34 | 35 | #[derive(Serialize, Deserialize, JsonSchema, Clone, Debug, PartialEq)] 36 | #[serde(rename_all = "snake_case")] 37 | pub enum TxAction { 38 | Transfer { 39 | from: HumanAddr, 40 | sender: HumanAddr, 41 | recipient: HumanAddr, 42 | }, 43 | Mint { 44 | minter: HumanAddr, 45 | recipient: HumanAddr, 46 | }, 47 | Burn { 48 | burner: HumanAddr, 49 | owner: HumanAddr, 50 | }, 51 | Deposit {}, 52 | Redeem {}, 53 | } 54 | 55 | // Note that id is a globally incrementing counter. 56 | // Since it's 64 bits long, even at 50 tx/s it would take 57 | // over 11 billion years for it to rollback. I'm pretty sure 58 | // we'll have bigger issues by then. 59 | #[derive(Serialize, Deserialize, JsonSchema, Clone, Debug, PartialEq)] 60 | #[serde(rename_all = "snake_case")] 61 | pub struct RichTx { 62 | pub id: u64, 63 | pub action: TxAction, 64 | pub coins: Coin, 65 | #[serde(skip_serializing_if = "Option::is_none")] 66 | pub memo: Option, 67 | pub block_time: u64, 68 | pub block_height: u64, 69 | } 70 | 71 | // Stored types: 72 | 73 | /// This type is the stored version of the legacy transfers 74 | #[derive(Serialize, Deserialize, Clone, Debug)] 75 | #[serde(rename_all = "snake_case")] 76 | struct StoredLegacyTransfer { 77 | id: u64, 78 | from: CanonicalAddr, 79 | sender: CanonicalAddr, 80 | receiver: CanonicalAddr, 81 | coins: Coin, 82 | memo: Option, 83 | block_time: u64, 84 | block_height: u64, 85 | } 86 | 87 | impl StoredLegacyTransfer { 88 | pub fn into_humanized(self, api: &A) -> StdResult { 89 | let tx = Tx { 90 | id: self.id, 91 | from: api.human_address(&self.from)?, 92 | sender: api.human_address(&self.sender)?, 93 | receiver: api.human_address(&self.receiver)?, 94 | coins: self.coins, 95 | memo: self.memo, 96 | block_time: Some(self.block_time), 97 | block_height: Some(self.block_height), 98 | }; 99 | Ok(tx) 100 | } 101 | } 102 | 103 | #[derive(Clone, Copy, Debug)] 104 | #[repr(u8)] 105 | enum TxCode { 106 | Transfer = 0, 107 | Mint = 1, 108 | Burn = 2, 109 | Deposit = 3, 110 | Redeem = 4, 111 | } 112 | 113 | impl TxCode { 114 | fn to_u8(self) -> u8 { 115 | self as u8 116 | } 117 | 118 | fn from_u8(n: u8) -> StdResult { 119 | use TxCode::*; 120 | match n { 121 | 0 => Ok(Transfer), 122 | 1 => Ok(Mint), 123 | 2 => Ok(Burn), 124 | 3 => Ok(Deposit), 125 | 4 => Ok(Redeem), 126 | other => Err(StdError::generic_err(format!( 127 | "Unexpected Tx code in transaction history: {} Storage is corrupted.", 128 | other 129 | ))), 130 | } 131 | } 132 | } 133 | 134 | #[derive(Serialize, Deserialize, Clone, Debug)] 135 | #[serde(rename_all = "snake_case")] 136 | struct StoredTxAction { 137 | tx_type: u8, 138 | address1: Option, 139 | address2: Option, 140 | address3: Option, 141 | } 142 | 143 | impl StoredTxAction { 144 | fn transfer(from: CanonicalAddr, sender: CanonicalAddr, recipient: CanonicalAddr) -> Self { 145 | Self { 146 | tx_type: TxCode::Transfer.to_u8(), 147 | address1: Some(from), 148 | address2: Some(sender), 149 | address3: Some(recipient), 150 | } 151 | } 152 | fn mint(minter: CanonicalAddr, recipient: CanonicalAddr) -> Self { 153 | Self { 154 | tx_type: TxCode::Mint.to_u8(), 155 | address1: Some(minter), 156 | address2: Some(recipient), 157 | address3: None, 158 | } 159 | } 160 | fn burn(owner: CanonicalAddr, burner: CanonicalAddr) -> Self { 161 | Self { 162 | tx_type: TxCode::Burn.to_u8(), 163 | address1: Some(burner), 164 | address2: Some(owner), 165 | address3: None, 166 | } 167 | } 168 | fn deposit() -> Self { 169 | Self { 170 | tx_type: TxCode::Deposit.to_u8(), 171 | address1: None, 172 | address2: None, 173 | address3: None, 174 | } 175 | } 176 | fn redeem() -> Self { 177 | Self { 178 | tx_type: TxCode::Redeem.to_u8(), 179 | address1: None, 180 | address2: None, 181 | address3: None, 182 | } 183 | } 184 | 185 | fn into_humanized(self, api: &A) -> StdResult { 186 | let transfer_addr_err = || { 187 | StdError::generic_err( 188 | "Missing address in stored Transfer transaction. Storage is corrupt", 189 | ) 190 | }; 191 | let mint_addr_err = || { 192 | StdError::generic_err("Missing address in stored Mint transaction. Storage is corrupt") 193 | }; 194 | let burn_addr_err = || { 195 | StdError::generic_err("Missing address in stored Burn transaction. Storage is corrupt") 196 | }; 197 | 198 | // In all of these, we ignore fields that we don't expect to find populated 199 | let action = match TxCode::from_u8(self.tx_type)? { 200 | TxCode::Transfer => { 201 | let from = self.address1.ok_or_else(transfer_addr_err)?; 202 | let sender = self.address2.ok_or_else(transfer_addr_err)?; 203 | let recipient = self.address3.ok_or_else(transfer_addr_err)?; 204 | let from = api.human_address(&from)?; 205 | let sender = api.human_address(&sender)?; 206 | let recipient = api.human_address(&recipient)?; 207 | TxAction::Transfer { 208 | from, 209 | sender, 210 | recipient, 211 | } 212 | } 213 | TxCode::Mint => { 214 | let minter = self.address1.ok_or_else(mint_addr_err)?; 215 | let recipient = self.address2.ok_or_else(mint_addr_err)?; 216 | let minter = api.human_address(&minter)?; 217 | let recipient = api.human_address(&recipient)?; 218 | TxAction::Mint { minter, recipient } 219 | } 220 | TxCode::Burn => { 221 | let burner = self.address1.ok_or_else(burn_addr_err)?; 222 | let owner = self.address2.ok_or_else(burn_addr_err)?; 223 | let burner = api.human_address(&burner)?; 224 | let owner = api.human_address(&owner)?; 225 | TxAction::Burn { burner, owner } 226 | } 227 | TxCode::Deposit => TxAction::Deposit {}, 228 | TxCode::Redeem => TxAction::Redeem {}, 229 | }; 230 | 231 | Ok(action) 232 | } 233 | } 234 | 235 | #[derive(Serialize, Deserialize, Clone, Debug)] 236 | #[serde(rename_all = "snake_case")] 237 | struct StoredRichTx { 238 | id: u64, 239 | action: StoredTxAction, 240 | coins: Coin, 241 | memo: Option, 242 | block_time: u64, 243 | block_height: u64, 244 | } 245 | 246 | impl StoredRichTx { 247 | fn new( 248 | id: u64, 249 | action: StoredTxAction, 250 | coins: Coin, 251 | memo: Option, 252 | block: &cosmwasm_std::BlockInfo, 253 | ) -> Self { 254 | Self { 255 | id, 256 | action, 257 | coins, 258 | memo, 259 | block_time: block.time, 260 | block_height: block.height, 261 | } 262 | } 263 | 264 | fn into_humanized(self, api: &A) -> StdResult { 265 | Ok(RichTx { 266 | id: self.id, 267 | action: self.action.into_humanized(api)?, 268 | coins: self.coins, 269 | memo: self.memo, 270 | block_time: self.block_time, 271 | block_height: self.block_height, 272 | }) 273 | } 274 | 275 | fn from_stored_legacy_transfer(transfer: StoredLegacyTransfer) -> Self { 276 | let action = StoredTxAction::transfer(transfer.from, transfer.sender, transfer.receiver); 277 | Self { 278 | id: transfer.id, 279 | action, 280 | coins: transfer.coins, 281 | memo: transfer.memo, 282 | block_time: transfer.block_time, 283 | block_height: transfer.block_height, 284 | } 285 | } 286 | } 287 | 288 | // Storage functions: 289 | 290 | fn increment_tx_count(store: &mut S) -> StdResult { 291 | let mut config = Config::from_storage(store); 292 | let id = config.tx_count() + 1; 293 | config.set_tx_count(id)?; 294 | Ok(id) 295 | } 296 | 297 | #[allow(clippy::too_many_arguments)] // We just need them 298 | pub fn store_transfer( 299 | store: &mut S, 300 | owner: &CanonicalAddr, 301 | sender: &CanonicalAddr, 302 | receiver: &CanonicalAddr, 303 | amount: Uint128, 304 | denom: String, 305 | memo: Option, 306 | block: &cosmwasm_std::BlockInfo, 307 | ) -> StdResult<()> { 308 | let id = increment_tx_count(store)?; 309 | let coins = Coin { denom, amount }; 310 | let transfer = StoredLegacyTransfer { 311 | id, 312 | from: owner.clone(), 313 | sender: sender.clone(), 314 | receiver: receiver.clone(), 315 | coins, 316 | memo, 317 | block_time: block.time, 318 | block_height: block.height, 319 | }; 320 | let tx = StoredRichTx::from_stored_legacy_transfer(transfer.clone()); 321 | 322 | // Write to the owners history if it's different from the other two addresses 323 | if owner != sender && owner != receiver { 324 | cosmwasm_std::debug_print("saving transaction history for owner"); 325 | append_tx(store, &tx, owner)?; 326 | append_transfer(store, &transfer, owner)?; 327 | } 328 | // Write to the sender's history if it's different from the receiver 329 | if sender != receiver { 330 | cosmwasm_std::debug_print("saving transaction history for sender"); 331 | append_tx(store, &tx, sender)?; 332 | append_transfer(store, &transfer, sender)?; 333 | } 334 | // Always write to the recipient's history 335 | cosmwasm_std::debug_print("saving transaction history for receiver"); 336 | append_tx(store, &tx, receiver)?; 337 | append_transfer(store, &transfer, receiver)?; 338 | 339 | Ok(()) 340 | } 341 | 342 | pub fn store_mint( 343 | store: &mut S, 344 | minter: &CanonicalAddr, 345 | recipient: &CanonicalAddr, 346 | amount: Uint128, 347 | denom: String, 348 | memo: Option, 349 | block: &cosmwasm_std::BlockInfo, 350 | ) -> StdResult<()> { 351 | let id = increment_tx_count(store)?; 352 | let coins = Coin { denom, amount }; 353 | let action = StoredTxAction::mint(minter.clone(), recipient.clone()); 354 | let tx = StoredRichTx::new(id, action, coins, memo, block); 355 | 356 | if minter != recipient { 357 | append_tx(store, &tx, recipient)?; 358 | } 359 | append_tx(store, &tx, minter)?; 360 | 361 | Ok(()) 362 | } 363 | 364 | pub fn store_burn( 365 | store: &mut S, 366 | owner: &CanonicalAddr, 367 | burner: &CanonicalAddr, 368 | amount: Uint128, 369 | denom: String, 370 | memo: Option, 371 | block: &cosmwasm_std::BlockInfo, 372 | ) -> StdResult<()> { 373 | let id = increment_tx_count(store)?; 374 | let coins = Coin { denom, amount }; 375 | let action = StoredTxAction::burn(owner.clone(), burner.clone()); 376 | let tx = StoredRichTx::new(id, action, coins, memo, block); 377 | 378 | if burner != owner { 379 | append_tx(store, &tx, owner)?; 380 | } 381 | append_tx(store, &tx, burner)?; 382 | 383 | Ok(()) 384 | } 385 | 386 | pub fn store_deposit( 387 | store: &mut S, 388 | recipient: &CanonicalAddr, 389 | amount: Uint128, 390 | denom: String, 391 | block: &cosmwasm_std::BlockInfo, 392 | ) -> StdResult<()> { 393 | let id = increment_tx_count(store)?; 394 | let coins = Coin { denom, amount }; 395 | let action = StoredTxAction::deposit(); 396 | let tx = StoredRichTx::new(id, action, coins, None, block); 397 | 398 | append_tx(store, &tx, recipient)?; 399 | 400 | Ok(()) 401 | } 402 | 403 | pub fn store_redeem( 404 | store: &mut S, 405 | redeemer: &CanonicalAddr, 406 | amount: Uint128, 407 | denom: String, 408 | block: &cosmwasm_std::BlockInfo, 409 | ) -> StdResult<()> { 410 | let id = increment_tx_count(store)?; 411 | let coins = Coin { denom, amount }; 412 | let action = StoredTxAction::redeem(); 413 | let tx = StoredRichTx::new(id, action, coins, None, block); 414 | 415 | append_tx(store, &tx, redeemer)?; 416 | 417 | Ok(()) 418 | } 419 | 420 | fn append_tx( 421 | store: &mut S, 422 | tx: &StoredRichTx, 423 | for_address: &CanonicalAddr, 424 | ) -> StdResult<()> { 425 | let mut store = PrefixedStorage::multilevel(&[PREFIX_TXS, for_address.as_slice()], store); 426 | let mut store = AppendStoreMut::attach_or_create(&mut store)?; 427 | store.push(tx) 428 | } 429 | 430 | fn append_transfer( 431 | store: &mut S, 432 | tx: &StoredLegacyTransfer, 433 | for_address: &CanonicalAddr, 434 | ) -> StdResult<()> { 435 | let mut store = PrefixedStorage::multilevel(&[PREFIX_TRANSFERS, for_address.as_slice()], store); 436 | let mut store = AppendStoreMut::attach_or_create(&mut store)?; 437 | store.push(tx) 438 | } 439 | 440 | pub fn get_txs( 441 | api: &A, 442 | storage: &S, 443 | for_address: &CanonicalAddr, 444 | page: u32, 445 | page_size: u32, 446 | ) -> StdResult<(Vec, u64)> { 447 | let store = ReadonlyPrefixedStorage::multilevel(&[PREFIX_TXS, for_address.as_slice()], storage); 448 | 449 | // Try to access the storage of txs for the account. 450 | // If it doesn't exist yet, return an empty list of transfers. 451 | let store = AppendStore::::attach(&store); 452 | let store = if let Some(result) = store { 453 | result? 454 | } else { 455 | return Ok((vec![], 0)); 456 | }; 457 | 458 | // Take `page_size` txs starting from the latest tx, potentially skipping `page * page_size` 459 | // txs from the start. 460 | let tx_iter = store 461 | .iter() 462 | .rev() 463 | .skip((page * page_size) as _) 464 | .take(page_size as _); 465 | 466 | // The `and_then` here flattens the `StdResult>` to an `StdResult` 467 | let txs: StdResult> = tx_iter 468 | .map(|tx| tx.map(|tx| tx.into_humanized(api)).and_then(|x| x)) 469 | .collect(); 470 | txs.map(|txs| (txs, store.len() as u64)) 471 | } 472 | 473 | pub fn get_transfers( 474 | api: &A, 475 | storage: &S, 476 | for_address: &CanonicalAddr, 477 | page: u32, 478 | page_size: u32, 479 | ) -> StdResult<(Vec, u64)> { 480 | let store = 481 | ReadonlyPrefixedStorage::multilevel(&[PREFIX_TRANSFERS, for_address.as_slice()], storage); 482 | 483 | // Try to access the storage of transfers for the account. 484 | // If it doesn't exist yet, return an empty list of transfers. 485 | let store = AppendStore::::attach(&store); 486 | let store = if let Some(result) = store { 487 | result? 488 | } else { 489 | return Ok((vec![], 0)); 490 | }; 491 | 492 | // Take `page_size` txs starting from the latest tx, potentially skipping `page * page_size` 493 | // txs from the start. 494 | let transfer_iter = store 495 | .iter() 496 | .rev() 497 | .skip((page * page_size) as _) 498 | .take(page_size as _); 499 | 500 | // The `and_then` here flattens the `StdResult>` to an `StdResult` 501 | let transfers: StdResult> = transfer_iter 502 | .map(|tx| tx.map(|tx| tx.into_humanized(api)).and_then(|x| x)) 503 | .collect(); 504 | transfers.map(|txs| (txs, store.len() as u64)) 505 | } 506 | -------------------------------------------------------------------------------- /contracts/lp_token/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 | -------------------------------------------------------------------------------- /contracts/lp_token/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 | 8 | use crate::rand::{sha_256, Prng}; 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 | -------------------------------------------------------------------------------- /contracts/lp_token/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 | -------------------------------------------------------------------------------- /contracts/lp_token/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 | -------------------------------------------------------------------------------- /contracts/lp_token/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 | -------------------------------------------------------------------------------- /contracts/lp_token/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 | -------------------------------------------------------------------------------- /contracts/lp_token/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 | -------------------------------------------------------------------------------- /contracts/lp_token/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 | -------------------------------------------------------------------------------- /contracts/lp_token/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 | memo: _, 41 | } => try_receive(deps, env, sender, from, amount, msg), 42 | HandleMsg::Redeem { 43 | addr, 44 | hash, 45 | to, 46 | amount, 47 | } => try_redeem(deps, env, addr, hash, to, amount), 48 | HandleMsg::Fail {} => try_fail(), 49 | } 50 | } 51 | 52 | pub fn try_increment( 53 | deps: &mut Extern, 54 | _env: Env, 55 | ) -> StdResult { 56 | let mut count = 0; 57 | config(&mut deps.storage).update(|mut state| { 58 | state.count += 1; 59 | count = state.count; 60 | Ok(state) 61 | })?; 62 | 63 | let mut context = Context::new(); 64 | context.add_log("count", count.to_string()); 65 | 66 | Ok(context.into()) 67 | } 68 | 69 | pub fn try_reset( 70 | deps: &mut Extern, 71 | env: Env, 72 | count: i32, 73 | ) -> StdResult { 74 | let sender_address_raw = deps.api.canonical_address(&env.message.sender)?; 75 | config(&mut deps.storage).update(|mut state| { 76 | if sender_address_raw != state.owner { 77 | return Err(StdError::Unauthorized { backtrace: None }); 78 | } 79 | state.count = count; 80 | Ok(state) 81 | })?; 82 | Ok(HandleResponse::default()) 83 | } 84 | 85 | pub fn try_register( 86 | deps: &mut Extern, 87 | env: Env, 88 | reg_addr: HumanAddr, 89 | reg_hash: String, 90 | ) -> StdResult { 91 | let mut conf = config(&mut deps.storage); 92 | let mut state = conf.load()?; 93 | if !state.known_snip_20.contains(®_addr) { 94 | state.known_snip_20.push(reg_addr.clone()); 95 | } 96 | conf.save(&state)?; 97 | 98 | let msg = to_binary(&Snip20Msg::register_receive(env.contract_code_hash))?; 99 | let message = CosmosMsg::Wasm(WasmMsg::Execute { 100 | contract_addr: reg_addr, 101 | callback_code_hash: reg_hash, 102 | msg, 103 | send: vec![], 104 | }); 105 | 106 | Ok(HandleResponse { 107 | messages: vec![message], 108 | log: vec![], 109 | data: None, 110 | }) 111 | } 112 | 113 | pub fn try_receive( 114 | deps: &mut Extern, 115 | env: Env, 116 | _sender: HumanAddr, 117 | _from: HumanAddr, 118 | _amount: Uint128, 119 | msg: Binary, 120 | ) -> StdResult { 121 | let msg: HandleMsg = from_binary(&msg)?; 122 | 123 | if matches!(msg, HandleMsg::Receive { .. }) { 124 | return Err(StdError::generic_err( 125 | "Recursive call to receive() is not allowed", 126 | )); 127 | } 128 | 129 | let state = config_read(&deps.storage).load()?; 130 | if !state.known_snip_20.contains(&env.message.sender) { 131 | return Err(StdError::generic_err(format!( 132 | "{} is not a known SNIP-20 coin that this contract registered to", 133 | env.message.sender 134 | ))); 135 | } 136 | 137 | /* use sender & amount */ 138 | handle(deps, env, msg) 139 | } 140 | 141 | fn try_redeem( 142 | deps: &mut Extern, 143 | env: Env, 144 | addr: HumanAddr, 145 | hash: String, 146 | to: HumanAddr, 147 | amount: Uint128, 148 | ) -> StdResult { 149 | let state = config_read(&deps.storage).load()?; 150 | if !state.known_snip_20.contains(&addr) { 151 | return Err(StdError::generic_err(format!( 152 | "{} is not a known SNIP-20 coin that this contract registered to", 153 | addr 154 | ))); 155 | } 156 | 157 | let msg = to_binary(&Snip20Msg::redeem(amount))?; 158 | let secret_redeem = CosmosMsg::Wasm(WasmMsg::Execute { 159 | contract_addr: addr, 160 | callback_code_hash: hash, 161 | msg, 162 | send: vec![], 163 | }); 164 | let redeem = CosmosMsg::Bank(BankMsg::Send { 165 | amount: vec![Coin::new(amount.u128(), "uscrt")], 166 | from_address: env.contract.address, 167 | to_address: to, 168 | }); 169 | 170 | Ok(HandleResponse { 171 | messages: vec![secret_redeem, redeem], 172 | log: vec![], 173 | data: None, 174 | }) 175 | } 176 | 177 | fn try_fail() -> StdResult { 178 | Err(StdError::generic_err("intentional failure")) 179 | } 180 | 181 | pub fn query( 182 | deps: &Extern, 183 | msg: QueryMsg, 184 | ) -> StdResult { 185 | match msg { 186 | QueryMsg::GetCount {} => to_binary(&query_count(deps)?), 187 | } 188 | } 189 | 190 | fn query_count(deps: &Extern) -> StdResult { 191 | let state = config_read(&deps.storage).load()?; 192 | Ok(CountResponse { count: state.count }) 193 | } 194 | -------------------------------------------------------------------------------- /contracts/lp_token/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 | -------------------------------------------------------------------------------- /contracts/lp_token/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 | memo: Option, 26 | msg: Binary, 27 | }, 28 | Redeem { 29 | addr: HumanAddr, 30 | hash: String, 31 | to: HumanAddr, 32 | amount: Uint128, 33 | }, 34 | Fail {}, 35 | } 36 | 37 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] 38 | #[serde(rename_all = "snake_case")] 39 | pub enum QueryMsg { 40 | // GetCount returns the current count as a json-encoded number 41 | GetCount {}, 42 | } 43 | 44 | // We define a custom struct for each query response 45 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] 46 | pub struct CountResponse { 47 | pub count: i32, 48 | } 49 | 50 | // Messages sent to SNIP-20 contracts 51 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] 52 | #[serde(rename_all = "snake_case")] 53 | pub enum Snip20Msg { 54 | RegisterReceive { 55 | code_hash: String, 56 | padding: Option, 57 | }, 58 | Redeem { 59 | amount: Uint128, 60 | padding: Option, 61 | }, 62 | } 63 | 64 | impl Snip20Msg { 65 | pub fn register_receive(code_hash: String) -> Self { 66 | Snip20Msg::RegisterReceive { 67 | code_hash, 68 | padding: None, // TODO add padding calculation 69 | } 70 | } 71 | 72 | pub fn redeem(amount: Uint128) -> Self { 73 | Snip20Msg::Redeem { 74 | amount, 75 | padding: None, // TODO add padding calculation 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /contracts/lp_token/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 | -------------------------------------------------------------------------------- /contracts/lp_token/tests/integration.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | #[ignore] 3 | fn empty_test() {} 4 | -------------------------------------------------------------------------------- /contracts/stable_pool/.cargo/config: -------------------------------------------------------------------------------- 1 | [alias] 2 | wasm = "build --release --target wasm32-unknown-unknown" 3 | wasm-debug = "build --target wasm32-unknown-unknown" 4 | unit-test = "test --lib --features backtraces" 5 | integration-test = "test --test integration" 6 | schema = "run --example schema" 7 | -------------------------------------------------------------------------------- /contracts/stable_pool/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "secretswap-stablepools" 3 | version = "0.0.1" 4 | authors = ["Enigma MPC"] 5 | edition = "2018" 6 | description = "A SecretSwap stable pools contract" 7 | license = "MIT" 8 | exclude = [ 9 | # 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. 10 | "contract.wasm", 11 | "hash.txt", 12 | ] 13 | 14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 15 | [lib] 16 | crate-type = ["cdylib", "rlib"] 17 | 18 | [features] 19 | default = [] 20 | # for quicker tests, cargo test --lib 21 | # for more explicit tests, cargo test --features=backtraces 22 | backtraces = ["cosmwasm-std/backtraces"] 23 | debug-print = ["cosmwasm-std/debug-print"] 24 | 25 | [dependencies] 26 | secret-toolkit = { git = "https://github.com/enigmampc/secret-toolkit", rev = "v0.1.1-debug-print" } 27 | cosmwasm-storage = { git = "https://github.com/enigmampc/SecretNetwork", tag = "v1.0.4-debug-print" } 28 | cosmwasm-std = { git = "https://github.com/enigmampc/SecretNetwork", tag = "v1.0.4-debug-print" } 29 | lp-token = { path = "../lp_token" } 30 | schemars = "0.7" 31 | serde = { version = "1.0.103", default-features = false, features = ["derive"] } 32 | base64 = "0.13.0" 33 | primitive-types = { version = "0.9.0", default-features = false } 34 | -------------------------------------------------------------------------------- /contracts/stable_pool/README.md: -------------------------------------------------------------------------------- 1 | # TerraSwap Pair 2 | 3 | ## Handlers 4 | 5 | ### Initialize 6 | 7 | This is mainly used from terraswap factory contract to create new terraswap pair. It initialize all swap created parameters which can be updated later with owner key. 8 | 9 | It creates liquidity token contract as init response, and execute init hook to register created liquidity token contract to self. 10 | 11 | ```rust 12 | { 13 | /// Asset infos 14 | pub asset_infos: [AssetInfo; 2], 15 | /// Token code ID for liqudity token creation 16 | pub token_code_id: u64, 17 | /// Hook for post initalization 18 | pub init_hook: Option, 19 | } 20 | ``` 21 | 22 | ### Liquidity Provider 23 | 24 | The contract has two types of pool, the one is collateral and the other is asset pool. A user can provide liquidity to each pool by sending `provide_liquidity` msgs and also can withdraw with `withdraw_liquidity` msgs. 25 | 26 | Whenever liquidity is deposited into a pool, special tokens known as liquidity tokens are minted to the provider’s address, in proportion to how much liquidity they contributed to the pool. These tokens are a representation of a liquidity provider’s contribution to a pool. Whenever a trade occurs, the `lp_commission%` of fee is distributed pro-rata to all LPs in the pool at the moment of the trade. To receive the underlying liquidity back, plus commission fees that were accrued while their liquidity was locked, LPs must burn their liquidity tokens. 27 | 28 | When providing liquidity from a smart contract, the most important thing to keep in mind is that tokens deposited into a pool at any rate other than the current oracle price ratio are vulnerable to being arbitraged. As an example, if the ratio of x:y in a pair is 10:2 (i.e. the price is 5), and someone naively adds liquidity at 5:2 (a price of 2.5), the contract will simply accept all tokens (changing the price to 3.75 and opening up the market to arbitrage), but only issue pool tokens entitling the sender to the amount of assets sent at the proper ratio, in this case 5:1. To avoid donating to arbitrageurs, it is imperative to add liquidity at the current price. Luckily, it’s easy to ensure that this condition is met! 29 | 30 | > Note before executing the `provide_liqudity` operation, a user must allow the contract to use the liquidity amount of asset in the token contract. 31 | 32 | #### Slipage Tolerance 33 | 34 | If a user specify the slipage tolerance at provide liquidity msg, the contract restricts the operation when the exchange rate is dropped more than the tolerance. 35 | 36 | So, at a 1% tolerance level, if a user sends a transaction with 200 UST and 1 ASSET, amountUSTMin should be set to e.g. 198 UST, and amountASSETMin should be set to .99 ASSET. This means that, at worst, liquidity will be added at a rate between 198 ASSET/1 UST and 202.02 UST/1 ASSET (200 UST/.99 ASSET). 37 | 38 | #### Request Format 39 | 40 | - Provide Liquidity 41 | 42 | 1. Without Slippage Tolerance 43 | 44 | ```json 45 | { 46 | "provide_liquidity": { 47 | "assets": [ 48 | { 49 | "info": { 50 | "token": { 51 | "contract_addr": "terra~~" 52 | } 53 | }, 54 | "amount": "1000000" 55 | }, 56 | { 57 | "info": { 58 | "native_token": { 59 | "denom": "uusd" 60 | } 61 | }, 62 | "amount": "1000000" 63 | } 64 | ] 65 | } 66 | } 67 | ``` 68 | 69 | 2. With Slippage Tolerance 70 | 71 | ```json 72 | { 73 | "provide_liquidity": { 74 | "assets": [ 75 | { 76 | "info": { 77 | "token": { 78 | "contract_addr": "terra~~" 79 | } 80 | }, 81 | "amount": "1000000" 82 | }, 83 | { 84 | "info": { 85 | "native_token": { 86 | "denom": "uusd" 87 | } 88 | }, 89 | "amount": "1000000" 90 | } 91 | ] 92 | }, 93 | "slippage_tolerance": "0.01" 94 | } 95 | ``` 96 | 97 | - Withdraw Liquidity (must be sent to liquidity token contract) 98 | ```json 99 | { 100 | "withdraw_liquidity": {} 101 | } 102 | ``` 103 | 104 | ### Swap 105 | 106 | Any user can swap an asset by sending `swap` or invoking `send` msg to token contract with `swap` hook message. 107 | 108 | - Native Token => Token 109 | 110 | ```json 111 | { 112 | "swap": { 113 | "offer_asset": { 114 | "info": { 115 | "native_token": { 116 | "denom": String 117 | } 118 | }, 119 | "amount": Uint128 120 | }, 121 | "belief_price": Option, 122 | "max_spread": Option, 123 | "to": Option 124 | } 125 | } 126 | ``` 127 | 128 | - Token => Native Token 129 | 130 | **Must be sent to token contract** 131 | 132 | ```json 133 | { 134 | "send": { 135 | "contract": HumanAddr, 136 | "amount": Uint128, 137 | "msg": Binary({ 138 | "swap": { 139 | "belief_price": Option, 140 | "max_spread": Option, 141 | "to": Option 142 | } 143 | }) 144 | } 145 | } 146 | ``` 147 | 148 | #### Swap Spread 149 | 150 | The spread is determined with following uniswap mechanism: 151 | 152 | ```rust 153 | // -max_minus_spread < spread < max_spread 154 | // minus_spread means discount rate. 155 | // Ensure `asset pool * collateral pool = constant product` 156 | let cp = Uint128(offer_pool.u128() * ask_pool.u128()); 157 | let return_amount = offer_amount * exchange_rate; 158 | let return_amount = (ask_pool - cp.multiply_ratio(1u128, offer_pool + offer_amount))?; 159 | 160 | 161 | // calculate spread & commission 162 | let spread_amount: Uint128 = 163 | (offer_amount * Decimal::from_ratio(ask_pool, offer_pool) - return_amount)?; 164 | let lp_commission: Uint128 = return_amount * config.lp_commission; 165 | let owner_commission: Uint128 = return_amount * config.owner_commission; 166 | 167 | // commission will be absorbed to pool 168 | let return_amount: Uint128 = 169 | (return_amount - (lp_commission + owner_commission)).unwrap(); 170 | ``` 171 | 172 | #### Commission 173 | 174 | The `lp_commission` remains in the swap pool, which is fixed to `0.3%`, causing a permanent increase in the constant product K. The value of this permanently increased pool goes to all LPs. 175 | 176 | -------------------------------------------------------------------------------- /contracts/stable_pool/src/contract.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ops::{Add, Mul, Sub}, 3 | u128, 4 | }; 5 | 6 | use cosmwasm_std::{ 7 | debug_print, from_binary, log, to_binary, Api, Binary, CosmosMsg, Decimal, Env, Extern, 8 | HandleResponse, HandleResult, HumanAddr, InitResponse, Querier, StdError, StdResult, Storage, 9 | Uint128, WasmMsg, 10 | }; 11 | use primitive_types::U256; 12 | 13 | use lp_token as snip20; 14 | use secret_toolkit::snip20 as snip20_utils; 15 | 16 | use crate::{ 17 | math::{decimal_multiplication, decimal_subtraction, reverse_decimal}, 18 | msg::{Config, HandleMsg, InitMsg, QueryMsg, Snip20ReceiveMsg, Token, TokenAmount, TokenInfo}, 19 | querier::query_token_decimals, 20 | state::{read_all_assets, read_config, store_all_assets, store_config}, 21 | u256_math::*, 22 | }; 23 | 24 | use crate::querier::{query_token_balance, query_token_total_supply}; 25 | 26 | pub fn init( 27 | deps: &mut Extern, 28 | env: Env, 29 | msg: InitMsg, 30 | ) -> StdResult { 31 | if msg.swap_fee_denom == Uint128::zero() { 32 | return Err(StdError::generic_err("swap_fee_denom cannot be zero")); 33 | } 34 | 35 | let mut messages: Vec = vec![]; 36 | let mut assets: Vec = vec![]; 37 | 38 | // Setup pool's tokens 39 | for token in msg.assets { 40 | // Set initial viewing key for token 41 | messages.push(snip20_utils::set_viewing_key_msg( 42 | msg.initial_tokens_viewing_key.clone(), 43 | None, 44 | 256, 45 | token.code_hash.clone(), 46 | token.address.clone(), 47 | )?); 48 | 49 | // Register for receive message from token 50 | messages.push(snip20_utils::register_receive_msg( 51 | env.contract_code_hash.clone(), 52 | None, 53 | 256, 54 | token.code_hash.clone(), 55 | token.address.clone(), 56 | )?); 57 | 58 | let decimals = query_token_decimals(deps, &token.address, &token.code_hash)?; 59 | if decimals > 18 { 60 | return Err(StdError::generic_err(format!( 61 | "Decimals must not exceed 18 for token: {:?}", 62 | token 63 | ))); 64 | } 65 | 66 | assets.push(TokenInfo { 67 | address: token.address.clone(), 68 | code_hash: token.code_hash.clone(), 69 | viewing_key: msg.initial_tokens_viewing_key.clone(), 70 | decimals, 71 | }) 72 | } 73 | 74 | store_all_assets(&mut deps.storage, &assets)?; 75 | 76 | // Create LP token 77 | messages.extend(vec![CosmosMsg::Wasm(WasmMsg::Instantiate { 78 | code_id: msg.lp_token_code_id, 79 | msg: to_binary(&snip20::msg::InitMsg { 80 | name: format!("StableSwap Liquidity Provider (LP) token for TODO"), 81 | admin: Some(env.contract.address), 82 | symbol: "STABLE-LP".to_string(), 83 | decimals: 18, 84 | initial_balances: None, 85 | prng_seed: msg.lp_token_prng_seed, 86 | config: Some(snip20::msg::InitConfig { 87 | public_total_supply: Some(true), 88 | enable_deposit: Some(false), 89 | enable_redeem: Some(false), 90 | enable_mint: Some(true), 91 | enable_burn: Some(true), 92 | }), 93 | after_init_hook: Some(snip20::msg::AfterInitHook { 94 | msg: to_binary(&HandleMsg::PostInitialize {})?, 95 | contract_addr: env.contract.address, 96 | code_hash: env.contract_code_hash, 97 | }), 98 | })?, 99 | send: vec![], 100 | label: msg.lp_token_label.clone(), 101 | callback_code_hash: msg.lp_token_code_hash.clone(), 102 | })]); 103 | 104 | store_config( 105 | &mut deps.storage, 106 | &Config { 107 | admin: msg.admin, 108 | swap_fee_nom: msg.swap_fee_nom, 109 | swap_fee_denom: msg.swap_fee_denom, 110 | is_halted: msg.is_halted, 111 | round_down_pool_answer_to_nearest: msg.round_down_pool_answer_to_nearest, 112 | lp_token_address: HumanAddr::default(), 113 | lp_token_code_hash: msg.lp_token_code_hash, 114 | }, 115 | )?; 116 | 117 | Ok(InitResponse { 118 | messages, 119 | log: vec![log("status", "success")], // See https://github.com/CosmWasm/wasmd/pull/386 120 | }) 121 | } 122 | 123 | pub fn handle( 124 | deps: &mut Extern, 125 | env: Env, 126 | msg: HandleMsg, 127 | ) -> HandleResult { 128 | match msg { 129 | HandleMsg::Receive { amount, msg, from } => receive_snip20(deps, env, from, amount, msg), 130 | HandleMsg::PostInitialize {} => try_post_initialize(deps, env), 131 | HandleMsg::ProvideLiquidity { 132 | assets, 133 | cancel_if_no_bonus, 134 | } => try_provide_liquidity(deps, env, assets, cancel_if_no_bonus), 135 | HandleMsg::UpdateViewingKeys {} => todo!(), 136 | } 137 | } 138 | 139 | pub fn receive_snip20( 140 | deps: &mut Extern, 141 | env: Env, 142 | sender: HumanAddr, 143 | amount: Uint128, 144 | msg: Binary, 145 | ) -> HandleResult { 146 | let receive_token_address = env.message.sender.clone(); 147 | 148 | match from_binary(&msg)? { 149 | Snip20ReceiveMsg::Swap { 150 | to_token, 151 | recipient, 152 | } => { 153 | let supported_tokens = read_all_assets(&deps.storage)?; 154 | 155 | if !supported_tokens 156 | .iter() 157 | .any(|t| t.address == receive_token_address) 158 | { 159 | // only asset contract can execute this message 160 | return Err(StdError::generic_err(format!( 161 | "Unknown source asset {:?}", 162 | receive_token_address, 163 | ))); 164 | } 165 | if !supported_tokens.iter().any(|t| t.address == to_token) { 166 | // only asset contract can execute this message 167 | return Err(StdError::generic_err(format!( 168 | "Unknown destination asset {:?}", 169 | receive_token_address, 170 | ))); 171 | } 172 | 173 | try_swap( 174 | deps, 175 | env, 176 | amount, 177 | receive_token_address, 178 | to_token, 179 | recipient.unwrap_or(sender), 180 | ) 181 | } 182 | Snip20ReceiveMsg::WithdrawLiquidity {} => { 183 | let config = read_config(&deps.storage)?; 184 | if env.message.sender != config.lp_token_address { 185 | return Err(StdError::generic_err(format!( 186 | "Unknown liqudity token {:?}", 187 | env.message.sender, 188 | ))); 189 | } 190 | 191 | try_withdraw_liquidity(deps, env, sender, amount) 192 | } 193 | } 194 | } 195 | 196 | // Must token contract execute it 197 | pub fn try_post_initialize( 198 | deps: &mut Extern, 199 | env: Env, 200 | ) -> HandleResult { 201 | let mut config: Config = read_config(&deps.storage)?; 202 | 203 | // permission check 204 | if config.lp_token_address != HumanAddr::default() { 205 | return Err(StdError::unauthorized()); 206 | } 207 | 208 | config.lp_token_address = env.message.sender.clone(); 209 | 210 | store_config(&mut deps.storage, &config)?; 211 | 212 | Ok(HandleResponse { 213 | messages: vec![snip20_utils::register_receive_msg( 214 | env.contract_code_hash, 215 | None, 216 | 256, 217 | config.lp_token_code_hash, 218 | config.lp_token_address, 219 | )?], 220 | log: vec![log("liquidity_token_address", config.lp_token_address)], 221 | data: None, 222 | }) 223 | } 224 | 225 | /// CONTRACT - should approve contract to use the amount of token 226 | pub fn try_provide_liquidity( 227 | deps: &mut Extern, 228 | env: Env, 229 | assets_deposits: Vec, 230 | cancel_if_no_bonus: Option, 231 | ) -> HandleResult { 232 | let supported_tokens = read_all_assets(&deps.storage)?; 233 | 234 | let mut messages = vec![]; 235 | let mut logs = vec![log("action", "provide_liquidity")]; 236 | for deposited_token in assets_deposits.iter() { 237 | if !supported_tokens 238 | .iter() 239 | .any(|supported_token| supported_token.address == deposited_token.address) 240 | { 241 | return Err(StdError::generic_err(format!( 242 | "Token not supported: {:?} ", 243 | deposited_token 244 | ))); 245 | } 246 | 247 | // Execute TransferFrom msg to receive funds 248 | messages.push(snip20_utils::transfer_from_msg( 249 | env.message.sender.clone(), 250 | env.contract.address.clone(), 251 | deposited_token.amount, 252 | None, 253 | 256, 254 | deposited_token.code_hash.clone(), 255 | deposited_token.address.clone(), 256 | )?); 257 | 258 | logs.push(log("token", deposited_token.address)); 259 | } 260 | 261 | if Some(true) == cancel_if_no_bonus { 262 | // TODO 263 | } 264 | 265 | let config = read_config(&deps.storage)?; 266 | 267 | // For now share = sum of all deposits 268 | let mut share = Uint128::zero(); 269 | for token_deposit in assets_deposits.iter() { 270 | let decimals: u8 = supported_tokens 271 | .iter() 272 | .find(|a| a.address == token_deposit.address) 273 | .unwrap() // can unwrap because if we're here then it's already tested 274 | .decimals; 275 | 276 | let factor: u128 = 10u128.pow(18 - decimals as u32); // not checked_pow because 10^18 < 2^128 277 | 278 | let normalized_deposit: u128 = 279 | token_deposit 280 | .amount 281 | .u128() 282 | .checked_mul(factor) 283 | .ok_or_else(|| { 284 | StdError::generic_err(format!( 285 | "Cannot normalize token deposit for 18 decimals: {:?}", 286 | token_deposit 287 | )) 288 | })?; 289 | 290 | share += normalized_deposit.into(); 291 | } 292 | 293 | messages.push(snip20_utils::mint_msg( 294 | env.message.sender, 295 | share, 296 | None, 297 | 256, 298 | config.lp_token_code_hash, 299 | config.lp_token_address, 300 | )?); 301 | logs.push(log("share", share.to_string())); 302 | 303 | Ok(HandleResponse { 304 | messages, 305 | log: logs, 306 | data: None, 307 | }) 308 | } 309 | 310 | pub fn try_withdraw_liquidity( 311 | deps: &mut Extern, 312 | env: Env, 313 | sender: HumanAddr, 314 | amount: Uint128, 315 | ) -> HandleResult { 316 | let pair_info: PairInfoRaw = read_pair_info(&deps.storage)?; 317 | let liquidity_addr: HumanAddr = deps.api.human_address(&pair_info.liquidity_token)?; 318 | 319 | let pools: [Token; 2] = pair_info.query_pools(&deps, &env.contract.address)?; 320 | let total_share: Uint128 = 321 | query_token_total_supply(&deps, &liquidity_addr, &pair_info.token_code_hash)?; 322 | 323 | let refund_assets: Vec = pools 324 | .iter() 325 | .map(|a| { 326 | // withdrawn_asset_amount = a.amount * amount / total_share 327 | 328 | let current_pool_amount = Some(U256::from(a.amount.u128())); 329 | let withdrawn_share_amount = Some(U256::from(amount.u128())); 330 | let total_share = Some(U256::from(total_share.u128())); 331 | 332 | let withdrawn_asset_amount = div( 333 | mul(current_pool_amount, withdrawn_share_amount), 334 | total_share, 335 | ) 336 | .ok_or_else(|| { 337 | StdError::generic_err(format!( 338 | "Cannot calculate current_pool_amount {} * withdrawn_share_amount {} / total_share {}", 339 | a.amount, 340 | amount, 341 | total_share.unwrap() 342 | )) 343 | })?; 344 | 345 | Ok(Token { 346 | info: a.info.clone(), 347 | amount: Uint128(withdrawn_asset_amount.low_u128()), 348 | }) 349 | }) 350 | .collect::>>()?; 351 | 352 | // update pool info 353 | Ok(HandleResponse { 354 | messages: vec![ 355 | // refund asset tokens 356 | refund_assets[0].clone().into_msg( 357 | deps, 358 | env.contract.address.clone(), 359 | sender.clone(), 360 | )?, 361 | refund_assets[1].clone().into_msg( 362 | deps, 363 | env.contract.address.clone(), 364 | sender.clone(), 365 | )?, 366 | // burn liquidity token 367 | snip20_utils::burn_msg( 368 | amount, 369 | None, 370 | 256, 371 | pair_info.token_code_hash, 372 | deps.api.human_address(&pair_info.liquidity_token)?, 373 | )?, 374 | ], 375 | log: vec![ 376 | log("action", "withdraw_liquidity"), 377 | log("withdrawn_share", &amount.to_string()), 378 | log( 379 | "refund_assets", 380 | format!("{}, {}", refund_assets[0].clone(), refund_assets[1].clone()), 381 | ), 382 | ], 383 | data: None, 384 | }) 385 | } 386 | 387 | // CONTRACT - a user must do token approval 388 | pub fn try_swap( 389 | deps: &mut Extern, 390 | env: Env, 391 | src_amount: Uint128, 392 | src_token: HumanAddr, 393 | dst_token: HumanAddr, 394 | recipient: HumanAddr, 395 | ) -> HandleResult { 396 | let supported_tokens = read_all_assets(&deps.storage)?; 397 | 398 | let src_token = supported_tokens 399 | .iter() 400 | .find(|t| t.address == src_token) 401 | .unwrap() /* this was checked before going into try_swap */; 402 | let dst_token = supported_tokens 403 | .iter() 404 | .find(|t| t.address == dst_token) 405 | .unwrap() /* this was checked before going into try_swap */; 406 | 407 | // Normalize amount (due to decimals differences) 408 | let mut dst_amount = src_amount.u128(); 409 | if src_token.decimals > dst_token.decimals { 410 | let factor: u128 = 10u128.pow((src_token.decimals - dst_token.decimals) as u32); 411 | dst_amount = dst_amount / factor; 412 | } else if dst_token.decimals > src_token.decimals { 413 | let factor: u128 = 10u128.pow((dst_token.decimals - src_token.decimals) as u32); 414 | dst_amount = dst_amount * factor; 415 | } 416 | 417 | // Take fee 418 | let config = read_config(&deps.storage)?; 419 | 420 | dst_amount = dst_amount * config.swap_fee_nom.u128() / config.swap_fee_denom.u128(); 421 | 422 | // TODO 423 | 424 | let mut messages = Vec::::new(); 425 | messages.push(return_asset.clone().into_msg( 426 | &deps, 427 | env.contract.address.clone(), 428 | to.clone().unwrap_or(sender.clone()), 429 | )?); 430 | 431 | Ok(HandleResponse { 432 | messages, 433 | log: vec![ 434 | log("action", "swap"), 435 | log("offer_asset", offer_asset.info.to_string()), 436 | log("ask_asset", ask_pool.info.to_string()), 437 | log("offer_amount", offer_amount.to_string()), 438 | log("return_amount", return_amount.to_string()), 439 | log("spread_amount", spread_amount.to_string()), 440 | log("commission_amount", commission_amount.to_string()), 441 | ], 442 | data: None, 443 | }) 444 | } 445 | 446 | pub fn query( 447 | deps: &Extern, 448 | msg: QueryMsg, 449 | ) -> StdResult { 450 | todo!(); 451 | match msg { 452 | QueryMsg::GetTokens {} => to_binary(&query_pair_info(&deps)?), 453 | QueryMsg::GetPools {} => to_binary(&query_pool(&deps)?), 454 | QueryMsg::GetConfig {} => to_binary(todo!()), 455 | QueryMsg::GetMostNeededToken {} => to_binary(todo!()), 456 | } 457 | } 458 | 459 | pub fn query_pair_info( 460 | deps: &Extern, 461 | ) -> StdResult { 462 | let pair_info: PairInfoRaw = read_pair_info(&deps.storage)?; 463 | pair_info.to_normal(&deps) 464 | } 465 | 466 | pub fn query_pool( 467 | deps: &Extern, 468 | ) -> StdResult { 469 | let pair_info: PairInfoRaw = read_pair_info(&deps.storage)?; 470 | let contract_addr = deps.api.human_address(&pair_info.contract_addr)?; 471 | let assets: [Token; 2] = pair_info.query_pools(&deps, &contract_addr)?; 472 | let total_share: Uint128 = query_token_total_supply( 473 | &deps, 474 | &deps.api.human_address(&pair_info.liquidity_token)?, 475 | &pair_info.token_code_hash, 476 | )?; 477 | 478 | let resp = PoolResponse { 479 | assets, 480 | total_share, 481 | }; 482 | 483 | Ok(resp) 484 | } 485 | -------------------------------------------------------------------------------- /contracts/stable_pool/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod contract; 2 | pub mod math; 3 | pub mod msg; 4 | pub mod querier; 5 | pub mod state; 6 | pub mod u256_math; 7 | 8 | #[cfg(all(target_arch = "wasm32", not(feature = "library")))] 9 | cosmwasm_std::create_entry_points!(contract); 10 | -------------------------------------------------------------------------------- /contracts/stable_pool/src/math.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::{Decimal, StdResult, Uint128}; 2 | 3 | ///////////////////////////////////////////////////////////// 4 | const DECIMAL_FRACTIONAL: Uint128 = Uint128(1_000_000_000u128); 5 | 6 | pub fn reverse_decimal(decimal: Decimal) -> Decimal { 7 | if decimal.is_zero() { 8 | return Decimal::zero(); 9 | } 10 | 11 | Decimal::from_ratio(DECIMAL_FRACTIONAL, decimal * DECIMAL_FRACTIONAL) 12 | } 13 | 14 | pub fn decimal_subtraction(a: Decimal, b: Decimal) -> StdResult { 15 | Ok(Decimal::from_ratio( 16 | (a * DECIMAL_FRACTIONAL - b * DECIMAL_FRACTIONAL)?, 17 | DECIMAL_FRACTIONAL, 18 | )) 19 | } 20 | 21 | pub fn decimal_multiplication(a: Decimal, b: Decimal) -> Decimal { 22 | Decimal::from_ratio(a * DECIMAL_FRACTIONAL * b, DECIMAL_FRACTIONAL) 23 | } 24 | -------------------------------------------------------------------------------- /contracts/stable_pool/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 | #[serde(rename_all = "snake_case")] 7 | pub struct Token { 8 | pub address: HumanAddr, 9 | pub code_hash: String, 10 | } 11 | 12 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] 13 | #[serde(rename_all = "snake_case")] 14 | pub struct TokenAmount { 15 | pub address: HumanAddr, 16 | pub code_hash: String, 17 | pub amount: Uint128, 18 | } 19 | 20 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] 21 | #[serde(rename_all = "snake_case")] 22 | pub struct TokenInfo { 23 | pub address: HumanAddr, 24 | pub code_hash: String, 25 | pub viewing_key: String, 26 | pub decimals: u8, 27 | } 28 | 29 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] 30 | #[serde(rename_all = "snake_case")] 31 | pub struct Config { 32 | pub admin: HumanAddr, 33 | pub swap_fee_nom: Uint128, 34 | pub swap_fee_denom: Uint128, 35 | pub is_halted: bool, 36 | pub lp_token_address: HumanAddr, 37 | pub lp_token_code_hash: String, 38 | } 39 | 40 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] 41 | pub struct InitMsg { 42 | pub assets: Vec, 43 | pub initial_tokens_viewing_key: String, 44 | 45 | pub lp_token_code_id: u64, 46 | pub lp_token_code_hash: String, 47 | pub lp_token_prng_seed: Binary, 48 | pub lp_token_label: String, 49 | 50 | pub admin: HumanAddr, 51 | pub swap_fee_nom: Uint128, 52 | pub swap_fee_denom: Uint128, 53 | pub is_halted: bool, 54 | } 55 | 56 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] 57 | #[serde(rename_all = "snake_case")] 58 | pub enum HandleMsg { 59 | Receive { 60 | from: HumanAddr, 61 | msg: Binary, 62 | amount: Uint128, 63 | }, 64 | ProvideLiquidity { 65 | assets: Vec, 66 | cancel_if_no_bonus: Option, 67 | }, 68 | PostInitialize {}, 69 | UpdateViewingKeys {}, 70 | } 71 | 72 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] 73 | #[serde(rename_all = "snake_case")] 74 | pub enum Snip20ReceiveMsg { 75 | Swap { 76 | to_token: HumanAddr, 77 | recipient: Option, 78 | }, 79 | WithdrawLiquidity {}, 80 | } 81 | 82 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] 83 | #[serde(rename_all = "snake_case")] 84 | pub enum QueryMsg { 85 | GetConfig {}, 86 | GetTokens {}, 87 | GetPools {}, 88 | GetMostNeededToken {}, 89 | } 90 | -------------------------------------------------------------------------------- /contracts/stable_pool/src/querier.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::{Api, Extern, HumanAddr, Querier, StdError, StdResult, Storage, Uint128}; 2 | 3 | use secret_toolkit::snip20 as snip20_utils; 4 | 5 | pub fn query_token_balance( 6 | deps: &Extern, 7 | token_address: &HumanAddr, 8 | token_code_hash: &String, 9 | account: &HumanAddr, 10 | viewing_key: &String, 11 | ) -> StdResult { 12 | let msg = snip20_utils::balance_query( 13 | &deps.querier, 14 | account.clone(), 15 | viewing_key.clone(), 16 | 256, 17 | token_code_hash.clone(), 18 | token_address.clone(), 19 | )?; 20 | 21 | Ok(msg.amount) 22 | } 23 | 24 | pub fn query_token_total_supply( 25 | deps: &Extern, 26 | token_address: &HumanAddr, 27 | token_code_hash: &String, 28 | ) -> StdResult { 29 | let token_info = snip20_utils::token_info_query( 30 | &deps.querier, 31 | 256, 32 | token_code_hash.clone(), 33 | token_address.clone(), 34 | )?; 35 | 36 | if token_info.total_supply.is_none() { 37 | return Err(StdError::generic_err(format!( 38 | "Tried to query a token {} with unavailable supply", 39 | token_address 40 | ))); 41 | } 42 | 43 | Ok(token_info.total_supply.unwrap()) 44 | } 45 | 46 | pub fn query_token_decimals( 47 | deps: &Extern, 48 | token_address: &HumanAddr, 49 | token_code_hash: &String, 50 | ) -> StdResult { 51 | let token_info = snip20_utils::token_info_query( 52 | &deps.querier, 53 | 256, 54 | token_code_hash.clone(), 55 | token_address.clone(), 56 | )?; 57 | 58 | Ok(token_info.decimals) 59 | } 60 | -------------------------------------------------------------------------------- /contracts/stable_pool/src/state.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::{StdResult, Storage}; 2 | use cosmwasm_storage::{ReadonlySingleton, Singleton}; 3 | 4 | use crate::msg::{TokenInfo, Config}; 5 | 6 | const ALL_ASSETS_KEY: &[u8] = b"all_assets"; 7 | 8 | pub fn store_all_assets(storage: &mut S, assets: &Vec) -> StdResult<()> { 9 | Singleton::new(storage, ALL_ASSETS_KEY).save(assets) 10 | } 11 | 12 | pub fn read_all_assets(storage: &S) -> StdResult> { 13 | ReadonlySingleton::new(storage, ALL_ASSETS_KEY).load() 14 | } 15 | 16 | const CONFIG_KEY: &[u8] = b"config"; 17 | 18 | pub fn store_config(storage: &mut S, config: &Config) -> StdResult<()> { 19 | Singleton::new(storage, CONFIG_KEY).save(config) 20 | } 21 | 22 | pub fn read_config(storage: &S) -> StdResult { 23 | ReadonlySingleton::new(storage, CONFIG_KEY).load() 24 | } 25 | -------------------------------------------------------------------------------- /contracts/stable_pool/src/u256_math.rs: -------------------------------------------------------------------------------- 1 | use primitive_types::U256; 2 | 3 | fn checked_sub(b: U256) -> impl Fn(U256) -> Option { 4 | move |a: U256| a.checked_sub(b) 5 | } 6 | 7 | pub fn sub(a: Option, b: Option) -> Option { 8 | match b { 9 | Some(b) => a.and_then(checked_sub(b)), 10 | None => None, 11 | } 12 | } 13 | 14 | fn checked_div(denom: U256) -> impl Fn(U256) -> Option { 15 | move |nom: U256| nom.checked_div(denom) 16 | } 17 | 18 | pub fn div(nom: Option, denom: Option) -> Option { 19 | match denom { 20 | Some(denom) => nom.and_then(checked_div(denom)), 21 | None => None, 22 | } 23 | } 24 | 25 | fn checked_add(b: U256) -> impl Fn(U256) -> Option { 26 | move |a: U256| a.checked_add(b) 27 | } 28 | 29 | pub fn add(a: Option, b: Option) -> Option { 30 | match b { 31 | Some(b) => a.and_then(checked_add(b)), 32 | None => None, 33 | } 34 | } 35 | 36 | fn checked_mul(b: U256) -> impl Fn(U256) -> Option { 37 | move |a: U256| a.checked_mul(b) 38 | } 39 | 40 | pub fn mul(a: Option, b: Option) -> Option { 41 | match b { 42 | Some(b) => a.and_then(checked_mul(b)), 43 | None => None, 44 | } 45 | } 46 | 47 | /// U256 sqrt ported from here: https://ethereum.stackexchange.com/a/87713/12112 48 | /// 49 | /// function sqrt(uint y) internal pure returns (uint z) { 50 | /// if (y > 3) { 51 | /// z = y; 52 | /// uint x = y / 2 + 1; 53 | /// while (x < z) { 54 | /// z = x; 55 | /// x = (y / x + x) / 2; 56 | /// } 57 | /// } else if (y != 0) { 58 | /// z = 1; 59 | /// } 60 | /// } 61 | /// 62 | /// Tested it here: https://github.com/enigmampc/u256-sqrt-test/blob/aa7693/src/main.rs 63 | pub fn u256_sqrt(y: U256) -> Option { 64 | let mut z = U256::from(0); 65 | if y.gt(&U256::from(3)) { 66 | z = y.clone(); 67 | let mut x = y.checked_div(U256::from(2))?.checked_add(U256::from(1))?; 68 | while x.lt(&z) { 69 | z = x.clone(); 70 | x = y 71 | .checked_div(x)? 72 | .checked_add(x)? 73 | .checked_div(U256::from(2))?; 74 | } 75 | } else if !y.is_zero() { 76 | z = U256::from(1); 77 | } 78 | 79 | return Some(z); 80 | } 81 | --------------------------------------------------------------------------------