diff --git a/.gitignore b/.gitignore index 4890c6903..097ab8fde 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,6 @@ contracts/hackatom/hash.txt # Auto-gen .cargo-ok + +*.pyc +__pycache__/ diff --git a/Cargo.lock b/Cargo.lock index 30e08d09c..c0c2c14cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,15 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + [[package]] name = "arrayref" version = "0.3.6" @@ -50,6 +59,15 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bincode2" version = "2.0.1" @@ -169,6 +187,12 @@ dependencies = [ "subtle 1.0.0", ] +[[package]] +name = "difference" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" + [[package]] name = "digest" version = "0.8.1" @@ -193,12 +217,33 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "downcast" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb454f0228b18c7f4c3b0ebbee346ed9c52e7443b0999cd543ff3571205701d" + [[package]] name = "fake-simd" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +[[package]] +name = "float-cmp" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1267f4ac4f343772758f7b1bdcbe767c218bbab93bb432acbf5162bbf85a6c4" +dependencies = [ + "num-traits", +] + +[[package]] +name = "fragile" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69a039c3498dc930fe810151a34ba0c1c70b02b8625035592e74432f678591f2" + [[package]] name = "generic-array" version = "0.12.4" @@ -251,6 +296,12 @@ version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libc" version = "0.2.98" @@ -303,6 +354,64 @@ dependencies = [ "snafu", ] +[[package]] +name = "mock_band" +version = "0.1.0" +dependencies = [ + "bincode", + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-storage", + "mockall", + "schemars", + "secret-toolkit", + "serde", + "shade-protocol", + "snafu", +] + +[[package]] +name = "mockall" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ab571328afa78ae322493cacca3efac6a0f2e0a67305b4df31fd439ef129ac0" +dependencies = [ + "cfg-if", + "downcast", + "fragile", + "lazy_static", + "mockall_derive", + "predicates", + "predicates-tree", +] + +[[package]] +name = "mockall_derive" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7e25b214433f669161f414959594216d8e6ba83b6679d3db96899c0b4639033" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + [[package]] name = "object" version = "0.26.0" @@ -328,9 +437,11 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" name = "oracle" version = "0.1.0" dependencies = [ + "bincode", "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", + "mockall", "schemars", "secret-toolkit", "serde", @@ -344,6 +455,35 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" +[[package]] +name = "predicates" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f49cfaf7fdaa3bfacc6fa3e7054e65148878354a5cfddcf661df4c851f8021df" +dependencies = [ + "difference", + "float-cmp", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates-core" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57e35a3326b75e49aa85f5dc6ec15b41108cf5aee58eabb1f274dd18b73c2451" + +[[package]] +name = "predicates-tree" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7dd0fd014130206c9352efbdc92be592751b2b9274dff685348341082c6ea3d" +dependencies = [ + "predicates-core", + "treeline", +] + [[package]] name = "proc-macro2" version = "1.0.28" @@ -398,6 +538,23 @@ dependencies = [ "rand_core", ] +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + [[package]] name = "rustc-demangle" version = "0.1.20" @@ -645,6 +802,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "treeline" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41" + [[package]] name = "typenum" version = "1.13.0" diff --git a/Cargo.toml b/Cargo.toml index 3c7db5517..b0d5db163 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,2 +1,7 @@ [workspace] -members = ["contracts/mint","contracts/oracle", "packages/shade_protocol"] +members = [ + "packages/shade_protocol", + "contracts/mint", + "contracts/oracle", + "contracts/mock_band", +] diff --git a/README.md b/README.md index 37d618acd..748ef7ec3 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,14 @@ | Contract | Reference | Description | | --------------------------- | --------------------------------- | ------------------------------------- | | [`mint`](./contracts/mint) | [doc](./contracts/mint/README.md) | Handles asset burning and silk minting| +| [`oracle`](./contracts/oracle) | [doc](./contracts/oracle/README.md) | Handles asset price queries | ## Development ## Development Environment Instlal docker for local envirnment -Source from [testner](https://build.scrt.network/dev/quickstart.html#setup-the-local-developer-testnet) +Source from [testnet](https://build.scrt.network/dev/quickstart.html#setup-the-local-developer-testnet) ``` docker run -it --rm -p 26657:26657 -p 26656:26656 -p 1337:1337 -v $(pwd):/root/code --name secretdev enigmampc/secret-network-sw-dev @@ -64,4 +65,7 @@ bash ./compile-contracts.sh ### Testing -You can optionally run extended tests inside a private testnet using the [contract tester](./contracts/compiled/contract_tester.py) \ No newline at end of file +You can optionally run extended tests using the [tester](contracts/compiled/tester.py) + +To run a test deployment on a public testnet you can run ```tester.py --testnet public```. +For the private testnet you can run ```tester.py --testnet private```. diff --git a/compile-contracts.sh b/compile-contracts.sh index 6afd0f71e..8651023cc 100755 --- a/compile-contracts.sh +++ b/compile-contracts.sh @@ -3,17 +3,23 @@ root_dir=$(git rev-parse --show-toplevel) contracts_dir="${root_dir}/contracts" compiled_dir="${contracts_dir}/compiled" +checksum_dir="${compiled_dir}/checksum" compile_contract() { # Run tests (cd ${contracts_dir}/$1; cargo unit-test) - (cd ${contracts_dir}/$1; cargo integration-test) + #(cd ${contracts_dir}/$1; cargo integration-test) (cd ${compiled_dir}; rm $1.wasm.gz) (cd ${contracts_dir}; cargo build --release --target wasm32-unknown-unknown --locked) wasm-opt -Oz ./target/wasm32-unknown-unknown/release/$1.wasm -o ./$1.wasm + echo $(md5sum $1.wasm | cut -f 1 -d " ") >> ${checksum_dir}/$1.txt cat ./$1.wasm | gzip -n -9 > ${compiled_dir}/$1.wasm.gz rm -f ./$1.wasm } +# There should be a cleaner way to do this +rm -r ${checksum_dir} +mkdir ${checksum_dir} compile_contract "mint" compile_contract "oracle" +compile_contract "mock_band" diff --git a/contracts/compiled/__init__.py b/contracts/compiled/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/contracts/compiled/checksum/mint.txt b/contracts/compiled/checksum/mint.txt new file mode 100644 index 000000000..f924f73f1 --- /dev/null +++ b/contracts/compiled/checksum/mint.txt @@ -0,0 +1 @@ +5837fae4e377ec630732b06ce018adba diff --git a/contracts/compiled/checksum/mock_band.txt b/contracts/compiled/checksum/mock_band.txt new file mode 100644 index 000000000..964d86b34 --- /dev/null +++ b/contracts/compiled/checksum/mock_band.txt @@ -0,0 +1 @@ +581bb67ce7144b7993190b46ece45893 diff --git a/contracts/compiled/checksum/oracle.txt b/contracts/compiled/checksum/oracle.txt new file mode 100644 index 000000000..ca921c0ee --- /dev/null +++ b/contracts/compiled/checksum/oracle.txt @@ -0,0 +1 @@ +ef8dc00f91dbedbff4f39fcb7161146c diff --git a/contracts/compiled/contract_tester.py b/contracts/compiled/contract_tester.py deleted file mode 100644 index ecdbed8a0..000000000 --- a/contracts/compiled/contract_tester.py +++ /dev/null @@ -1,63 +0,0 @@ -import random -from contractlib.secretlib import secretlib -from contractlib.snip20lib import SNIP20 -from contractlib.mintlib import Mint -from contractlib.oraclelib import Oracle -from contractlib.utils import gen_label - -account_key = 'a' -account = secretlib.run_command(['secretcli', 'keys', 'show', '-a', account_key]).rstrip() - -print("Configuring sSCRT") -sscrt = SNIP20(gen_label(8), decimals=6, public_total_supply=True, enable_deposit=True) -sscrt_password = sscrt.set_view_key(account_key, "password") -sscrt.print() - -sscrt_mint_amount = '100000000000000' -print(f"\tDepositing {sscrt_mint_amount} uSCRT") -sscrt.deposit(account, sscrt_mint_amount + "uscrt") -sscrt_minted = sscrt.get_balance(account, sscrt_password) -print(f"\tReceived {sscrt_minted} usSCRT") -assert sscrt_mint_amount == sscrt_minted, f"Minted {sscrt_minted}; expected {sscrt_mint_amount}" - -print("Configuring silk") -silk = SNIP20(gen_label(8), decimals=6, public_total_supply=True, enable_mint=True) -silk_password = silk.set_view_key(account_key, "password") - -print('Configuring Oracle') -oracle = Oracle(gen_label(8)) -price = int(oracle.get_silk_price()["price"]) -print(price / (10**18)) - -print("Configuring Mint contract") -mint = Mint(gen_label(8), silk, oracle) -silk.set_minters([mint.address]) -mint.register_asset(sscrt) -assets = mint.get_supported_assets()['supported_assets']['assets'][0] -assert sscrt.address == assets, f"Got {assets}; expected {sscrt.address}" - -print("Sending to mint contract") - -total_amount = int(sscrt_mint_amount) -minimum_amount = 1000 -total_tests = 5 - -total_sent = 0 - -for i in range(total_tests): - send_amount = random.randint(minimum_amount, int(total_amount/total_tests)-1) - total_sent += send_amount - - print(f"\tSending {send_amount} usSCRT") - sscrt.send(account_key, mint.address, send_amount) - silk_minted = silk.get_balance(account, silk_password) - #assert total_sent == int(silk_minted), f"Total minted {silk_minted}; expected {total_sent}" - - print(f"\tSilk balance: {silk_minted} uSILK") - burned_amount = mint.get_asset(sscrt)["asset"]["asset"]["burned_tokens"] - print(f"\tTotal burned: {burned_amount} usSCRT\n") - #assert total_sent == int(burned_amount), f"Burnt {burned_amount}; expected {total_sent}" - -print("Testing migration") -new_mint = mint.migrate(gen_label(8), int(mint.contract_id), mint.code_hash) -assert mint.get_supported_assets() == new_mint.get_supported_assets(), "Contracts are not the same" diff --git a/contracts/compiled/contractlib/__init__.py b/contracts/compiled/contractlib/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/contracts/compiled/contractlib/contractlib.py b/contracts/compiled/contractlib/contractlib.py index f1261c61a..809b93c5c 100644 --- a/contracts/compiled/contractlib/contractlib.py +++ b/contracts/compiled/contractlib/contractlib.py @@ -2,24 +2,25 @@ class PreInstantiatedContract: - def __init__(self, contract_id, address, code_hash): - self.contract_id = contract_id + def __init__(self, address, code_hash): self.address = address self.code_hash = code_hash class Contract: - def __init__(self, contract, initMsg, label, admin='a', uploader='a', gas='10000000', backend='test', wait=6, - instantiated_contract=None): + def __init__(self, contract, initMsg, label, admin='a', uploader='a', gas='10000000', backend='test', + instantiated_contract=None, code_id=None): self.label = label self.admin = admin self.uploader = uploader self.gas = gas self.backend = backend - self.wait = wait if instantiated_contract is None: - self.contract_id = secretlib.store_contract(contract, uploader, gas, backend) + if code_id is None: + self.contract_id = secretlib.store_contract(contract, uploader, gas, backend) + else: + self.contract_id = code_id initResponse = secretlib.instantiate_contract(str(self.contract_id), initMsg, label, admin, backend) contracts = secretlib.list_code() self.code_hash = contracts[int(self.contract_id) - 1]["data_hash"] @@ -29,7 +30,7 @@ def __init__(self, contract, initMsg, label, admin='a', uploader='a', gas='10000 break else: - self.contract_id = instantiated_contract.contract_id + self.contract_id = code_id self.code_hash = instantiated_contract.code_hash self.address = instantiated_contract.address diff --git a/contracts/compiled/contractlib/mintlib.py b/contracts/compiled/contractlib/mintlib.py index 5b34ac0b4..21ce41234 100644 --- a/contracts/compiled/contractlib/mintlib.py +++ b/contracts/compiled/contractlib/mintlib.py @@ -6,13 +6,12 @@ class Mint(Contract): - def __init__(self, label, silk, oracle, contract='mint.wasm.gz', admin='a', uploader='a', gas='10000000', - backend='test', instantiated_contract=None): + def __init__(self, label, oracle, contract='mint.wasm.gz', admin='a', uploader='a', gas='10000000', + backend='test', instantiated_contract=None, code_id=None): init_msg = json.dumps( - {"silk": {"address": silk.address, "code_hash": silk.code_hash}, - "oracle": {"address": oracle.address, "code_hash": oracle.code_hash}}) + {"oracle": {"address": oracle.address, "code_hash": oracle.code_hash}}) super().__init__(contract, init_msg, label, admin, uploader, gas, backend, - instantiated_contract=instantiated_contract) + instantiated_contract=instantiated_contract, code_id=code_id) def migrate(self, label, code_id, code_hash): """ @@ -34,7 +33,7 @@ def migrate(self, label, code_id, code_hash): new_mint.code_hash = code_hash return new_mint - def update_config(self, owner=None, silk=None, oracle=None): + def update_config(self, owner=None, oracle=None): """ Updates the minting contract's config :param owner: New admin @@ -45,12 +44,6 @@ def update_config(self, owner=None, silk=None, oracle=None): raw_msg = {"update_config": {}} if owner is not None: raw_msg["update_config"]["owner"] = owner - if silk is not None: - contract = { - "address": silk.address, - "code_hash": silk.code_hash - } - raw_msg["update_config"]["silk"] = contract if oracle is not None: contract = { "address": oracle.address, @@ -61,27 +54,23 @@ def update_config(self, owner=None, silk=None, oracle=None): msg = json.dumps(raw_msg) return self.execute(msg) - def register_asset(self, snip20): + def register_asset(self, snip20, name=None, burnable=None, total_burned=None): """ Registers a SNIP20 asset + :param total_burned: Total value burned + :param burnable: If burning is allowed + :param name: The Snip20's ticker :param snip20: SNIP20 object to add :return: Result """ - msg = json.dumps( - {"register_asset": {"contract": {"address": snip20.address, "code_hash": snip20.code_hash}}}) - - return self.execute(msg) - - def update_asset(self, old_snip20, snip20): - """ - Updates a SNIP20 asset's info - :param old_snip20: The registered snip20 - :param snip20: New snip20 to replace with - :return: Result - """ - msg = json.dumps( - {"update_asset": {"asset": old_snip20.address, "contract": {"address": snip20.address, - "code_hash": snip20.code_hash}}}) + raw_msg = {"register_asset": {"contract": {"address": snip20.address, "code_hash": snip20.code_hash}}} + if name is not None: + raw_msg["register_asset"]["name"] = name + if burnable is not None: + raw_msg["register_asset"]["burnable"] = burnable + if total_burned is not None: + raw_msg["register_asset"]["total_burned"] = total_burned + msg = json.dumps(raw_msg) return self.execute(msg) diff --git a/contracts/compiled/contractlib/oraclelib.py b/contracts/compiled/contractlib/oraclelib.py index c8b4c6050..e1ca1ff58 100644 --- a/contracts/compiled/contractlib/oraclelib.py +++ b/contracts/compiled/contractlib/oraclelib.py @@ -6,18 +6,41 @@ class Oracle(Contract): - def __init__(self, label, contract='oracle.wasm.gz', admin='a', uploader='a', gas='10000000', backend='test', - instantiated_contract=None): - init_msg = json.dumps({}) + def __init__(self, label, band_contract, contract='oracle.wasm.gz', admin='a', uploader='a', gas='10000000', backend='test', + instantiated_contract=None, code_id=None): + + init_msg = json.dumps({ + 'band': { + 'address': band_contract.address, + 'code_hash': band_contract.code_hash, + } + }) + super().__init__(contract, init_msg, label, admin, uploader, gas, backend, - instantiated_contract=instantiated_contract) + instantiated_contract=instantiated_contract, code_id=code_id) + + def get_price(self, coin): + """ + Get current coin price + :param coin: Coin ticker + :return: + """ + msg = json.dumps({'get_price': {'symbol': coin}}) + + return self.query(msg) + + def get_shade_price(self): + """ + Get current shade price + :return: + """ + + return self.get_price('SHD') - def get_silk_price(self): + def get_scrt_price(self): """ - Get current silk price + Get current scrt price :return: """ - msg = json.dumps( - {"get_scrt_price": {}}) - return self.query(msg) \ No newline at end of file + return self.get_price('SCRT') diff --git a/contracts/compiled/contractlib/secretlib/__init__.py b/contracts/compiled/contractlib/secretlib/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/contracts/compiled/contractlib/secretlib/secretlib.py b/contracts/compiled/contractlib/secretlib/secretlib.py index b6737c860..e4fc0aef0 100644 --- a/contracts/compiled/contractlib/secretlib/secretlib.py +++ b/contracts/compiled/contractlib/secretlib/secretlib.py @@ -1,12 +1,13 @@ -import subprocess +from subprocess import Popen, PIPE import json import time # Presetup some commands query_list_code = ['secretcli', 'query', 'compute', 'list-code'] +MAX_TRIES = 10 -def run_command(command, wait=6): +def run_command(command): """ Will run any cli command and return its output after waiting a set amount :param command: Array of command to run @@ -14,12 +15,12 @@ def run_command(command, wait=6): :return: Output string """ - result = subprocess.run(command, stdout=subprocess.PIPE, text=True) - time.sleep(wait) - return result.stdout + p = Popen(command, stdout=PIPE, stderr=PIPE, text=True) + output, err = p.communicate() + status = p.wait() + return output - -def store_contract(contract, user='a', gas='10000000', backend='test', wait=15): +def store_contract(contract, user='a', gas='10000000', backend='test'): """ Store contract and return its ID :param contract: Contract name @@ -35,12 +36,16 @@ def store_contract(contract, user='a', gas='10000000', backend='test', wait=15): if backend is not None: command += ['--keyring-backend', backend] - for attribute in run_command_query_hash(command, wait)['logs'][0]['events'][0]['attributes']: - if attribute["key"] == "code_id": - return attribute['value'] + output = run_command_query_hash(command) + if 'logs' not in output: + return str(output) + else: + for attribute in output['logs'][0]['events'][0]['attributes']: + if attribute["key"] == "code_id": + return attribute['value'] -def instantiate_contract(contract, msg, label, user='a', backend='test', wait=6): +def instantiate_contract(contract, msg, label, user='a', backend='test'): """ Instantiates a contract :param contract: Contract name @@ -52,21 +57,21 @@ def instantiate_contract(contract, msg, label, user='a', backend='test', wait=6) """ command = ['secretcli', 'tx', 'compute', 'instantiate', contract, msg, '--from', - user, '--label', label, '-y'] + user, '--gas', '300000', '--label', label, '-y'] if backend is not None: command += ['--keyring-backend', backend] - return run_command_query_hash(command, wait) + return run_command_query_hash(command) def list_code(): command = ['secretcli', 'query', 'compute', 'list-code'] - return json.loads(run_command(command, 3)) + return json.loads(run_command(command)) -def execute_contract(contract, msg, user='a', backend='test', amount=None, compute=True, wait=6): +def execute_contract(contract, msg, user='a', backend='test', amount=None, compute=True): command = ['secretcli', 'tx', 'compute', 'execute', contract, msg, '--from', user, '--gas', '10000000', '-y'] if backend is not None: @@ -77,12 +82,12 @@ def execute_contract(contract, msg, user='a', backend='test', amount=None, compu command.append(amount) if compute: - return run_command_compute_hash(command, wait) - return run_command_query_hash(command, wait) + return run_command_compute_hash(command) + return run_command_query_hash(command) def query_hash(hash): - return run_command(['secretcli', 'q', 'tx', hash], 3) + return run_command(['secretcli', 'q', 'tx', hash]) def compute_hash(hash): @@ -95,13 +100,26 @@ def query_contract(contract, msg): return json.loads(run_command(command)) -def run_command_compute_hash(command, wait=6): - out = run_command(command, wait) +def run_command_compute_hash(command): + out = run_command(command) txhash = json.loads(out)["txhash"] - return json.loads(compute_hash(txhash)) + for _ in range(MAX_TRIES): + try: + out = json.loads(compute_hash(txhash)) + return out + except: + time.sleep(1) + print(' '.join(command), f'exceeded max tries ({MAX_TRIES})') -def run_command_query_hash(command, wait=6): - out = run_command(command, wait) +def run_command_query_hash(command): + out = run_command(command) txhash = json.loads(out)["txhash"] - return json.loads(query_hash(txhash)) + + for _ in range(MAX_TRIES): + try: + out = json.loads(query_hash(txhash)) + return out + except: + time.sleep(1) + print(' '.join(command), f'exceeded max tries ({MAX_TRIES})') diff --git a/contracts/compiled/contractlib/snip20lib.py b/contracts/compiled/contractlib/snip20lib.py index 388b1893b..5249d1e6c 100644 --- a/contracts/compiled/contractlib/snip20lib.py +++ b/contracts/compiled/contractlib/snip20lib.py @@ -7,7 +7,7 @@ class SNIP20(Contract): def __init__(self, label, name="token", symbol="TKN", decimals=3, seed="cGFzc3dvcmQ=", public_total_supply=False, enable_deposit=False, enable_redeem=False, enable_mint=False, enable_burn=False, contract='snip20.wasm.gz', admin='a', uploader='a', gas='10000000', backend='test', - instantiated_contract=None): + instantiated_contract=None, code_id=None): self.view_key = "" initMsg = json.dumps( {"name": name, "symbol": symbol, "decimals": decimals, "prng_seed": seed, "config": { @@ -15,7 +15,7 @@ def __init__(self, label, name="token", symbol="TKN", decimals=3, seed="cGFzc3dv "enable_redeem": enable_redeem, "enable_mint": enable_mint, "enable_burn": enable_burn }}) super().__init__(contract, initMsg, label, admin, uploader, gas, backend, - instantiated_contract=instantiated_contract) + instantiated_contract=instantiated_contract, code_id=code_id) def set_minters(self, accounts): """ @@ -52,16 +52,22 @@ def mint(self, recipient, amount): return self.execute(msg) - def send(self, account, recipient, amount): + def send(self, account, recipient, amount, message=None): """ Send amount from an account to a recipient :param account: User to generate the key for :param recipient: Address to be minted in :param amount: Amount to mint + :param message: Base64 encoded message :return: Response """ - msg = json.dumps( - {"send": {"recipient": recipient, "amount": str(amount)}}) + + raw_msg = {"send": {"recipient": recipient, "amount": str(amount)}} + + if message is not None: + raw_msg["send"]["msg"] = message + + msg = json.dumps(raw_msg) return self.execute(msg, account) @@ -88,3 +94,12 @@ def get_balance(self, address, password): {"balance": {"key": password, "address": address}}) return self.query(msg)["balance"]["amount"] + + def get_token_info(self): + """ + Gets token info + :return: Response + """ + msg = json.dumps({"token_info": {}}) + + return self.query(msg) \ No newline at end of file diff --git a/contracts/compiled/contractlib/utils.py b/contracts/compiled/contractlib/utils.py index 58decdd74..83f872380 100644 --- a/contracts/compiled/contractlib/utils.py +++ b/contracts/compiled/contractlib/utils.py @@ -1,7 +1,15 @@ import string import random +import base64 +import json def gen_label(length): # With combination of lower and upper case return ''.join(random.choice(string.ascii_letters) for i in range(length)) + + +def to_base64(dict): + dict_str = json.dumps(dict).encode('ascii') + encoded_str = base64.b64encode(dict_str) + return encoded_str.decode() diff --git a/contracts/compiled/mint.wasm.gz b/contracts/compiled/mint.wasm.gz index 1f0cf4c54..8daab652c 100644 Binary files a/contracts/compiled/mint.wasm.gz and b/contracts/compiled/mint.wasm.gz differ diff --git a/contracts/compiled/mock_band.wasm.gz b/contracts/compiled/mock_band.wasm.gz new file mode 100644 index 000000000..728c1c958 Binary files /dev/null and b/contracts/compiled/mock_band.wasm.gz differ diff --git a/contracts/compiled/oracle.wasm.gz b/contracts/compiled/oracle.wasm.gz index 1337a1103..3d32c53be 100644 Binary files a/contracts/compiled/oracle.wasm.gz and b/contracts/compiled/oracle.wasm.gz differ diff --git a/contracts/compiled/tester.py b/contracts/compiled/tester.py new file mode 100755 index 000000000..2881af823 --- /dev/null +++ b/contracts/compiled/tester.py @@ -0,0 +1,244 @@ +#!/usr/bin/env python3 +import copy +import json +import base64 +import random +import argparse +from contractlib.contractlib import PreInstantiatedContract +from contractlib.contractlib import Contract +from contractlib.secretlib import secretlib +from contractlib.snip20lib import SNIP20 +from contractlib.mintlib import Mint +from contractlib.oraclelib import Oracle +from contractlib.utils import gen_label, to_base64 + + +def test_send(burn_asset, burn_asset_password, mint_asset, mint_asset_password, mint, slipage, amount, account, account_key): + # Save token symbol + burn_asset_symbol = burn_asset.get_token_info()["token_info"]["symbol"] + mint_asset_symbol = mint_asset.get_token_info()["token_info"]["symbol"] + + # Get all the token amounts before sending + user_burn_asset_before = burn_asset.get_balance(account, burn_asset_password) + user_mint_asset_before = mint_asset.get_balance(account, mint_asset_password) + burn_asset_supply_before = burn_asset.get_token_info()["token_info"]["total_supply"] + queried_burn_asset_supply_before = mint.get_asset(burn_asset)["asset"]["asset"]["total_burned"] + mint_asset_supply_before = mint_asset.get_token_info()["token_info"]["total_supply"] + + # Get all the token amounts after sending + msg = to_base64({"minimum_expected_amount": str(slipage), "to_mint": mint_asset.address}) + send_response = burn_asset.send(account_key, mint.address, amount, msg) + + if send_response["output_error"] != {}: + print(f"Mint error: {send_response['output_error']}") + + user_burn_asset_after = burn_asset.get_balance(account, burn_asset_password) + user_mint_asset_after = mint_asset.get_balance(account, mint_asset_password) + burn_asset_supply_after = burn_asset.get_token_info()["token_info"]["total_supply"] + queried_burn_asset_supply_after = mint.get_asset(burn_asset)["asset"]["asset"]["total_burned"] + mint_asset_supply_after = mint_asset.get_token_info()["token_info"]["total_supply"] + print(f"Sending: {amount} u{burn_asset_symbol} to receive u{mint_asset_symbol}\n" + f"Sent: {int(user_burn_asset_before) - int(user_burn_asset_after)} u{burn_asset_symbol}\n" + f"Burned {int(burn_asset_supply_before) - int(burn_asset_supply_after)} u{burn_asset_symbol}\n" + f"Burn Query: {int(queried_burn_asset_supply_after) - int(queried_burn_asset_supply_before)} u{burn_asset_symbol}\n" + f"Received: {int(user_mint_asset_after) - int(user_mint_asset_before)} u{mint_asset_symbol}\n" + f"Mint: {int(mint_asset_supply_after) - int(mint_asset_supply_before)} u{mint_asset_symbol}\n") + + +parser = argparse.ArgumentParser(description='Automated smart contract tester') +parser.add_argument("--testnet", choices=["private", "public"], default="private", type=str, required=False, + help="Specify which deploy scenario to run") + +args = parser.parse_args() + +if args.testnet == "private": + account_key = 'a' + account = secretlib.run_command(['secretcli', 'keys', 'show', '-a', account_key]).rstrip() + + print("Configuring sSCRT") + sscrt = SNIP20(gen_label(8), name="sSCRT", symbol="SSCRT", decimals=6, public_total_supply=True, + enable_deposit=True) + sscrt_password = sscrt.set_view_key(account_key, "password") + + sscrt_mint_amount = '100000000000000' + print(f"\tDepositing {sscrt_mint_amount} uSCRT") + sscrt.deposit(account, sscrt_mint_amount + "uscrt") + sscrt_minted = sscrt.get_balance(account, sscrt_password) + print(f"\tReceived {sscrt_minted} usSCRT") + assert sscrt_mint_amount == sscrt_minted, f"Minted {sscrt_minted}; expected {sscrt_mint_amount}" + + print("Configuring Silk") + silk = SNIP20(gen_label(8), name="Silk", symbol="SILK", decimals=6, public_total_supply=True, enable_mint=True, + enable_burn=True) + silk_password = silk.set_view_key(account_key, "password") + + print("Configuring Shade") + shade = SNIP20(gen_label(8), name="Shade", symbol="SHD", decimals=6, public_total_supply=True, enable_mint=True, + enable_burn=True) + shade_password = shade.set_view_key(account_key, "password") + + print('Mocking Band') + band = Contract('mock_band.wasm.gz', '{}', gen_label(8)) + + print('Configuring Oracle') + oracle = Oracle(gen_label(8), band) + price = int(oracle.get_scrt_price()["rate"]) + print(price / (10 ** 18)) + + print("Configuring Mint contract") + mint = Mint(gen_label(8), oracle) + mint.register_asset(silk, burnable=True) + silk.set_minters([mint.address]) + mint.register_asset(shade, burnable=True) + shade.set_minters([mint.address]) + mint.register_asset(sscrt, name="SCRT") + assets = mint.get_supported_assets()['supported_assets']['assets'] + assert 3 == len(assets), f"Got {len(assets)}; expected {3}" + + print("Sending to mint contract") + + total_amount = int(sscrt_mint_amount) + minimum_amount = 1000 + total_tests = 1 + + total_sent = 0 + + for i in range(total_tests): + send_amount = random.randint(minimum_amount, int(total_amount / total_tests / 2) - 1) + total_sent += send_amount + + test_send(sscrt, sscrt_password, silk, silk_password, mint, 1, send_amount, account, account_key) + test_send(sscrt, sscrt_password, shade, shade_password, mint, 1, send_amount, account, account_key) + + send_amount = 1_000_000_000 + test_send(silk, silk_password, shade, shade_password, mint, 1, send_amount, account, account_key) + + send_amount = 10_000_000 + test_send(shade, shade_password, silk, silk_password, mint, 1, send_amount, account, account_key) + + print("Testing migration") + new_mint = mint.migrate(gen_label(8), int(mint.contract_id), mint.code_hash) + assert mint.get_supported_assets() == new_mint.get_supported_assets(), "Contracts are not the same" + +if args.testnet == "public": + account_key = 'admin' + account = secretlib.run_command(['secretcli', 'keys', 'show', '-a', account_key]).rstrip() + + with open("testnet-contracts.json", "r") as json_file: + contracts_config = json.load(json_file) + + print("Configuring Silk") + silk_updated = False + if contracts_config["silk"]["address"] == "": + print("Instantiating Silk") + contracts_config["silk"]["label"] = f"silk-{gen_label(8)}" + silk_instantiated_contract = None + silk_updated = True + else: + silk_instantiated_contract = PreInstantiatedContract( + address=contracts_config["silk"]["address"], + code_hash=contracts_config["silk"]["code_hash"]) + + silk = SNIP20(contracts_config["silk"]["label"], "silk", "SLK", decimals=6, public_total_supply=True, + enable_mint=True, enable_burn=True, admin=account, uploader=account, backend=None, + instantiated_contract=silk_instantiated_contract, code_id=contracts_config["silk"]["contract_id"]) + + contracts_config["silk"]["address"] = silk.address + contracts_config["silk"]["code_hash"] = silk.code_hash + silk_password = silk.set_view_key(account_key, "password") + silk.print() + + print("Configuring shade") + shade_updated = False + if contracts_config["shade"]["address"] == "": + print("Instantiating Shade") + contracts_config["shade"]["label"] = f"shade-{gen_label(8)}" + shade_instantiated_contract = None + shade_updated = True + else: + shade_instantiated_contract = PreInstantiatedContract( + address=contracts_config["shade"]["address"], + code_hash=contracts_config["shade"]["code_hash"]) + + shade = SNIP20(contracts_config["shade"]["label"], "shade", "SHD", decimals=6, public_total_supply=True, + enable_mint=True, enable_burn=True, admin=account, uploader=account, backend=None, + instantiated_contract=shade_instantiated_contract, code_id=contracts_config["shade"]["contract_id"]) + + contracts_config["shade"]["address"] = shade.address + contracts_config["shade"]["code_hash"] = shade.code_hash + shade_password = shade.set_view_key(account_key, "password") + shade.print() + + print("Configuring sSCRT") + sscrt = copy.deepcopy(silk) + sscrt.label = contracts_config["sscrt"]["label"] + sscrt.address = contracts_config["sscrt"]["address"] + sscrt.code_hash = contracts_config["sscrt"]["code_hash"] + sscrt_password = sscrt.set_view_key(account_key, "password") + sscrt.print() + + print("Configuring Oracle") + oracle_updated = False + band_contract = PreInstantiatedContract("secret1p0jtg47hhwuwgp4cjpc46m7qq6vyjhdsvy2nph", + "77c854ea110315d5103a42b88d3e7b296ca245d8b095e668c69997b265a75ac5") + with open("checksum/oracle.txt", 'r') as oracle_checksum: + if oracle_checksum.readline().strip() == contracts_config["oracle"]["checksum"].strip(): + oracle_instantiated_contract = PreInstantiatedContract( + address=contracts_config["oracle"]["address"], + code_hash=contracts_config["oracle"]["code_hash"]) + oracle = Oracle(contracts_config["oracle"]["label"], band_contract, admin=account, uploader=account, + backend=None, + instantiated_contract=oracle_instantiated_contract, + code_id=contracts_config["oracle"]["contract_id"]) + else: + print("Instantiating Oracle") + oracle_updated = True + contracts_config["oracle"]["label"] = f"oracle-{gen_label(8)}" + oracle = Oracle(contracts_config["oracle"]["label"], band_contract, admin=account, uploader=account, + backend=None) + contracts_config["oracle"]["contract_id"] = oracle.contract_id + contracts_config["oracle"]["address"] = oracle.address + contracts_config["oracle"]["code_hash"] = oracle.code_hash + + print(oracle.get_scrt_price()) + oracle.print() + + print("Configuring Mint") + mint_updated = False + with open("checksum/mint.txt", 'r') as mint_checksum: + mint_instantiated_contract = PreInstantiatedContract( + address=contracts_config["mint"]["address"], + code_hash=contracts_config["mint"]["code_hash"]) + mint = Mint(contracts_config["mint"]["label"], silk, shade, oracle, admin=account, uploader=account, + backend=None, + instantiated_contract=mint_instantiated_contract, code_id=contracts_config["mint"]["contract_id"]) + + if mint_checksum.readline().strip() != contracts_config["mint"]["checksum"].strip(): + print("Instantiating Mint") + mint_updated = True + label = f"mint-{gen_label(8)}" + # TODO: upload and get codehash + id of the contract without instantiating to call the mint.migrate + new_mint = Mint(label, silk, shade, oracle, admin=account, uploader=account, backend=None) + # mint.migrate() + mint = copy.deepcopy(new_mint) + contracts_config["mint"]["label"] = label + contracts_config["mint"]["contract_id"] = mint.contract_id + contracts_config["mint"]["address"] = mint.address + contracts_config["mint"]["code_hash"] = mint.code_hash + + if silk_updated or oracle_updated or shade_updated: + mint.update_config(silk=silk, oracle=oracle) + + if silk_updated or mint_updated: + silk.set_minters([mint.address]) + + if mint_updated: + mint.register_asset(sscrt) + + assets = mint.get_supported_assets()['supported_assets']['assets'][0] + assert sscrt.address == assets, f"Got {assets}; expected {sscrt.address}" + mint.print() + + # Save json data + with open('testnet-contracts.json', 'w', encoding='utf-8') as json_file: + json.dump(contracts_config, json_file, ensure_ascii=False, indent=4) diff --git a/contracts/compiled/testnet-contracts.json b/contracts/compiled/testnet-contracts.json new file mode 100644 index 000000000..6e68af20d --- /dev/null +++ b/contracts/compiled/testnet-contracts.json @@ -0,0 +1,33 @@ +{ + "mint": { + "label": "mint-myXFfmGc", + "contract_id": "30215", + "address": "secret1kayf30aj8797jeygwd69qe0zaqj58wskxet4h2", + "code_hash": "B5CFBBBDB167D3EF9359DD31DEC02369981239CBB8632FE6929B858896362C8D", + "checksum": "b217f26eae6d450570ee295b0d604663" + }, + "oracle": { + "label": "oracle-bBwSOgHV", + "contract_id": "30214", + "address": "secret13zdwq0as65v0wtqtu3rl9c6ypymlw4ltmgf2la", + "code_hash": "F280D855FEF99EE9D6594086C897EB9AD76469C898FBC945A673C38405C975F4", + "checksum": "31c3ff776e85627bf77c00787a94a68b" + }, + "silk": { + "label": "silk", + "contract_id": 30067, + "address": "secret1zccl8tccrhwru2mfwzmxqz6lvmp08y9359jurx", + "code_hash": "35F5DB2BC5CD56815D10C7A567D6827BECCB8EAF45BC3FA016930C4A8209EA69" + }, + "shade": { + "label": "shade-kDFYsowA", + "contract_id": 30067, + "address": "secret19wpwzyd7km3q4qkc4x80zz7fk5e3arxpmgwqs6", + "code_hash": "35F5DB2BC5CD56815D10C7A567D6827BECCB8EAF45BC3FA016930C4A8209EA69" + }, + "sscrt": { + "label": "sSCRT", + "address": "secret1s7c6xp9wltthk5r6mmavql4xld5me3g37guhsx", + "code_hash": "CD400FB73F5C99EDBC6AAB22C2593332B8C9F2EA806BF9B42E3A523F3AD06F62" + } +} \ No newline at end of file diff --git a/contracts/mint/README.md b/contracts/mint/README.md index 79290095c..308c1d412 100644 --- a/contracts/mint/README.md +++ b/contracts/mint/README.md @@ -7,7 +7,6 @@ * [Migrate](#Migrate) * [UpdateConfig](#UpdateConfig) * [RegisterAsset](#RegisterAsset) - * [UpdateAsset](#UpdateAsset) * Queries * [GetConfig](#GetConfig) * [SupportedAssets](#SupportedAssets) @@ -22,11 +21,11 @@ The minting contract is used as a way to acquire newly minted Silk, sending a se ## Init ##### Request -|Name |Type |Description | optional | -|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| -|owner | string | New contract owner; SHOULD be a valid bech32 address, but contracts may use a different naming scheme as well | yes | -|silk | Contract | Silk contract | no | -|oracle | Contract | Oracle contract | no | +|Name |Type |Description | optional | +|---------------|-------------------------|-------------------------------------------------------------------------------------------------------------------|----------| +|owner | string | New contract owner; SHOULD be a valid bech32 address, but contracts may use a different naming scheme as well | yes | +|oracle | Contract | Oracle contract | no | +|initial_assets | array of SupportedAsset | Usually used for first migration, its good for transfering its state to another contract | yes ## Admin @@ -54,8 +53,7 @@ Updates the given values |Name |Type |Description | optional | |----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| |owner | string | New contract owner; SHOULD be a valid bech32 address, but contracts may use a different naming scheme as well | yes | -|silk | Contract | Silk contract | no | -|oracle | Contract | Oracle contract | no | +|oracle | Contract | Oracle contract | yes | ##### Response ```json { @@ -70,9 +68,12 @@ Registers a supported asset. The asset must be SNIP-20 compliant since [Register Note: Will return an error if there's an asset with that address already registered. ##### Request -|Name |Type |Description | optional | -|------------|--------|-----------------------------------------------------------------------------------------------------------------------|----------| -|contract | Contract | Type explained [here](#Contract) | no | +|Name |Type |Description | optional | +|------------|----------|---------------------------------------------------------------------------------------------|----------| +|name | String | Ticker replacement, can be left empty and it will use the given snip20's ticker | yes | +|contract | Contract | Type explained [here](#Contract) | no | +|burnable | Boolean | If true, when sending the asset, the contract will try to burn | yes | +|total_burned| Uint128 | Used when migrating | Defaults to 0 | yes | ##### Response ```json { @@ -82,24 +83,6 @@ Note: Will return an error if there's an asset with that address already registe } ``` -#### UpdateAsset -Updates a supported asset. The asset must be SNIP-20 compliant since [RegisterReceive](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-20.md#RegisterReceive) is called. - -Note: Will return an error if no asset exists already with that address. -##### Request -|Name |Type |Description | optional | -|------------|----------|-----------------------------------------------------------------------------------------------------------------------|----------| -|asset | string | Asset to update; SHOULD be a valid bech32 address, but contracts may use a different naming scheme as well | no | -|contract | Contract | Type explained [here](#Contract) | no | -##### Response -```json -{ - "update_asset": { - "status": "success" - } -} -``` - ### Queries #### GetConfig @@ -163,6 +146,11 @@ Get specific information on a supported asset. #### Receive To mint the user must use a supported asset's send function and send the amount over to the contract's address. The contract will take care of the rest. +In the msg field of a snip20 send command you must send a base64 encoded json like this one +```json +{"minimum_expected_amount": "Uint128", "to_mint": "Contract_Addr" } +``` + ## Contract Type used in many of the admin commands ```json diff --git a/contracts/mint/examples/schema.rs b/contracts/mint/examples/schema.rs index 54d7d06fb..99a11865b 100644 --- a/contracts/mint/examples/schema.rs +++ b/contracts/mint/examples/schema.rs @@ -4,7 +4,7 @@ use std::fs::create_dir_all; use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; use mint::msg::{SupportedAssetsResponse, AssetResponse, HandleMsg, InitMsg, QueryMsg}; -use mint::state::{MintConfig, BurnableAsset}; +use mint::state::{MintConfig, SupportedAsset}; fn main() { let mut out_dir = current_dir().unwrap(); @@ -16,7 +16,7 @@ fn main() { export_schema(&schema_for!(HandleMsg), &out_dir); export_schema(&schema_for!(QueryMsg), &out_dir); export_schema(&schema_for!(MintConfig), &out_dir); - export_schema(&schema_for!(BurnableAsset), &out_dir); + export_schema(&schema_for!(SupportedAsset), &out_dir); export_schema(&schema_for!(SupportedAssetsResponse), &out_dir); export_schema(&schema_for!(AssetResponse), &out_dir); } diff --git a/contracts/mint/src/contract.rs b/contracts/mint/src/contract.rs index 7b4937921..8823f5b49 100644 --- a/contracts/mint/src/contract.rs +++ b/contracts/mint/src/contract.rs @@ -1,20 +1,22 @@ -use cosmwasm_std::{debug_print, to_binary, Api, Binary, Env, Extern, HandleResponse, InitResponse, Querier, StdError, StdResult, Storage, CosmosMsg, HumanAddr, Uint128}; +use cosmwasm_std::{debug_print, to_binary, Api, Binary, Env, Extern, HandleResponse, InitResponse, Querier, StdError, StdResult, Storage, CosmosMsg, HumanAddr, Uint128, from_binary}; use crate::state::{config, config_read, assets_w, assets_r, asset_list, asset_list_read}; use secret_toolkit::{ - snip20::{mint_msg, register_receive_msg, token_info_query}, + snip20::{mint_msg, burn_msg, register_receive_msg, token_info_query, minters_query}, }; use shade_protocol::{ - mint::{InitMsg, HandleMsg, HandleAnswer, QueryMsg, QueryAnswer, AssetMsg, MintConfig, BurnableAsset}, + mint::{InitMsg, HandleMsg, HandleAnswer, QueryMsg, QueryAnswer, MintConfig, SupportedAsset, SnipMsgHook}, + oracle::{ + ReferenceData, + QueryMsg::GetPrice, + }, asset::{Contract}, msg_traits::{Init, Query}, + generic_response::ResponseStatus, }; -use shade_protocol::generic_response::ResponseStatus; +use std::convert::TryFrom; -// TODO: tester that tests for contract availability // TODO: add remove asset // TODO: add spacepad padding -// TODO: father contract must be snip20 contract owner -// TODO: father contract must change minters when migrating pub fn init( deps: &mut Extern, env: Env, @@ -25,25 +27,25 @@ pub fn init( None => { env.message.sender.clone() } Some(admin) => { admin } }, - silk: msg.silk, oracle: msg.oracle, activated: true, }; config(&mut deps.storage).save(&state)?; + let mut messages = vec![]; let empty_assets_list: Vec = Vec::new(); asset_list(&mut deps.storage).save(&empty_assets_list)?; if let Some(assets) = msg.initial_assets { for asset in assets { - let _response = try_register_asset(deps, &env, asset.contract, asset.burned_tokens); + messages.push(save_asset(deps, &env, asset)?); } } debug_print!("Contract was initialized by {}", env.message.sender); Ok(InitResponse { - messages: vec![], + messages, log: vec![] }) } @@ -61,16 +63,14 @@ pub fn handle( } => try_migrate(deps, env, label, code_id, code_hash), HandleMsg::UpdateConfig { owner, - silk, oracle - } => try_update_config(deps, env, owner, silk, oracle), + } => try_update_config(deps, env, owner, oracle), HandleMsg::RegisterAsset { + name, contract, - } => try_register_asset(deps, &env, contract, None), - HandleMsg::UpdateAsset { - asset, - contract, - } => try_update_asset(deps, env, asset, contract), + burnable, + total_burned, + } => try_register_asset(deps, &env, name, contract, burnable, total_burned), HandleMsg::Receive { sender, from, @@ -91,28 +91,26 @@ pub fn try_migrate( return Err(StdError::Unauthorized { backtrace: None }); } + // Disable contract let mut config = config(&mut deps.storage); config.update(|mut state| { state.activated = false; Ok(state) })?; + // Move all registered assets + let mut initial_assets: Vec = vec![]; let config_read = config.load()?; - let mut initial_assets: Vec = vec![]; let assets = assets_r(&deps.storage); - for asset_addr in asset_list_read(&deps.storage).load()? { if let Some(item) = assets.may_load(asset_addr.as_bytes())? { - initial_assets.push(AssetMsg { - contract: item.contract, - burned_tokens: Some(item.burned_tokens), - }) + initial_assets.push(item) } }; + // Move config let init_msg = InitMsg { admin: Option::from(config_read.owner), - silk: config_read.silk, oracle: config_read.oracle, initial_assets: Some(initial_assets) }; @@ -129,7 +127,6 @@ pub fn try_update_config( deps: &mut Extern, env: Env, owner: Option, - silk: Option, oracle: Option, ) -> StdResult { if !authorized(deps, &env, AllowedAccess::Admin)? { @@ -142,9 +139,6 @@ pub fn try_update_config( if let Some(owner) = owner { state.owner = owner; } - if let Some(silk) = silk { - state.silk = silk; - } if let Some(oracle) = oracle { state.oracle = oracle; } @@ -154,7 +148,7 @@ pub fn try_update_config( Ok(HandleResponse { messages: vec![], log: vec![], - data: Some( to_binary( &HandleAnswer::UpdateAsset { + data: Some( to_binary( &HandleAnswer::UpdateConfig { status: ResponseStatus::Success } )? ) }) } @@ -162,96 +156,39 @@ pub fn try_update_config( pub fn try_register_asset( deps: &mut Extern, env: &Env, + name: Option, contract: Contract, - burned_amount: Option + burnable: Option, + total_burned: Option, ) -> StdResult { - if !authorized(deps, &env, AllowedAccess::Admin)? { - return Err(StdError::Unauthorized { backtrace: None }); - } - - let contract_str = contract.address.to_string(); - let mut assets = assets_w(&mut deps.storage); - let mut messages = vec![]; - // Check if asset already exists - match assets.may_load(contract_str.as_bytes())? { - Some(_) => return Err(StdError::generic_err("Asset already exists")), - - None => { - // Add the new asset - assets.save(contract_str.as_bytes(), &BurnableAsset { - contract: contract.clone(), - burned_tokens: match burned_amount { - None => { Uint128(0) } - Some(amount) => { amount } - }, - })?; - // Add asset to list - asset_list(&mut deps.storage).update(|mut state| { - state.push(contract_str); - Ok(state) - })?; - // Register contract in asset - let register_msg = register_receive(env, contract.address, contract.code_hash)?; - messages.push(register_msg); + let asset = SupportedAsset { + name: match name { + None => { token_info_query(&deps.querier, 1, contract.code_hash.clone(), contract.address.clone())?.symbol } + Some(x) => x, + }, + contract, + burnable: match burnable { + None => false, + Some(x) => x + }, + total_burned: match total_burned { + None => Uint128(0), + Some(amount) => amount, } - } - - Ok(HandleResponse { - messages, - log: vec![], - data: Some( to_binary( &HandleAnswer::RegisterAsset { - status: ResponseStatus::Success } )? ) - }) -} + }; -pub fn try_update_asset( - deps: &mut Extern, - env: Env, - asset: HumanAddr, - contract: Contract, -) -> StdResult { if !authorized(deps, &env, AllowedAccess::Admin)? { return Err(StdError::Unauthorized { backtrace: None }); } - let asset_str = asset.to_string(); - let mut assets = assets_w(&mut deps.storage); let mut messages = vec![]; - - // Check if asset already exists - match assets.may_load(asset_str.as_bytes())? { - Some(loaded_asset) => { - // Remove the old asset - assets.remove(asset_str.as_bytes()); - // Add the new asset - assets.save(contract.address.to_string().as_bytes(), &BurnableAsset { - contract: contract.clone(), - burned_tokens: loaded_asset.burned_tokens - })?; - // Remove old asset from list - asset_list(&mut deps.storage).update(|mut state| { - for (i, asset) in state.iter().enumerate() { - if asset == &asset_str { - state.remove(i); - state.push(asset_str.clone()); - break; - } - } - Ok(state) - })?; - // Register contract in asset - let register_msg = register_receive(&env, contract.address, contract.code_hash)?; - messages.push(register_msg) - }, - - None => return Err(StdError::NotFound { kind: asset_str, backtrace: None }), - } + messages.push(save_asset(deps, env, asset)?); Ok(HandleResponse { messages, log: vec![], - data: Some( to_binary( &HandleAnswer::UpdateAsset { + data: Some( to_binary( &HandleAnswer::RegisterAsset { status: ResponseStatus::Success } )? ) }) } @@ -262,62 +199,86 @@ pub fn try_burn( _sender: HumanAddr, from: HumanAddr, amount: Uint128, - _msg: Option + msg: Option ) -> StdResult { if !authorized(deps, &env, AllowedAccess::User)? { return Err(StdError::Unauthorized { backtrace: None }); } - - // Check that the asset is supported - let mut assets = assets_w(&mut deps.storage); - let mut callback_code_hash: String = "".to_string(); - - // Check if asset already exists - match assets.may_load(env.message.sender.to_string().as_bytes())? { - Some(_) => { - assets.update(env.message.sender.to_string().as_bytes(), |item| { - let mut asset: BurnableAsset = item.unwrap(); - callback_code_hash = asset.contract.code_hash.clone(); - asset.burned_tokens += amount; - Ok(asset) - })?; - }, + let mut messages = vec![]; + + // Setup msgs + let msgs: SnipMsgHook = match msg { + Some(x) => from_binary(&x)?, + None => return Err(StdError::generic_err("data cannot be empty")), + }; + // Check that the assets are supported + let assets = assets_r(&deps.storage); + let burning_asset = match assets.may_load(env.message.sender.to_string().as_bytes())? { + Some(asset) => asset, None => return Err(StdError::NotFound { kind: env.message.sender.to_string(), backtrace: None }), - } + }; + let minting_asset = match assets.may_load(msgs.to_mint.to_string().as_bytes())? { + Some(asset) => asset, + None => return Err(StdError::NotFound { kind: msgs.to_mint.to_string(), backtrace: None }), + }; - // 1.6 = 1_600_000_000_000_000_000 - // 1.6 SCRT = 1_600_000 uSCRT = 1_600_000_000_000_000_000 - // 1.6 * 1.6 = 2.56 - // 2_560_000_000_000_000_000_00 + // Check that requested snip20 is supported and mint address is inside the mintable array + let mintable = minters_query(&deps.querier, 1, + minting_asset.contract.code_hash.clone(), + minting_asset.contract.address.clone())?.minters; - // TODO: make this a function that way it can be tested + if !mintable.contains(&env.contract.address) { + return Err(StdError::generic_err("Asset does allow mint contract to mint")) + } - // Returned value is x * 10**18 - let token_value = Uint128(call_oracle(deps)?.into()); + // Query prices + let in_price = call_oracle(&deps, burning_asset.name)?; + let target_price = call_oracle(&deps, minting_asset.name)?; + // Get asset decimals // Load the decimal information for both coins - let config = config_read(&deps.storage).load()?; - let send_decimals = token_info_query(&deps.querier, 1, callback_code_hash, - env.message.sender)?.decimals as u32; - let silk_decimals = token_info_query(&deps.querier, 1, - config.silk.code_hash, - config.silk.address)?.decimals as u32; - - // ( ( token_value * 10**18 ) * ( amount * 10**send_decimals ) ) / ( 10**(18 - ( send_decimals - silk-decimals ) ) ) - // This will calculate the total mind value - let value_to_mint = calculate_mint(token_value, amount, send_decimals, silk_decimals); - let mut messages = vec![]; + let in_decimals = token_info_query(&deps.querier, 1, + burning_asset.contract.code_hash.clone(), + burning_asset.contract.address.clone())?.decimals as u32; + let target_decimals = token_info_query(&deps.querier, 1, + minting_asset.contract.code_hash.clone(), + minting_asset.contract.address.clone())?.decimals as u32; + + // Calculate value to mint + let amount_to_mint = calculate_mint(in_price, target_price, amount, in_decimals, target_decimals); + + // If minimum amount is greater then ignore the process + if msgs.minimum_expected_amount > amount_to_mint { + return Err(StdError::generic_err("did not exceed expected amount")) + } - let mint_msg = mint_silk(deps, from, value_to_mint)?; - messages.push(mint_msg); + // if burnable then burn if not ignore + if burning_asset.burnable { + messages.push(burn_msg(amount, None, 256, + burning_asset.contract.code_hash, + burning_asset.contract.address)?); + } + + // Set burned amount + let mut mut_assets = assets_w(&mut deps.storage); + mut_assets.update(env.message.sender.to_string().as_bytes(), |item| { + let mut asset: SupportedAsset = item.unwrap(); + asset.total_burned += amount; + Ok(asset) + })?; + + // Mint + messages.push(mint_msg(from, amount_to_mint, None, 256, + minting_asset.contract.code_hash, + minting_asset.contract.address)?); Ok(HandleResponse { messages, log: vec![], data: Some( to_binary( &HandleAnswer::Burn { status: ResponseStatus::Success, - mint_amount: value_to_mint + mint_amount: amount_to_mint } )? ), }) } @@ -350,61 +311,88 @@ fn authorized( return Ok(true) } -fn calculate_mint(x: Uint128, y: Uint128, a: u32, b: u32) -> Uint128 { - // x = value * 10**18 - // y = value * 10**a - // ( x * y ) / ( 10**(18 - ( a - b ) ) ) - let exponent = (18 as i32 + a as i32 - b as i32) as u32; - x.multiply_ratio(y, 10u128.pow(exponent)) -} - fn register_receive ( env: &Env, - contract: HumanAddr, - code_hash: String, + contract: Contract ) -> StdResult { let cosmos_msg = register_receive_msg( env.contract_code_hash.clone(), None, 256, - code_hash, - contract, + contract.code_hash, + contract.address, ); cosmos_msg } -fn mint_silk( - deps: &Extern, - sender: HumanAddr, - amount: Uint128, +fn calculate_mint(in_price: Uint128, target_price: Uint128, in_amount: Uint128, in_decimals: u32, target_decimals: u32) -> Uint128 { + // Math must only be made in integers + // in_decimals = x + // target_decimals = y + // in_price = p1 * 10^18 + // target_price = p2 * 10^18 + // in_amount = a1 * 10^x + // return = a2 * 10^y + + // (a1 * 10^x) * (p1 * 10^18) = (a2 * 10^y) * (p2 * 10^18) + + // (p1 * 10^18) + // (a1 * 10^x) * -------------- = (a2 * 10^y) + // (p2 * 10^18) + + let in_total = in_amount.multiply_ratio(in_price, target_price); + + // in_total * 10^(y - x) = (a2 * 10^y) + let difference: i32 = target_decimals as i32 - in_decimals as i32; + + // To avoid a mess of different types doing math + if difference < 0 { + in_total.multiply_ratio(1u128, 10u128.pow(u32::try_from(difference.abs()).unwrap())) + } + else if difference > 0 { + Uint128(in_total.u128() * 10u128.pow(u32::try_from(difference).unwrap())) + } + else { + in_total + } +} + +fn save_asset ( + deps: &mut Extern, + env: &Env, + asset: SupportedAsset, ) -> StdResult { - let config = config_read(&deps.storage).load()?; - let cosmos_msg = mint_msg( - sender, - amount, - None, - 256, - config.silk.code_hash, - config.silk.address, - ); + let mut assets = assets_w(&mut deps.storage); - cosmos_msg + // Save the asset + let key = asset.contract.address.to_string(); + assets.save(key.as_bytes(), &asset)?; + + // Add the asset to list + asset_list(&mut deps.storage).update(|mut state| { + state.push(key); + Ok(state) + })?; + + // Register contract in asset + let register_msg = register_receive(env, asset.contract)?; + + Ok(register_msg) } fn call_oracle( - deps: &mut Extern, + deps: &Extern, + symbol: String, ) -> StdResult { let block_size = 1; //update this later let config = config_read(&deps.storage).load()?; - let query_msg = shade_protocol::oracle::QueryMsg::GetScrtPrice {}; - let answer: shade_protocol::oracle::PriceResponse = query_msg.query(&deps.querier, block_size, + let query_msg = GetPrice { symbol }; + let answer: ReferenceData = query_msg.query(&deps.querier, block_size, config.oracle.code_hash, config.oracle.address)?; - - let value = answer.price; - Ok(value) + Ok(answer.rate) } pub fn query( @@ -450,11 +438,10 @@ mod tests { } } - fn dummy_init(admin: String, silk: Contract, oracle: Contract) -> Extern { + fn dummy_init(admin: String, oracle: Contract) -> Extern { let mut deps = mock_dependencies(20, &[]); let msg = InitMsg { admin: None, - silk, oracle, initial_assets: None }; @@ -467,46 +454,46 @@ mod tests { #[test] fn proper_initialization() { let mut deps = mock_dependencies(20, &[]); + let some_contract = create_contract("contract", "hash"); let msg = InitMsg { admin: None, - silk: create_contract("", ""), oracle: create_contract("", ""), - initial_assets: None + initial_assets: Some(vec![SupportedAsset{ + name: "some_asset".to_string(), + contract: some_contract, + burnable: false, + total_burned: Uint128(0) + }]) }; let env = mock_env("creator", &coins(1000, "earth")); // we can just call .unwrap() to assert this was a success let res = init(&mut deps, env, msg).unwrap(); - assert_eq!(0, res.messages.len()); + // We should receive two registered messages + assert_eq!(1, res.messages.len()); } #[test] fn config_update() { - let silk_contract = create_contract("silk_contract", "silk_hash"); let oracle_contract = create_contract("oracle_contract", "oracle_hash"); - let mut deps = dummy_init("admin".to_string(), silk_contract, oracle_contract); + let mut deps = dummy_init("admin".to_string(), oracle_contract); // Check config is properly updated let res = query(&deps, QueryMsg::GetConfig {}).unwrap(); let value: QueryAnswer = from_binary(&res).unwrap(); - let silk_contract = create_contract("silk_contract", "silk_hash"); let oracle_contract = create_contract("oracle_contract", "oracle_hash"); match value { QueryAnswer::Config { config } => { - assert_eq!(config.silk, silk_contract); assert_eq!(config.oracle, oracle_contract); - } _ => { panic!("Received wrong answer") } } // Update config let user_env = mock_env("admin", &coins(1000, "earth")); - let new_silk_contract = create_contract("new_silk_contract", "silk_hash"); let new_oracle_contract = create_contract("new_oracle_contract", "oracle_hash"); let msg = HandleMsg::UpdateConfig { owner: None, - silk: Option::from(new_silk_contract), oracle: Option::from(new_oracle_contract), }; let _res = handle(&mut deps, user_env, msg); @@ -514,11 +501,9 @@ mod tests { // Check config is properly updated let res = query(&deps, QueryMsg::GetConfig {}).unwrap(); let value: QueryAnswer = from_binary(&res).unwrap(); - let new_silk_contract = create_contract("new_silk_contract", "silk_hash"); let new_oracle_contract = create_contract("new_oracle_contract", "oracle_hash"); match value { QueryAnswer::Config { config } => { - assert_eq!(config.silk, new_silk_contract); assert_eq!(config.oracle, new_oracle_contract); } @@ -530,14 +515,16 @@ mod tests { #[test] fn user_register_asset() { let mut deps = dummy_init("admin".to_string(), - create_contract("", ""), create_contract("", "")); // User should not be allowed to add an item let user_env = mock_env("user", &coins(1000, "earth")); let dummy_contract = create_contract("some_contract", "some_hash"); let msg = HandleMsg::RegisterAsset { + name: Some("asset".to_string()), contract: dummy_contract, + burnable: None, + total_burned: None }; let res = handle(&mut deps, user_env, msg); match res { @@ -550,21 +537,23 @@ mod tests { let value: QueryAnswer = from_binary(&res).unwrap(); match value { QueryAnswer::SupportedAssets { assets } => { assert_eq!(0, assets.len()) } - _ => { panic!("Received wrong answer") } + _ => { panic!("Expected empty array") } } } #[test] fn admin_register_asset() { let mut deps = dummy_init("admin".to_string(), - create_contract("", ""), create_contract("", "")); // Admin should be allowed to add an item let env = mock_env("admin", &coins(1000, "earth")); let dummy_contract = create_contract("some_contract", "some_hash"); let msg = HandleMsg::RegisterAsset { + name: Some("asset".to_string()), contract: dummy_contract, + burnable: None, + total_burned: None }; let _res = handle(&mut deps, env, msg).unwrap(); @@ -577,215 +566,77 @@ mod tests { } } - #[test] - fn duplicate_register_asset() { - let mut deps = dummy_init("admin".to_string(), - create_contract("", ""), - create_contract("", "")); - - let env = mock_env("admin", &coins(1000, "earth")); - let dummy_contract = create_contract("some_contract", "some_hash"); - let msg = HandleMsg::RegisterAsset { - contract: dummy_contract, - }; - let _res = handle(&mut deps, env, msg).unwrap(); - - // Should not be allowed to add an existing asset - let env = mock_env("admin", &coins(1000, "earth")); - let dummy_contract = create_contract("some_contract", "other_hash"); - let msg = HandleMsg::RegisterAsset { - contract: dummy_contract, - }; - let res = handle(&mut deps, env, msg); - match res { - Err(StdError::GenericErr { .. }) => {} - _ => panic!("Must return not found error"), - }; - } - - #[test] - fn user_update_asset() { - let mut deps = dummy_init("admin".to_string(), - create_contract("", ""), - create_contract("", "")); - - // Add a supported asset - let env = mock_env("admin", &coins(1000, "earth")); - let dummy_contract = create_contract("some_contract", "some_hash"); - let msg = HandleMsg::RegisterAsset { - contract: dummy_contract, - }; - let _res = handle(&mut deps, env, msg).unwrap(); - - // users should not be allowed to update assets - let user_env = mock_env("user", &coins(1000, "earth")); - let dummy_contract = create_contract("some_contract", "some_hash"); - let new_dummy_contract = create_contract("some_other_contract", "some_hash"); - let msg = HandleMsg::UpdateAsset { - asset: dummy_contract.address, - contract: new_dummy_contract, - }; - let res = handle(&mut deps, user_env, msg); - match res { - Err(StdError::Unauthorized { .. }) => {} - _ => panic!("Must return unauthorized error"), - }; - } - #[test] fn admin_update_asset() { let mut deps = dummy_init("admin".to_string(), - create_contract("", ""), create_contract("", "")); // Add a supported asset let env = mock_env("admin", &coins(1000, "earth")); let dummy_contract = create_contract("some_contract", "some_hash"); let msg = HandleMsg::RegisterAsset { + name: Some("old_asset".to_string()), contract: dummy_contract, + burnable: None, + total_burned: None }; let _res = handle(&mut deps, env, msg).unwrap(); // admins can update assets let env = mock_env("admin", &coins(1000, "earth")); let dummy_contract = create_contract("some_contract", "some_hash"); - let new_dummy_contract = create_contract("some_other_contract", "some_hash"); - let msg = HandleMsg::UpdateAsset { - asset: dummy_contract.address, - contract: new_dummy_contract, + let msg = HandleMsg::RegisterAsset { + name: Some("new_asset".to_string()), + contract: dummy_contract, + burnable: None, + total_burned: None }; let _res = handle(&mut deps, env, msg).unwrap(); // Response should be new dummy contract - let res = query(&deps, QueryMsg::GetAsset { contract: "some_other_contract".to_string() }).unwrap(); + let res = query(&deps, QueryMsg::GetAsset { contract: "some_contract".to_string() }).unwrap(); let value: QueryAnswer = from_binary(&res).unwrap(); match value { - QueryAnswer::Asset { asset } => { assert_eq!("some_other_contract".to_string(), asset.contract.address.to_string()) } + QueryAnswer::Asset { asset } => { assert_eq!("new_asset".to_string(), asset.name) } _ => { panic!("Received wrong answer") } }; } - #[test] - fn nonexisting_update_asset() { - let mut deps = dummy_init("admin".to_string(), - create_contract("", ""), - create_contract("", "")); - - // Add a supported asset - let env = mock_env("admin", &coins(1000, "earth")); - let dummy_contract = create_contract("some_contract", "some_hash"); - let msg = HandleMsg::RegisterAsset { - contract: dummy_contract, - }; - let _res = handle(&mut deps, env, msg).unwrap(); - - // Should now be able to update non existing asset - let env = mock_env("admin", &coins(1000, "earth")); - let bad_dummy_contract = create_contract("some_non_existing_contract", "some_hash"); - let new_dummy_contract = create_contract("some_other_contract", "some_hash"); - let msg = HandleMsg::UpdateAsset { - asset: bad_dummy_contract.address, - contract: new_dummy_contract, - }; - let res = handle(&mut deps, env, msg); - match res { - Err(StdError::NotFound { .. }) => {} - _ => panic!("Must return not found error"), - } - } - - #[test] - fn receiving_an_asset() { - let mut deps = dummy_init("admin".to_string(), - create_contract("", ""), - create_contract("", "")); - - // Add a supported asset - let env = mock_env("admin", &coins(1000, "earth")); - let dummy_contract = create_contract("some_contract", "some_hash"); - let msg = HandleMsg::RegisterAsset { - contract: dummy_contract, - }; - let _res = handle(&mut deps, env, msg).unwrap(); - - // Contract tries to send funds - let env = mock_env("some_contract", &coins(1000, "earth")); - let dummy_contract = create_contract("some_owner", "some_hash"); - - let msg = HandleMsg::Receive { - sender: dummy_contract.address, - from: Default::default(), - amount: Uint128(100), - msg: None, - memo: None - }; - - let res = handle(&mut deps, env, msg); - match res { - Err(err) => { - match err { - StdError::NotFound { .. } => {panic!("Not found");} - StdError::Unauthorized { .. } => {panic!("Unauthorized");} - _ => {//panic!("Must not return error"); - } - } - } - _ => {} - } - } - - #[test] - fn receiving_an_asset_from_non_supported_asset() { - let mut deps = dummy_init("admin".to_string(), - create_contract("", ""), - create_contract("", "")); - - // Add a supported asset - let env = mock_env("admin", &coins(1000, "earth")); - let dummy_contract = create_contract("some_contract", "some_hash"); - let msg = HandleMsg::RegisterAsset { - contract: dummy_contract, - }; - let _res = handle(&mut deps, env, msg).unwrap(); - - // Contract tries to send funds - let env = mock_env("some_other_contract", &coins(1000, "earth")); - let dummy_contract = create_contract("some_owner", "some_hash"); - let msg = HandleMsg::Receive { - sender: dummy_contract.address, - from: Default::default(), - amount: Uint128(100), - msg: None, - memo: None - }; - let res = handle(&mut deps, env, msg); - match res { - Err(StdError::NotFound { .. }) => {} - _ => {panic!("Must return not found error")}, - } - } - #[test] fn mint_algorithm_simple() { // In this example the "sent" value is 1 with 6 decimal places // The mint value will be 1 with 3 decimal places let price = Uint128(1_000_000_000_000_000_000); - let sent_value = Uint128(1_000_000); + let in_amount = Uint128(1_000_000); let expected_value = Uint128(1_000); - let value = calculate_mint(price, sent_value, 6, 3); + let value = calculate_mint(price, price, in_amount, 6, 3); assert_eq!(value, expected_value); } #[test] - fn mint_algorithm_complex() { + fn mint_algorithm_complex_1() { // In this example the "sent" value is 1.8 with 6 decimal places // The mint value will be 3.6 with 12 decimal places - let price = Uint128(2_000_000_000_000_000_000); - let sent_value = Uint128(1_800_000); + let in_price = Uint128(2_000_000_000_000_000_000); + let target_price = Uint128(1_000_000_000_000_000_000); + let in_amount = Uint128(1_800_000); let expected_value = Uint128(3_600_000_000_000); - let value = calculate_mint(price, sent_value, 6, 12); + let value = calculate_mint(in_price, target_price, in_amount, 6, 12); assert_eq!(value, expected_value); } -} + + #[test] + fn mint_algorithm_complex_2() { + // In amount is 50.000 valued at 20 + // target price is 100$ with 6 decimals + let in_price = Uint128(20_000_000_000_000_000_000); + let target_price = Uint128(100_000_000_000_000_000_000); + let in_amount = Uint128(50_000); + let expected_value = Uint128(10_000_000); + let value = calculate_mint(in_price, target_price, in_amount, 3, 6); + + assert_eq!(value, expected_value); + } +} \ No newline at end of file diff --git a/contracts/mint/src/state.rs b/contracts/mint/src/state.rs index c00b9cd48..cecf77216 100644 --- a/contracts/mint/src/state.rs +++ b/contracts/mint/src/state.rs @@ -1,7 +1,7 @@ use cosmwasm_std::{Storage}; use cosmwasm_storage::{singleton, singleton_read, ReadonlySingleton, Singleton, bucket, Bucket, bucket_read, ReadonlyBucket}; use shade_protocol::{ - mint::{MintConfig, BurnableAsset}, + mint::{MintConfig, SupportedAsset}, }; pub static CONFIG_KEY: &[u8] = b"config"; @@ -25,10 +25,10 @@ pub fn asset_list_read(storage: &S) -> ReadonlySingleton(storage: & S) -> ReadonlyBucket { +pub fn assets_r(storage: & S) -> ReadonlyBucket { bucket_read(ASSET_KEY, storage) } -pub fn assets_w(storage: &mut S) -> Bucket { +pub fn assets_w(storage: &mut S) -> Bucket { bucket(ASSET_KEY, storage) } \ No newline at end of file diff --git a/contracts/mock_band/.cargo/config b/contracts/mock_band/.cargo/config new file mode 100644 index 000000000..882fe08f6 --- /dev/null +++ b/contracts/mock_band/.cargo/config @@ -0,0 +1,5 @@ +[alias] +wasm = "build --release --target wasm32-unknown-unknown" +unit-test = "test --lib --features backtraces" +integration-test = "test --test integration" +schema = "run --example schema" diff --git a/contracts/mock_band/.circleci/config.yml b/contracts/mock_band/.circleci/config.yml new file mode 100644 index 000000000..127e1ae7d --- /dev/null +++ b/contracts/mock_band/.circleci/config.yml @@ -0,0 +1,52 @@ +version: 2.1 + +jobs: + build: + docker: + - image: rust:1.43.1 + steps: + - checkout + - run: + name: Version information + command: rustc --version; cargo --version; rustup --version + - restore_cache: + keys: + - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} + - run: + name: Add wasm32 target + command: rustup target add wasm32-unknown-unknown + - run: + name: Build + command: cargo wasm --locked + - run: + name: Unit tests + env: RUST_BACKTRACE=1 + command: cargo unit-test --locked + - run: + name: Integration tests + command: cargo integration-test --locked + - run: + name: Format source code + command: cargo fmt + - run: + name: Build and run schema generator + command: cargo schema --locked + - run: + name: Ensure checked-in source code and schemas are up-to-date + command: | + CHANGES_IN_REPO=$(git status --porcelain) + if [[ -n "$CHANGES_IN_REPO" ]]; then + echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" + git status && git --no-pager diff + exit 1 + fi + - save_cache: + paths: + - /usr/local/cargo/registry + - target/debug/.fingerprint + - target/debug/build + - target/debug/deps + - target/wasm32-unknown-unknown/release/.fingerprint + - target/wasm32-unknown-unknown/release/build + - target/wasm32-unknown-unknown/release/deps + key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/contracts/mock_band/Cargo.toml b/contracts/mock_band/Cargo.toml new file mode 100644 index 000000000..793b6daab --- /dev/null +++ b/contracts/mock_band/Cargo.toml @@ -0,0 +1,47 @@ +[package] +name = "mock_band" +version = "0.1.0" +authors = ["Jack Swenson "] +edition = "2018" + +exclude = [ + # 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. + "contract.wasm", + "hash.txt", +] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib", "rlib"] + +[profile.release] +opt-level = 3 +debug = false +rpath = false +lto = true +debug-assertions = false +codegen-units = 1 +panic = 'abort' +incremental = false +overflow-checks = true + +[features] +default = [] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +debug-print = ["cosmwasm-std/debug-print"] + +[dependencies] +bincode = "1.3.1" +cosmwasm-schema = { git = "https://github.com/enigmampc/SecretNetwork", tag = "v1.0.4-debug-print" } +cosmwasm-std = { git = "https://github.com/enigmampc/SecretNetwork", tag = "v1.0.4-debug-print" } +cosmwasm-storage = { git = "https://github.com/enigmampc/SecretNetwork", tag = "v1.0.4-debug-print" } +secret-toolkit = { git = "https://github.com/enigmampc/secret-toolkit", branch = "debug-print"} +shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol" } +schemars = "0.7" +serde = { version = "1.0.103", default-features = false, features = ["derive"] } +snafu = { version = "0.6.3" } + +mockall = "0.10.2" diff --git a/contracts/mock_band/Makefile b/contracts/mock_band/Makefile new file mode 100644 index 000000000..2493c22f4 --- /dev/null +++ b/contracts/mock_band/Makefile @@ -0,0 +1,68 @@ +.PHONY: check +check: + cargo check + +.PHONY: clippy +clippy: + cargo clippy + +PHONY: test +test: unit-test + +.PHONY: unit-test +unit-test: + cargo test + +# This is a local build with debug-prints activated. Debug prints only show up +# in the local development chain (see the `start-server` command below) +# and mainnet won't accept contracts built with the feature enabled. +.PHONY: build _build +build: _build compress-wasm +_build: + RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown --features="debug-print" + +# This is a build suitable for uploading to mainnet. +# Calls to `debug_print` get removed by the compiler. +.PHONY: build-mainnet _build-mainnet +build-mainnet: _build-mainnet compress-wasm +_build-mainnet: + RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown + +# like build-mainnet, but slower and more deterministic +.PHONY: build-mainnet-reproducible +build-mainnet-reproducible: + docker run --rm -v "$$(pwd)":/contract \ + --mount type=volume,source="$$(basename "$$(pwd)")_cache",target=/contract/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + enigmampc/secret-contract-optimizer:1.0.3 + +.PHONY: compress-wasm +compress-wasm: + cp ./target/wasm32-unknown-unknown/release/*.wasm ./contract.wasm + @## The following line is not necessary, may work only on linux (extra size optimization) + @# wasm-opt -Os ./contract.wasm -o ./contract.wasm + cat ./contract.wasm | gzip -9 > ./contract.wasm.gz + +.PHONY: schema +schema: + cargo run --example schema + +# Run local development chain with four funded accounts (named a, b, c, and d) +.PHONY: start-server +start-server: # CTRL+C to stop + docker run -it --rm \ + -p 26657:26657 -p 26656:26656 -p 1317:1317 \ + -v $$(pwd):/root/code \ + --name secretdev enigmampc/secret-network-sw-dev:v1.0.4-3 + +# This relies on running `start-server` in another console +# You can run other commands on the secretcli inside the dev image +# by using `docker exec secretdev secretcli`. +.PHONY: store-contract-local +store-contract-local: + docker exec secretdev secretcli tx compute store -y --from a --gas 1000000 /root/code/contract.wasm.gz + +.PHONY: clean +clean: + cargo clean + -rm -f ./contract.wasm ./contract.wasm.gz diff --git a/contracts/mock_band/README.md b/contracts/mock_band/README.md new file mode 100644 index 000000000..8fad67554 --- /dev/null +++ b/contracts/mock_band/README.md @@ -0,0 +1,21 @@ +# Mock Band Contract +* [Introduction](#Introduction) +* [Sections](#Sections) + * [User](#User) + * Queries + * [GetReferenceData](#GetReferenceData) +# Introduction +The Mocked Band contract is used to test locally when there is no official band contract available + +### Queries + +#### GetReferenceData +Get a hardcoded sample from an ETH query for testing locally +##### Response +```json +{ + "rate": "3119154999999000000000", + "last_updated_base": 1628548483, + "last_updated_quote": 3377610 +} +``` diff --git a/contracts/mock_band/rustfmt.toml b/contracts/mock_band/rustfmt.toml new file mode 100644 index 000000000..11a85e6a9 --- /dev/null +++ b/contracts/mock_band/rustfmt.toml @@ -0,0 +1,15 @@ +# stable +newline_style = "unix" +hard_tabs = false +tab_spaces = 4 + +# unstable... should we require `rustup run nightly cargo fmt` ? +# or just update the style guide when they are stable? +#fn_single_line = true +#format_code_in_doc_comments = true +#overflow_delimited_expr = true +#reorder_impl_items = true +#struct_field_align_threshold = 20 +#struct_lit_single_line = true +#report_todo = "Always" + diff --git a/contracts/mock_band/src/contract.rs b/contracts/mock_band/src/contract.rs new file mode 100644 index 000000000..f2993ff27 --- /dev/null +++ b/contracts/mock_band/src/contract.rs @@ -0,0 +1,55 @@ +use cosmwasm_std::{ + to_binary, Api, Binary, + Env, Extern, HandleResponse, InitResponse, + Querier, StdResult, StdError, Storage, Uint128, +}; +use serde::{Deserialize, Serialize}; +use schemars::JsonSchema; +use shade_protocol::oracle::ReferenceData; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct InitMsg { } + +pub fn init( + _deps: &mut Extern, + _env: Env, + _msg: InitMsg, +) -> StdResult { + Ok(InitResponse::default()) +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum HandleMsg { } + +pub fn handle( + _deps: &mut Extern, + _env: Env, + _msg: HandleMsg, +) -> StdResult { + + Err(StdError::GenericErr { msg: "Not Implemented".to_string(), backtrace: None}) +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum QueryMsg { + GetReferenceData { + base_symbol: String, + quote_symbol: String, + }, +} +pub fn query( + _deps: &Extern, + msg: QueryMsg, +) -> StdResult { + match msg { + QueryMsg::GetReferenceData { base_symbol: _, quote_symbol: _ } => + to_binary(&ReferenceData { + // data from ETH + rate: Uint128(1_000_000_000_000_000_000), + last_updated_base: 1628544285u64, + last_updated_quote: 3377610u64 + }), + } +} diff --git a/contracts/mock_band/src/lib.rs b/contracts/mock_band/src/lib.rs new file mode 100644 index 000000000..4d128dcb3 --- /dev/null +++ b/contracts/mock_band/src/lib.rs @@ -0,0 +1,38 @@ +pub mod contract; + +#[cfg(target_arch = "wasm32")] +mod wasm { + use super::contract; + use cosmwasm_std::{ + do_handle, do_init, do_query, ExternalApi, ExternalQuerier, ExternalStorage, + }; + + #[no_mangle] + extern "C" fn init(env_ptr: u32, msg_ptr: u32) -> u32 { + do_init( + &contract::init::, + env_ptr, + msg_ptr, + ) + } + + #[no_mangle] + extern "C" fn handle(env_ptr: u32, msg_ptr: u32) -> u32 { + do_handle( + &contract::handle::, + env_ptr, + msg_ptr, + ) + } + + #[no_mangle] + extern "C" fn query(msg_ptr: u32) -> u32 { + do_query( + &contract::query::, + msg_ptr, + ) + } + + // Other C externs like cosmwasm_vm_version_1, allocate, deallocate are available + // automatically because we `use cosmwasm_std`. +} diff --git a/contracts/oracle/Cargo.toml b/contracts/oracle/Cargo.toml index 5eb777911..8122b2dad 100644 --- a/contracts/oracle/Cargo.toml +++ b/contracts/oracle/Cargo.toml @@ -34,6 +34,7 @@ backtraces = ["cosmwasm-std/backtraces"] debug-print = ["cosmwasm-std/debug-print"] [dependencies] +bincode = "1.3.1" cosmwasm-schema = { git = "https://github.com/enigmampc/SecretNetwork", tag = "v1.0.4-debug-print" } cosmwasm-std = { git = "https://github.com/enigmampc/SecretNetwork", tag = "v1.0.4-debug-print" } cosmwasm-storage = { git = "https://github.com/enigmampc/SecretNetwork", tag = "v1.0.4-debug-print" } @@ -42,3 +43,5 @@ shade-protocol = { version = "0.1.0", path = "../../packages/shade_protocol" } schemars = "0.7" serde = { version = "1.0.103", default-features = false, features = ["derive"] } snafu = { version = "0.6.3" } + +mockall = "0.10.2" diff --git a/contracts/oracle/README.md b/contracts/oracle/README.md index 49cfadadb..c28631421 100644 --- a/contracts/oracle/README.md +++ b/contracts/oracle/README.md @@ -15,17 +15,61 @@ The oracle contract is used to query the price of different currencies |Name |Type |Description | optional | |----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| |owner | string | New contract owner; SHOULD be a valid bech32 address, but contracts may use a different naming scheme as well | yes | +|band | Contract | Band protocol contract | no | ##User +### Messages + +#### UpdateConfig +Updates the given values +##### Request +|Name |Type |Description | optional | +|----------|----------|-------------------------------------------------------------------------------------------------------------------|----------| +|owner | string | New contract owner; SHOULD be a valid bech32 address, but contracts may use a different naming scheme as well | yes | +|silk | Contract | Silk contract | no | +|oracle | Contract | Oracle contract | no | +##### Response +```json +{ + "update_config": { + "status": "success" + } +} +``` + ### Queries -#### GetScrtPrice -Get SCRT's price according to band protocol. +#### GetPrice +Get asset price according to band protocol. +##### Request +|Name |Type |Description | optional | +|------------|--------|-----------------------------------------------------------------------------------------------------------------------|----------| +|symbol | string | asset abbreviation e.g. BTC/ETH/SCRT; | no | +##### Response +```json +{ + { + "rate": "1470000000000000000", + "last_updated_base": 1628569146, + "last_updated_quote": 3377610 + } +} +``` +#### GetReferenceData +Get base asset price relative to quote asset according to band protocol. +##### Request +|Name |Type |Description | optional | +|------------|--------|-----------------------------------------------------------------------------------------------------------------------|----------| +|base_symbol | string | asset abbreviation e.g. BTC/ETH/SCRT; | no | +|quote_symbol| string | asset abbreviation e.g. BTC/ETH/SCRT; | no | ##### Response ```json { - "get_scrt_price": { + { + "rate": "1470000000000000000", + "last_updated_base": 1628569146, + "last_updated_quote": 3377610 } } -``` \ No newline at end of file +``` diff --git a/contracts/oracle/src/contract.rs b/contracts/oracle/src/contract.rs index f19b7a7c3..9f41bbb58 100644 --- a/contracts/oracle/src/contract.rs +++ b/contracts/oracle/src/contract.rs @@ -1,7 +1,18 @@ -use cosmwasm_std::{debug_print, to_binary, Api, Binary, Env, Extern, HandleResponse, InitResponse, Querier, StdResult, Storage, Uint128}; -use crate::state::{config}; +use cosmwasm_std::{ + debug_print, to_binary, Api, Binary, + Env, Extern, HandleResponse, InitResponse, + Querier, StdResult, StdError, Storage, Uint128, + HumanAddr, +}; +use crate::state::{config, config_read}; use shade_protocol::{ - oracle::{InitMsg, HandleMsg, QueryMsg, PriceResponse, OracleConfig}, + oracle::{ + InitMsg, HandleMsg, HandleAnswer, + QueryMsg, QueryAnswer, OracleConfig, ReferenceData + }, + asset::Contract, + msg_traits::Query, + generic_response::ResponseStatus, }; pub fn init( @@ -14,6 +25,7 @@ pub fn init( None => { env.message.sender.clone() } Some(admin) => { admin } }, + band: msg.band, }; config(&mut deps.storage).save(&state)?; @@ -24,26 +36,116 @@ pub fn init( } pub fn handle( - _deps: &mut Extern, - _env: Env, + deps: &mut Extern, + env: Env, msg: HandleMsg, ) -> StdResult { match msg { + HandleMsg::UpdateConfig { + owner, + band + } => try_update_config(deps, env, owner, band), } } +pub fn try_update_config( + deps: &mut Extern, + env: Env, + owner: Option, + band: Contract, +) -> StdResult { + if !authorized(deps, &env, AllowedAccess::Admin)? { + return Err(StdError::Unauthorized { backtrace: None }); + } + + // Save new info + let mut config = config(&mut deps.storage); + config.update(|mut state| { + if let Some(owner) = owner { + state.owner = owner; + } + state.band = band; + + Ok(state) + })?; + + Ok(HandleResponse { + messages: vec![], + log: vec![], + data: Some( to_binary( &HandleAnswer::UpdateConfig{ + status: ResponseStatus::Success } )? ) + }) +} + pub fn query( - _deps: &Extern, + deps: &Extern, msg: QueryMsg, ) -> StdResult { match msg { - QueryMsg::GetScrtPrice {} => to_binary(&query_silk_price()?), + QueryMsg::GetConfig {} => to_binary(&query_config(deps)?), + QueryMsg::GetReferenceData { base_symbol, quote_symbol } => + to_binary(&query_reference_data(deps, base_symbol, quote_symbol)?), + QueryMsg::GetPrice{ symbol } => + to_binary(&query_reference_data(deps, symbol, "USD".to_string())?), } } -fn query_silk_price<>() -> StdResult { - let price:Uint128 = Uint128(10u64.pow(18) as u128); - Ok(PriceResponse { price }) +fn query_config(deps: &Extern) -> StdResult { + Ok(QueryAnswer::Config { config: config_read(&deps.storage).load()? }) +} + +// cross-contract query +fn query_reference_data( + deps: &Extern, + base_symbol: String, + quote_symbol: String, +) -> StdResult { + + if base_symbol == "SHD" { + // this can read from the local storage + return Ok(ReferenceData { + //11.47 * 10^18 + rate: Uint128(1147 * 10u128.pow(16)), + last_updated_base: 0, + last_updated_quote: 0 + }); + } + + let config_read = config_read(&deps.storage).load()?; + + // If band oracle is not defined it will return a default value + Ok(QueryMsg::GetReferenceData { + base_symbol, + quote_symbol + }.query( + &deps.querier, + 1, //block_size + config_read.band.code_hash, + config_read.band.address)?) +} + +// Helper functions + +#[derive(PartialEq)] +pub enum AllowedAccess{ + Admin, + User, +} + +fn authorized( + deps: &Extern, + env: &Env, + access: AllowedAccess, +) -> StdResult { + let config = config_read(&deps.storage).load()?; + + if access == AllowedAccess::Admin { + // Check if admin + if env.message.sender != config.owner { + return Ok(false) + } + } + return Ok(true) } #[cfg(test)] @@ -51,11 +153,22 @@ mod tests { use super::*; use cosmwasm_std::testing::{mock_dependencies, mock_env, MockStorage, MockApi, MockQuerier}; use cosmwasm_std::{coins, from_binary}; + use shade_protocol::asset::Contract; + use mockall::{automock, predicate::*}; - fn dummy_init(admin: &str) -> Extern { + fn create_contract(address: &str, code_hash: &str) -> Contract { + let env = mock_env(address.to_string(), &[]); + return Contract{ + address: env.message.sender, + code_hash: code_hash.to_string() + } + } + + fn dummy_init(admin: &str, band: Contract) -> Extern { let mut deps = mock_dependencies(20, &[]); let msg = InitMsg { admin: None, + band: band, }; let env = mock_env(admin.to_string(), &coins(1000, "earth")); let _res = init(&mut deps, env, msg).unwrap(); @@ -67,7 +180,10 @@ mod tests { fn proper_initialization() { let mut deps = mock_dependencies(20, &[]); - let msg = InitMsg { admin: None }; + let msg = InitMsg { + admin: None, + band: create_contract("", ""), + }; let env = mock_env("creator", &coins(1000, "earth")); // we can just call .unwrap() to assert this was a success @@ -75,14 +191,32 @@ mod tests { assert_eq!(0, res.messages.len()); } + #[cfg_attr(test, automock)] + trait Query{ + fn query(&self, + _querier: &QueryMsg, + _block_size: usize, + _callback_code_hash: String, + _contract_addr: HumanAddr, + ) -> StdResult { + Ok(ReferenceData { + //11.47 * 10^18 + rate: Uint128(1147 * 10u128.pow(16)), + last_updated_base: 0, + last_updated_quote: 0 + }) + } + } + #[test] - fn query_price() { - let deps = dummy_init("admin"); - - // Query the price - let res = query(&deps, QueryMsg::GetScrtPrice {}).unwrap(); - let value: PriceResponse = from_binary(&res).unwrap(); - let expected_price = Uint128(10u64.pow(18) as u128); - assert_eq!(expected_price, value.price); + fn price_query() { + let mut deps = dummy_init(&"admin".to_string(), + create_contract("", "")); + let msg = QueryMsg::GetPrice{ + symbol: "SHD".to_string(), + }; + let res = query(&mut deps, msg).unwrap(); + let value: ReferenceData = from_binary(&res).unwrap(); + assert_eq!(value.rate, Uint128(1147 * 10u128.pow(16))) } } diff --git a/contracts/oracle/src/state.rs b/contracts/oracle/src/state.rs index d5ae1b5ab..7b66cc92a 100644 --- a/contracts/oracle/src/state.rs +++ b/contracts/oracle/src/state.rs @@ -1,8 +1,9 @@ use cosmwasm_std::{Storage}; -use cosmwasm_storage::{singleton, singleton_read, ReadonlySingleton, Singleton}; +use cosmwasm_storage::{singleton, singleton_read, ReadonlySingleton, Singleton, PrefixedStorage, ReadonlyPrefixedStorage}; use shade_protocol::oracle::OracleConfig; pub static CONFIG_KEY: &[u8] = b"config"; +pub static PRICE_KEY: &[u8] = b"price"; /* pub struct ReferenceData { @@ -19,3 +20,12 @@ pub fn config(storage: &mut S) -> Singleton { pub fn config_read(storage: &S) -> ReadonlySingleton { singleton_read(storage, CONFIG_KEY) } + +// price +pub fn price(storage: &mut S) -> PrefixedStorage { + PrefixedStorage::new(PRICE_KEY, storage) +} + +pub fn price_read(storage: &S) -> ReadonlyPrefixedStorage { + ReadonlyPrefixedStorage::new(PRICE_KEY, storage) +} diff --git a/packages/shade_protocol/src/asset.rs b/packages/shade_protocol/src/asset.rs index de5ea9bc1..1af7fb3d1 100644 --- a/packages/shade_protocol/src/asset.rs +++ b/packages/shade_protocol/src/asset.rs @@ -7,4 +7,4 @@ use cosmwasm_std::HumanAddr; pub struct Contract { pub address: HumanAddr, pub code_hash: String, -} \ No newline at end of file +} diff --git a/packages/shade_protocol/src/lib.rs b/packages/shade_protocol/src/lib.rs index 3a02c20b3..153133ccc 100644 --- a/packages/shade_protocol/src/lib.rs +++ b/packages/shade_protocol/src/lib.rs @@ -2,4 +2,4 @@ pub mod asset; pub mod mint; pub mod oracle; pub mod generic_response; -pub mod msg_traits; \ No newline at end of file +pub mod msg_traits; diff --git a/packages/shade_protocol/src/mint.rs b/packages/shade_protocol/src/mint.rs index 50cded17c..a988597e7 100644 --- a/packages/shade_protocol/src/mint.rs +++ b/packages/shade_protocol/src/mint.rs @@ -1,6 +1,6 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use cosmwasm_std::{HumanAddr, Uint128, Binary, CosmosMsg}; +use cosmwasm_std::{HumanAddr, Uint128, Binary}; use crate::asset::Contract; use crate::generic_response::ResponseStatus; use crate::msg_traits::{Init, Handle, Query}; @@ -8,24 +8,24 @@ use crate::msg_traits::{Init, Handle, Query}; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct MintConfig { pub owner: HumanAddr, - pub silk: Contract, pub oracle: Contract, pub activated: bool, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] -pub struct BurnableAsset { +pub struct SupportedAsset { + pub name: String, pub contract: Contract, - pub burned_tokens: Uint128, + pub burnable: bool, + pub total_burned: Uint128, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct InitMsg { pub admin: Option, - pub silk: Contract, pub oracle: Contract, - pub initial_assets: Option>, + pub initial_assets: Option>, } impl Init<'_> for InitMsg {} @@ -40,27 +40,32 @@ pub enum HandleMsg { }, UpdateConfig { owner: Option, - silk: Option, oracle: Option, }, RegisterAsset { + name: Option, contract: Contract, - }, - UpdateAsset { - asset: HumanAddr, - contract: Contract, + burnable: Option, + total_burned: Option, }, Receive { sender: HumanAddr, from: HumanAddr, amount: Uint128, memo: Option, - msg: Option, + msg: Option, }, } impl Handle<'_> for HandleMsg{} +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct SnipMsgHook { + pub minimum_expected_amount: Uint128, + pub to_mint: HumanAddr, +} + #[derive(Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum HandleAnswer { @@ -68,7 +73,6 @@ pub enum HandleAnswer { Migrate { status: ResponseStatus }, UpdateConfig { status: ResponseStatus}, RegisterAsset { status: ResponseStatus}, - UpdateAsset { status: ResponseStatus}, Burn { status: ResponseStatus, mint_amount: Uint128 } } @@ -88,13 +92,6 @@ impl Query for QueryMsg {} #[serde(rename_all = "snake_case")] pub enum QueryAnswer { SupportedAssets { assets: Vec, }, - Asset { asset: BurnableAsset }, + Asset { asset: SupportedAsset }, Config { config: MintConfig }, } - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub struct AssetMsg { - pub contract: Contract, - pub burned_tokens: Option, -} \ No newline at end of file diff --git a/packages/shade_protocol/src/oracle.rs b/packages/shade_protocol/src/oracle.rs index f8bf7b917..5d8afe76b 100644 --- a/packages/shade_protocol/src/oracle.rs +++ b/packages/shade_protocol/src/oracle.rs @@ -1,16 +1,22 @@ +use cosmwasm_std::{HumanAddr, Uint128}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use cosmwasm_std::{HumanAddr, Uint128}; -use crate::msg_traits::{Init, Handle, Query}; +use crate::{ + msg_traits::{Init, Handle, Query}, + asset::Contract, + generic_response::ResponseStatus, +}; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct OracleConfig { pub owner: HumanAddr, + pub band: Contract, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct InitMsg { pub admin: Option, + pub band: Contract, } impl Init<'_> for InitMsg {} @@ -18,19 +24,42 @@ impl Init<'_> for InitMsg {} #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum HandleMsg { + UpdateConfig { + owner: Option, + band: Contract, + }, } impl Handle<'_> for HandleMsg{} +#[derive(Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum HandleAnswer { + UpdateConfig { status: ResponseStatus}, +} + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum QueryMsg { - GetScrtPrice {} + GetPrice { symbol: String }, + GetConfig {}, + GetReferenceData { + base_symbol: String, + quote_symbol: String, + }, } impl Query for QueryMsg {} -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct PriceResponse { - pub price: Uint128, -} \ No newline at end of file +#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, JsonSchema)] +pub struct ReferenceData { + pub rate: Uint128, + pub last_updated_base: u64, + pub last_updated_quote: u64, +} + +#[derive(Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum QueryAnswer { + Config { config: OracleConfig }, +}