diff --git a/Cargo.lock b/Cargo.lock index cbfa76190..30e08d09c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,19 +1,21 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "addr2line" -version = "0.13.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b6a2d3371669ab3ca9797670853d61402b03d0b4b9ebf33d677dfa720203072" +checksum = "3e61f2b7f93d2c7d2b08263acaa4a363b3e276806c68af6134c44f523bf1aacd" dependencies = [ "gimli", ] [[package]] name = "adler" -version = "0.2.3" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "arrayref" @@ -29,11 +31,12 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "backtrace" -version = "0.3.50" +version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46254cf2fdcdf1badb5934448c1bcbe046a56537b3987d96c51a7afc5d03f293" +checksum = "e7a905d892734eea339e896738c14b9afce22b5318f64b951e70bf3844419b01" dependencies = [ "addr2line", + "cc", "cfg-if", "libc", "miniz_oxide", @@ -66,7 +69,7 @@ dependencies = [ "block-padding", "byte-tools", "byteorder", - "generic-array 0.12.3", + "generic-array 0.12.4", ] [[package]] @@ -95,15 +98,21 @@ checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" [[package]] name = "byteorder" -version = "1.3.4" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cc" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" +checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2" [[package]] name = "cfg-if" -version = "0.1.10" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cosmwasm-schema" @@ -114,18 +123,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "cosmwasm-std" -version = "0.10.0" -source = "git+https://github.com/enigmampc/SecretNetwork?tag=v1.0.0#490fba9243e6cb291462e9d3c1bcbd1975c0df1e" -dependencies = [ - "base64", - "schemars", - "serde", - "serde-json-wasm", - "snafu", -] - [[package]] name = "cosmwasm-std" version = "0.10.0" @@ -138,53 +135,24 @@ dependencies = [ "snafu", ] -[[package]] -name = "cosmwasm-std" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f85908a2696117c8f2c1b3ce201d34a1aa9a6b3c1583a65cfb794ec66e1cfde4" -dependencies = [ - "base64", - "schemars", - "serde", - "serde-json-wasm", - "snafu", -] - -[[package]] -name = "cosmwasm-storage" -version = "0.10.0" -source = "git+https://github.com/enigmampc/SecretNetwork?tag=v1.0.0#490fba9243e6cb291462e9d3c1bcbd1975c0df1e" -dependencies = [ - "cosmwasm-std 0.10.0 (git+https://github.com/enigmampc/SecretNetwork?tag=v1.0.0)", - "serde", -] - [[package]] name = "cosmwasm-storage" version = "0.10.0" source = "git+https://github.com/enigmampc/SecretNetwork?tag=v1.0.4-debug-print#004c6bca6f2b7f31a6594abe4f44f2e41b1456b3" dependencies = [ - "cosmwasm-std 0.10.0 (git+https://github.com/enigmampc/SecretNetwork?tag=v1.0.4-debug-print)", + "cosmwasm-std", "serde", ] [[package]] -name = "cosmwasm-storage" -version = "0.10.1" +name = "cpufeatures" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e103531a2ce636e86b7639cec25d348c4d360832ab8e0e7f9a6e00f08aac1379" +checksum = "66c99696f6c9dd7f35d486b9d04d7e6e202aa3e8c40d553f2fdf5e7e0c6a71ef" dependencies = [ - "cosmwasm-std 0.10.1", - "serde", + "libc", ] -[[package]] -name = "cpuid-bool" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" - [[package]] name = "crunchy" version = "0.2.2" @@ -197,40 +165,17 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" dependencies = [ - "generic-array 0.12.3", + "generic-array 0.12.4", "subtle 1.0.0", ] -[[package]] -name = "cw0" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21544b8cbbcbb99dfdf071408288925ebeaf8f633a6138a19bd2156b8ff6ab31" -dependencies = [ - "cosmwasm-std 0.10.1", - "schemars", - "serde", -] - -[[package]] -name = "cw20" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c68a7162d61e99e7cbdc228025b847506bd8c5501c80494b33c0df6d1628d0f4" -dependencies = [ - "cosmwasm-std 0.10.1", - "cw0", - "schemars", - "serde", -] - [[package]] name = "digest" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" dependencies = [ - "generic-array 0.12.3", + "generic-array 0.12.4", ] [[package]] @@ -256,9 +201,9 @@ checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" [[package]] name = "generic-array" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" dependencies = [ "typenum", ] @@ -275,9 +220,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.22.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724" +checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7" [[package]] name = "hmac" @@ -296,21 +241,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6e570451493f10f6581b48cdd530413b63ea9e780f544bfd3bdcaa0d89d1a7b" dependencies = [ "digest 0.8.1", - "generic-array 0.12.3", + "generic-array 0.12.4", "hmac", ] [[package]] name = "itoa" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" +checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" [[package]] name = "libc" -version = "0.2.77" +version = "0.2.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f96b10ec2560088a8e76961b00d47107b3a625fecb76dedb29ee7ccbf98235" +checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790" [[package]] name = "libsecp256k1" @@ -324,37 +269,48 @@ dependencies = [ "hmac-drbg", "rand", "sha2 0.8.2", - "subtle 2.3.0", + "subtle 2.4.1", "typenum", ] +[[package]] +name = "memchr" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" + [[package]] name = "miniz_oxide" -version = "0.4.2" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c60c0dfe32c10b43a144bad8fc83538c52f58302c92300ea7ec7bf7b38d5a7b9" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" dependencies = [ "adler", "autocfg", ] [[package]] -name = "mirror-protocol" -version = "1.1.0" +name = "mint" +version = "0.1.0" dependencies = [ - "cosmwasm-std 0.10.1", - "cosmwasm-storage 0.10.1", - "cw20", + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-storage", "schemars", + "secret-toolkit", "serde", - "terraswap", + "shade-protocol", + "snafu", ] [[package]] name = "object" -version = "0.20.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5" +checksum = "c55827317fb4c08822499848a14237d2874d6f139828893017237e7ab93eb386" +dependencies = [ + "memchr", +] [[package]] name = "opaque-debug" @@ -368,6 +324,20 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "oracle" +version = "0.1.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-storage", + "schemars", + "secret-toolkit", + "serde", + "shade-protocol", + "snafu", +] + [[package]] name = "ppv-lite86" version = "0.2.10" @@ -376,18 +346,18 @@ checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" [[package]] name = "proc-macro2" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" +checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" dependencies = [ "unicode-xid", ] [[package]] name = "quote" -version = "1.0.7" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" dependencies = [ "proc-macro2", ] @@ -430,9 +400,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.16" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" +checksum = "dead70b0b5e03e9c814bcb6b01e03e68f7c57a80aa48c72ec92152ab3e818d49" [[package]] name = "ryu" @@ -466,7 +436,7 @@ dependencies = [ [[package]] name = "secret-toolkit" version = "0.1.0" -source = "git+https://github.com/enigmampc/secret-toolkit?branch=master#87b3a5d721bebbb73a20be56b9f403b4be10eed2" +source = "git+https://github.com/enigmampc/secret-toolkit?branch=debug-print#f8de2a926fccab9d1f10653bdbc00ea5436df570" dependencies = [ "secret-toolkit-crypto", "secret-toolkit-serialization", @@ -479,31 +449,31 @@ dependencies = [ [[package]] name = "secret-toolkit-crypto" version = "0.1.0" -source = "git+https://github.com/enigmampc/secret-toolkit?branch=master#87b3a5d721bebbb73a20be56b9f403b4be10eed2" +source = "git+https://github.com/enigmampc/secret-toolkit?branch=debug-print#f8de2a926fccab9d1f10653bdbc00ea5436df570" dependencies = [ - "cosmwasm-std 0.10.0 (git+https://github.com/enigmampc/SecretNetwork?tag=v1.0.0)", + "cosmwasm-std", "libsecp256k1", "rand_chacha", "rand_core", - "sha2 0.9.1", + "sha2 0.9.5", ] [[package]] name = "secret-toolkit-serialization" version = "0.1.0" -source = "git+https://github.com/enigmampc/secret-toolkit?branch=master#87b3a5d721bebbb73a20be56b9f403b4be10eed2" +source = "git+https://github.com/enigmampc/secret-toolkit?branch=debug-print#f8de2a926fccab9d1f10653bdbc00ea5436df570" dependencies = [ "bincode2", - "cosmwasm-std 0.10.0 (git+https://github.com/enigmampc/SecretNetwork?tag=v1.0.0)", + "cosmwasm-std", "serde", ] [[package]] name = "secret-toolkit-snip20" version = "0.1.0" -source = "git+https://github.com/enigmampc/secret-toolkit?branch=master#87b3a5d721bebbb73a20be56b9f403b4be10eed2" +source = "git+https://github.com/enigmampc/secret-toolkit?branch=debug-print#f8de2a926fccab9d1f10653bdbc00ea5436df570" dependencies = [ - "cosmwasm-std 0.10.0 (git+https://github.com/enigmampc/SecretNetwork?tag=v1.0.0)", + "cosmwasm-std", "schemars", "secret-toolkit-utils", "serde", @@ -512,9 +482,9 @@ dependencies = [ [[package]] name = "secret-toolkit-snip721" version = "0.1.0" -source = "git+https://github.com/enigmampc/secret-toolkit?branch=master#87b3a5d721bebbb73a20be56b9f403b4be10eed2" +source = "git+https://github.com/enigmampc/secret-toolkit?branch=debug-print#f8de2a926fccab9d1f10653bdbc00ea5436df570" dependencies = [ - "cosmwasm-std 0.10.0 (git+https://github.com/enigmampc/SecretNetwork?tag=v1.0.0)", + "cosmwasm-std", "schemars", "secret-toolkit-utils", "serde", @@ -523,10 +493,10 @@ dependencies = [ [[package]] name = "secret-toolkit-storage" version = "0.1.0" -source = "git+https://github.com/enigmampc/secret-toolkit?branch=master#87b3a5d721bebbb73a20be56b9f403b4be10eed2" +source = "git+https://github.com/enigmampc/secret-toolkit?branch=debug-print#f8de2a926fccab9d1f10653bdbc00ea5436df570" dependencies = [ - "cosmwasm-std 0.10.0 (git+https://github.com/enigmampc/SecretNetwork?tag=v1.0.0)", - "cosmwasm-storage 0.10.0 (git+https://github.com/enigmampc/SecretNetwork?tag=v1.0.0)", + "cosmwasm-std", + "cosmwasm-storage", "secret-toolkit-serialization", "serde", ] @@ -534,36 +504,36 @@ dependencies = [ [[package]] name = "secret-toolkit-utils" version = "0.1.0" -source = "git+https://github.com/enigmampc/secret-toolkit?branch=master#87b3a5d721bebbb73a20be56b9f403b4be10eed2" +source = "git+https://github.com/enigmampc/secret-toolkit?branch=debug-print#f8de2a926fccab9d1f10653bdbc00ea5436df570" dependencies = [ - "cosmwasm-std 0.10.0 (git+https://github.com/enigmampc/SecretNetwork?tag=v1.0.0)", + "cosmwasm-std", "schemars", "serde", ] [[package]] name = "serde" -version = "1.0.126" +version = "1.0.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" +checksum = "f03b9878abf6d14e6779d3f24f07b2cfa90352cfec4acc5aab8f1ac7f146fae8" dependencies = [ "serde_derive", ] [[package]] name = "serde-json-wasm" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7294d94d390f1d2334697c065ea591d7074c676e2d20aa6f1df752fced29823f" +checksum = "120bad73306616e91acd7ceed522ba96032a51cffeef3cc813de7f367df71e37" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.126" +version = "1.0.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" +checksum = "a024926d3432516606328597e0f224a51355a493b49fdd67e9209187cbe55ecc" dependencies = [ "proc-macro2", "quote", @@ -583,9 +553,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.57" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "164eacbdb13512ec2745fb09d51fd5b22b0d65ed294a1dcf7285a360c80a675c" +checksum = "336b10da19a12ad094b59d870ebde26a45402e5b470add4b5fd03c5048a32127" dependencies = [ "itoa", "ryu", @@ -606,24 +576,24 @@ dependencies = [ [[package]] name = "sha2" -version = "0.9.1" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2933378ddfeda7ea26f48c555bdad8bb446bf8a3d17832dc83e380d444cfb8c1" +checksum = "b362ae5752fd2137731f9fa25fd4d9058af34666ca1966fb969119cc35719f12" dependencies = [ "block-buffer 0.9.0", "cfg-if", - "cpuid-bool", + "cpufeatures", "digest 0.9.0", "opaque-debug 0.3.0", ] [[package]] -name = "shade-mint" +name = "shade-protocol" version = "0.1.0" dependencies = [ "cosmwasm-schema", - "cosmwasm-std 0.10.0 (git+https://github.com/enigmampc/SecretNetwork?tag=v1.0.4-debug-print)", - "cosmwasm-storage 0.10.0 (git+https://github.com/enigmampc/SecretNetwork?tag=v1.0.4-debug-print)", + "cosmwasm-std", + "cosmwasm-storage", "schemars", "secret-toolkit", "serde", @@ -632,9 +602,9 @@ dependencies = [ [[package]] name = "snafu" -version = "0.6.8" +version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f5aed652511f5c9123cf2afbe9c244c29db6effa2abb05c866e965c82405ce" +checksum = "eab12d3c261b2308b0d80c26fffb58d17eba81a4be97890101f416b478c79ca7" dependencies = [ "backtrace", "doc-comment", @@ -643,9 +613,9 @@ dependencies = [ [[package]] name = "snafu-derive" -version = "0.6.8" +version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebf8f7d5720104a9df0f7076a8682024e958bba0fe9848767bb44f251f3648e9" +checksum = "1508efa03c362e23817f96cde18abed596a25219a8b2c66e8db33c03543d315b" dependencies = [ "proc-macro2", "quote", @@ -660,60 +630,35 @@ checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" [[package]] name = "subtle" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "343f3f510c2915908f155e94f17220b19ccfacf2a64a2a5d8004f2c3e311e7fd" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.73" +version = "1.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7" +checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c" dependencies = [ "proc-macro2", "quote", "unicode-xid", ] -[[package]] -name = "terra-cosmwasm" -version = "1.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d7275aacd385e4f41647634c35692b1982085917b4dcfc1fdfa3984ee4ce45d" -dependencies = [ - "cosmwasm-std 0.10.1", - "schemars", - "serde", -] - -[[package]] -name = "terraswap" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02334ec5ad280fcc09c86467d40383ea1e4d977103345e53a4f03006c4be51c2" -dependencies = [ - "cosmwasm-std 0.10.1", - "cosmwasm-storage 0.10.1", - "cw20", - "schemars", - "serde", - "terra-cosmwasm", -] - [[package]] name = "typenum" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" +checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" [[package]] name = "unicode-xid" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" [[package]] name = "version_check" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" diff --git a/Cargo.toml b/Cargo.toml index 67d04c481..3c7db5517 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,14 +1,2 @@ [workspace] -members = ["packages/*", "shade-contracts/*"] - -[profile.release.package.mirror-protocol] -opt-level = 3 -debug = false -debug-assertions = false -codegen-units = 1 -incremental = false - -[profile.release] -rpath = false -lto = true -overflow-checks = true +members = ["contracts/mint","contracts/oracle", "packages/shade_protocol"] diff --git a/LICENSE b/LICENSE index cd382ffc4..153d416dc 100644 --- a/LICENSE +++ b/LICENSE @@ -1,52 +1,165 @@ -Apache License + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 -Version 2.0, January 2004 + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. -http://www.apache.org/licenses/ -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. -1. Definitions. + 0. Additional Definitions. -"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. -"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. - -"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - -"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. - -"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. - -"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. - -"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). - -"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. - -"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." - -"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: - -You must give any other recipients of the Work or Derivative Works a copy of this License; and -You must cause any modified files to carry prominent notices stating that You changed the files; and -You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and -If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. - -You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. \ No newline at end of file diff --git a/README.md b/README.md index 91b44531d..37d618acd 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,33 @@ # Shade Protocol Core Contracts -| Contract | Reference | Description | -| --------------------------------------------------- | ------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- | -| [Placeholder]() | [Placeholder]() | [Placeholder] | -| [Placeholder]() | [Placeholder]() | [Placeholder] | -| [Placeholder]() | [Placeholder]() | [Placeholder] | -| [Placeholder]() | [Placeholder]() | [Placeholder] | -| [Placeholder]() | [Placeholder]() | [Placeholder] | -|[Placeholder]() | [Placeholder]() | [Placeholder] | -| [Placeholder]() | [Placeholder]() |[Placeholder] | +| Contract | Reference | Description | +| --------------------------- | --------------------------------- | ------------------------------------- | +| [`mint`](./contracts/mint) | [doc](./contracts/mint/README.md) | Handles asset burning and silk minting| +## Development +## Development Environment +Instlal docker for local envirnment -# Mirror Core Contracts - -This monorepository contains the source code for the core smart contracts implementing Mirror Protocol on the [Terra](https://terra.money) blockchain. - -You can find information about the architecture, usage, and function of the smart contracts on the official Mirror documentation [site](https://docs.mirror.finance/contracts/architecture). - -### Dependencies - -Mirror depends on [Terraswap](https://terraswap.io) and uses its [implementation](https://github.com/terraswap/terraswap) of the CW20 token specification. +Source from [testner](https://build.scrt.network/dev/quickstart.html#setup-the-local-developer-testnet) -## Contracts +``` +docker run -it --rm -p 26657:26657 -p 26656:26656 -p 1337:1337 -v $(pwd):/root/code --name secretdev enigmampc/secret-network-sw-dev -| Contract | Reference | Description | -| --------------------------------------------------- | ------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- | -| [`mirror_collector`](./contracts/mirror_collector) | [doc](https://docs.mirror.finance/contracts/collector) | Gathers protocol fees incurred from CDP withdrawals and liquidations and sends to Gov | -| [`mirror_community`](../contracts/mirror_community) | [doc](https://docs.mirror.finance/contracts/community) | Manages the commuinty pool fund | -| [`mirror_factory`](./contracts/mirror_factory) | [doc](https://docs.mirror.finance/contracts/factory) | Central directory that organizes the various component contracts of Mirror | -| [`mirror_gov`](./contracts/mirror_gov) | [doc](https://docs.mirror.finance/contracts/gov) | Allows other Mirror contracts to be controlled by decentralized governance, distributes MIR received from Collector to MIR stakers | -| [`mirror_mint`](./contracts/mirror_mint) | [doc](https://docs.mirror.finance/contracts/mint) | Handles CDP creation, management and liquidation | -| [`mirror_oracle`](./contracts/mirror_oracle) | [doc](https://docs.mirror.finance/contracts/oracle) | Provides interface for oracle feeders to post prices for mAssets | -| [`mirror_staking`](./contracts/mirror_staking) | [doc](https://docs.mirror.finance/contracts/staking) | Distributes MIR rewards from block reward to LP stakers | +docker exec -it secretdev /bin/bash -## Development +``` +#### Testing the environment +Inside the container: +``` +run python3 contract_tester.py +``` ### Environment Setup - Rust v1.44.1+ - `wasm32-unknown-unknown` target - Docker +- binaryen 1. Install `rustup` via https://rustup.rs/ @@ -52,6 +40,11 @@ rustup target add wasm32-unknown-unknown 3. Make sure [Docker](https://www.docker.com/) is installed +4. To compile the contracts install binaryen +```sh +apt install binaryen +``` + ### Unit / Integration Tests Each contract contains Rust unit and integration tests embedded within the contract source directories. You can run: @@ -63,32 +56,12 @@ cargo integration-test ### Compiling -After making sure tests pass, you can compile each contract with the following: - -```sh -RUSTFLAGS='-C link-arg=-s' cargo wasm -cp ../../target/wasm32-unknown-unknown/release/cw1_subkeys.wasm . -ls -l cw1_subkeys.wasm -sha256sum cw1_subkeys.wasm -``` - -#### Production - -For production builds, run the following: +Run this script to run all of the contract's unit / integration tests and then prepare the contracts for production in /contracts/compiled: ```sh -docker run --rm -v "$(pwd)":/code \ - --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ - --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - cosmwasm/workspace-optimizer:0.10.2 +bash ./compile-contracts.sh ``` -This performs several optimizations which can significantly reduce the final size of the contract binaries, which will be available inside the `artifacts/` directory. - -## License - -Copyright 2020 Mirror Protocol - -Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +### Testing -See the License for the specific language governing permissions and limitations under the License. +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 diff --git a/artifacts/checksums.txt b/artifacts/checksums.txt deleted file mode 100644 index 574a55cc1..000000000 --- a/artifacts/checksums.txt +++ /dev/null @@ -1,10 +0,0 @@ -12e7f8dfe7edbe0527b7cd4b51e62281c091ebff7288c2232907af792d8615ab mirror_collateral_oracle.wasm -209b62233d441bf87f8e8404bcb71e7c51f04e5487bb2944bd94f116881f4e75 mirror_collector.wasm -a6b7aeff684d79715e3cffda23de2044c0e7b6db9e21fd2f71924d84754928fe mirror_community.wasm -5d6309d575a4b2a1bdc9ddfde5a1ca4a012dc8ea38f93fcbf11a62fc4e65c668 mirror_factory.wasm -d3ca1c56db12dea2b8b58b58216500cbc8814a3de346ffdda2697b361f5cbfb8 mirror_gov.wasm -a4a1793160bc826fd48ab2513ede7a74175796db176a8bf26f19df97f4aebb17 mirror_limit_order.wasm -db6e6599dde8218e7afe31ec1d6d916246c2b4ab3eb02f4ddf8a19f84d05de27 mirror_lock.wasm -5719651503503c80369d1ffdbbd9448096f059bbc83be50a9bcddf36b3b16947 mirror_mint.wasm -c763656f90330870bde049b352a859b43dc461eb04199c9214e3f673eabca460 mirror_oracle.wasm -de9362bc7dd6b1ffdb64c87165d11117e14cbdb3cb37deefac4e6088e37af432 mirror_staking.wasm diff --git a/artifacts/mirror_collateral_oracle.wasm b/artifacts/mirror_collateral_oracle.wasm deleted file mode 100644 index 87da459ca..000000000 Binary files a/artifacts/mirror_collateral_oracle.wasm and /dev/null differ diff --git a/artifacts/mirror_collector.wasm b/artifacts/mirror_collector.wasm deleted file mode 100644 index 5d7a9361f..000000000 Binary files a/artifacts/mirror_collector.wasm and /dev/null differ diff --git a/artifacts/mirror_community.wasm b/artifacts/mirror_community.wasm deleted file mode 100644 index 23d586e66..000000000 Binary files a/artifacts/mirror_community.wasm and /dev/null differ diff --git a/artifacts/mirror_factory.wasm b/artifacts/mirror_factory.wasm deleted file mode 100644 index d18c4e445..000000000 Binary files a/artifacts/mirror_factory.wasm and /dev/null differ diff --git a/artifacts/mirror_gov.wasm b/artifacts/mirror_gov.wasm deleted file mode 100644 index 3ed0451a5..000000000 Binary files a/artifacts/mirror_gov.wasm and /dev/null differ diff --git a/artifacts/mirror_limit_order.wasm b/artifacts/mirror_limit_order.wasm deleted file mode 100644 index 0e67d073e..000000000 Binary files a/artifacts/mirror_limit_order.wasm and /dev/null differ diff --git a/artifacts/mirror_lock.wasm b/artifacts/mirror_lock.wasm deleted file mode 100644 index 023bed012..000000000 Binary files a/artifacts/mirror_lock.wasm and /dev/null differ diff --git a/artifacts/mirror_mint.wasm b/artifacts/mirror_mint.wasm deleted file mode 100644 index 5d9b5ff72..000000000 Binary files a/artifacts/mirror_mint.wasm and /dev/null differ diff --git a/artifacts/mirror_oracle.wasm b/artifacts/mirror_oracle.wasm deleted file mode 100644 index 9655c0d25..000000000 Binary files a/artifacts/mirror_oracle.wasm and /dev/null differ diff --git a/artifacts/mirror_staking.wasm b/artifacts/mirror_staking.wasm deleted file mode 100644 index 95d45e760..000000000 Binary files a/artifacts/mirror_staking.wasm and /dev/null differ diff --git a/compile-contracts.sh b/compile-contracts.sh new file mode 100755 index 000000000..6afd0f71e --- /dev/null +++ b/compile-contracts.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +root_dir=$(git rev-parse --show-toplevel) +contracts_dir="${root_dir}/contracts" +compiled_dir="${contracts_dir}/compiled" + +compile_contract() { + # Run tests + (cd ${contracts_dir}/$1; cargo unit-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 + cat ./$1.wasm | gzip -n -9 > ${compiled_dir}/$1.wasm.gz + rm -f ./$1.wasm +} + +compile_contract "mint" +compile_contract "oracle" diff --git a/contracts/compiled/__init__.py b/contracts/compiled/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/contracts/compiled/contract_tester.py b/contracts/compiled/contract_tester.py new file mode 100644 index 000000000..ecdbed8a0 --- /dev/null +++ b/contracts/compiled/contract_tester.py @@ -0,0 +1,63 @@ +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 new file mode 100644 index 000000000..e69de29bb diff --git a/contracts/compiled/contractlib/contractlib.py b/contracts/compiled/contractlib/contractlib.py new file mode 100644 index 000000000..f1261c61a --- /dev/null +++ b/contracts/compiled/contractlib/contractlib.py @@ -0,0 +1,63 @@ +from .secretlib import secretlib + + +class PreInstantiatedContract: + def __init__(self, contract_id, address, code_hash): + self.contract_id = contract_id + 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): + 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) + 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"] + for attribute in initResponse["logs"][0]["events"][0]["attributes"]: + if attribute["key"] == "contract_address": + self.address = attribute["value"] + break + + else: + self.contract_id = instantiated_contract.contract_id + self.code_hash = instantiated_contract.code_hash + self.address = instantiated_contract.address + + def execute(self, msg, sender=None, amount=None, compute=True): + """ + Execute said msg + :param msg: Execute msg + :param sender: Who will be sending the message, defaults to contract admin + :param amount: Optional string amount to send along with transaction + :return: Result + """ + signer = sender if sender is not None else self.admin + return secretlib.execute_contract(self.address, msg, signer, self.backend, amount, compute) + + def query(self, msg): + """ + Query said msg + :param msg: Query msg + :return: Query + """ + return secretlib.query_contract(self.address, msg) + + def print(self): + """ + Prints the contract info + :return: + """ + print(f"Label: {self.label}\n" + f"Address: {self.address}\n" + f"Id: {self.contract_id}\n" + f"Hash: {self.code_hash}") diff --git a/contracts/compiled/contractlib/mintlib.py b/contracts/compiled/contractlib/mintlib.py new file mode 100644 index 000000000..5b34ac0b4 --- /dev/null +++ b/contracts/compiled/contractlib/mintlib.py @@ -0,0 +1,117 @@ +import copy + +from .contractlib import Contract +from .secretlib import secretlib +import json + + +class Mint(Contract): + def __init__(self, label, silk, oracle, contract='mint.wasm.gz', admin='a', uploader='a', gas='10000000', + backend='test', instantiated_contract=None): + init_msg = json.dumps( + {"silk": {"address": silk.address, "code_hash": silk.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) + + def migrate(self, label, code_id, code_hash): + """ + Instantiate another mint contract and migrate this contracts info into that one + :param label: Label name of the contract + :param code_id: Code id of the contract + :param code_hash: Code hash + :return: new Mint + """ + msg = json.dumps( + {"migrate": {"label": label, "code_id": code_id, "code_hash": code_hash}}) + + new_mint = copy.deepcopy(self) + for attribute in self.execute(msg, compute=False)["logs"][0]["events"][0]["attributes"]: + if attribute["key"] == "contract_address": + new_mint.address = attribute["value"] + break + new_mint.contract_id = code_id + new_mint.code_hash = code_hash + return new_mint + + def update_config(self, owner=None, silk=None, oracle=None): + """ + Updates the minting contract's config + :param owner: New admin + :param silk: Silk contract + :param oracle: Oracle contract + :return: Result + """ + 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, + "code_hash": oracle.code_hash + } + raw_msg["update_config"]["oracle"] = contract + + msg = json.dumps(raw_msg) + return self.execute(msg) + + def register_asset(self, snip20): + """ + Registers a SNIP20 asset + :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}}}) + + return self.execute(msg) + + def get_supported_assets(self): + """ + Get all supported asset addressed + :return: Supported assets info + """ + msg = json.dumps( + {"get_supported_assets": {}}) + + return self.query(msg) + + def get_config(self): + """ + Get the contracts config information + :return: Contract config info + """ + msg = json.dumps( + {"get_config": {}}) + + return self.query(msg) + + def get_asset(self, snip20): + """ + Returns that assets info + :param snip20: SNIP20 object to query + :return: Asset info + """ + msg = json.dumps( + {"get_asset": {"contract": snip20.address}}) + + return self.query(msg) diff --git a/contracts/compiled/contractlib/oraclelib.py b/contracts/compiled/contractlib/oraclelib.py new file mode 100644 index 000000000..c8b4c6050 --- /dev/null +++ b/contracts/compiled/contractlib/oraclelib.py @@ -0,0 +1,23 @@ +import copy + +from .contractlib import Contract +from .secretlib import secretlib +import json + + +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({}) + super().__init__(contract, init_msg, label, admin, uploader, gas, backend, + instantiated_contract=instantiated_contract) + + def get_silk_price(self): + """ + Get current silk price + :return: + """ + msg = json.dumps( + {"get_scrt_price": {}}) + + return self.query(msg) \ No newline at end of file diff --git a/contracts/compiled/contractlib/secretlib/__init__.py b/contracts/compiled/contractlib/secretlib/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/contracts/compiled/contractlib/secretlib/secretlib.py b/contracts/compiled/contractlib/secretlib/secretlib.py new file mode 100644 index 000000000..b6737c860 --- /dev/null +++ b/contracts/compiled/contractlib/secretlib/secretlib.py @@ -0,0 +1,107 @@ +import subprocess +import json +import time + +# Presetup some commands +query_list_code = ['secretcli', 'query', 'compute', 'list-code'] + + +def run_command(command, wait=6): + """ + Will run any cli command and return its output after waiting a set amount + :param command: Array of command to run + :param wait: Time to wait for command + :return: Output string + """ + + result = subprocess.run(command, stdout=subprocess.PIPE, text=True) + time.sleep(wait) + return result.stdout + + +def store_contract(contract, user='a', gas='10000000', backend='test', wait=15): + """ + Store contract and return its ID + :param contract: Contract name + :param user: User to upload with + :param gas: Gas to use + :param backend: Keyring backend + :return: Contract ID + """ + + command = ['secretcli', 'tx', 'compute', 'store', contract, + '--from', user, '--gas', gas, '-y'] + + 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'] + + +def instantiate_contract(contract, msg, label, user='a', backend='test', wait=6): + """ + Instantiates a contract + :param contract: Contract name + :param msg: Init msg + :param label: Name to give to the contract + :param user: User to instantiate with + :param backend: Keyring backend + :return: + """ + + command = ['secretcli', 'tx', 'compute', 'instantiate', contract, msg, '--from', + user, '--label', label, '-y'] + + if backend is not None: + command += ['--keyring-backend', backend] + + return run_command_query_hash(command, wait) + + +def list_code(): + command = ['secretcli', 'query', 'compute', 'list-code'] + + return json.loads(run_command(command, 3)) + + +def execute_contract(contract, msg, user='a', backend='test', amount=None, compute=True, wait=6): + command = ['secretcli', 'tx', 'compute', 'execute', contract, msg, '--from', user, '--gas', '10000000', '-y'] + + if backend is not None: + command += ['--keyring-backend', backend] + + if amount is not None: + command.append("--amount") + command.append(amount) + + if compute: + return run_command_compute_hash(command, wait) + return run_command_query_hash(command, wait) + + +def query_hash(hash): + return run_command(['secretcli', 'q', 'tx', hash], 3) + + +def compute_hash(hash): + return run_command(['secretcli', 'q', 'compute', 'tx', hash]) + + +def query_contract(contract, msg): + command = ['secretcli', 'query', 'compute', 'query', contract, msg] + + return json.loads(run_command(command)) + + +def run_command_compute_hash(command, wait=6): + out = run_command(command, wait) + txhash = json.loads(out)["txhash"] + return json.loads(compute_hash(txhash)) + + +def run_command_query_hash(command, wait=6): + out = run_command(command, wait) + txhash = json.loads(out)["txhash"] + return json.loads(query_hash(txhash)) diff --git a/contracts/compiled/contractlib/snip20lib.py b/contracts/compiled/contractlib/snip20lib.py new file mode 100644 index 000000000..388b1893b --- /dev/null +++ b/contracts/compiled/contractlib/snip20lib.py @@ -0,0 +1,90 @@ +from .contractlib import Contract +from .secretlib import secretlib +import json + + +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): + self.view_key = "" + initMsg = json.dumps( + {"name": name, "symbol": symbol, "decimals": decimals, "prng_seed": seed, "config": { + "public_total_supply": public_total_supply, "enable_deposit": enable_deposit, + "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) + + def set_minters(self, accounts): + """ + Sets minters + :param accounts: Accounts list + :return: Response + """ + msg = json.dumps( + {"set_minters": {"minters": accounts}}) + + return self.execute(msg) + + def deposit(self, account, amount): + """ + Deposit a specified amount to contract + :param account: User which will deposit + :param amount: uSCRT + :return: Response + """ + msg = json.dumps( + {"deposit": {}}) + + return self.execute(msg, account, amount) + + def mint(self, recipient, amount): + """ + Mint an amount into the recipients wallet + :param recipient: Address to be minted in + :param amount: Amount to mint + :return: Response + """ + msg = json.dumps( + {"mint": {"recipient": recipient, "amount": str(amount)}}) + + return self.execute(msg) + + def send(self, account, recipient, amount): + """ + 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 + :return: Response + """ + msg = json.dumps( + {"send": {"recipient": recipient, "amount": str(amount)}}) + + return self.execute(msg, account) + + def set_view_key(self, account, entropy): + """ + Generate view key to query balance + :param account: User to generate the key for + :param entropy: Password generation entropy + :return: Password + """ + msg = json.dumps( + {"create_viewing_key": {"entropy": entropy}}) + + return json.loads(self.execute(msg, account)["output_data_as_string"])["create_viewing_key"]["key"] + + def get_balance(self, address, password): + """ + Gets amount of coins in wallet + :param address: Account to access + :param password: View key + :return: Response + """ + msg = json.dumps( + {"balance": {"key": password, "address": address}}) + + return self.query(msg)["balance"]["amount"] diff --git a/contracts/compiled/contractlib/utils.py b/contracts/compiled/contractlib/utils.py new file mode 100644 index 000000000..58decdd74 --- /dev/null +++ b/contracts/compiled/contractlib/utils.py @@ -0,0 +1,7 @@ +import string +import random + + +def gen_label(length): + # With combination of lower and upper case + return ''.join(random.choice(string.ascii_letters) for i in range(length)) diff --git a/contracts/compiled/mint.wasm.gz b/contracts/compiled/mint.wasm.gz new file mode 100644 index 000000000..1f0cf4c54 Binary files /dev/null and b/contracts/compiled/mint.wasm.gz differ diff --git a/contracts/compiled/oracle.wasm.gz b/contracts/compiled/oracle.wasm.gz new file mode 100644 index 000000000..1337a1103 Binary files /dev/null and b/contracts/compiled/oracle.wasm.gz differ diff --git a/contracts/compiled/snip20.wasm.gz b/contracts/compiled/snip20.wasm.gz new file mode 100644 index 000000000..be54f8913 Binary files /dev/null and b/contracts/compiled/snip20.wasm.gz differ diff --git a/contracts/mirror_gov/.cargo/config b/contracts/mint/.cargo/config similarity index 100% rename from contracts/mirror_gov/.cargo/config rename to contracts/mint/.cargo/config diff --git a/contracts/mint/.circleci/config.yml b/contracts/mint/.circleci/config.yml new file mode 100644 index 000000000..127e1ae7d --- /dev/null +++ b/contracts/mint/.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/mirror_collateral_oracle/Cargo.toml b/contracts/mint/Cargo.toml similarity index 55% rename from contracts/mirror_collateral_oracle/Cargo.toml rename to contracts/mint/Cargo.toml index 5975759e7..b72f053da 100644 --- a/contracts/mirror_collateral_oracle/Cargo.toml +++ b/contracts/mint/Cargo.toml @@ -1,10 +1,8 @@ [package] -name = "mirror-collateral-oracle" -version = "1.1.0" -authors = ["Terraform Labs, PTE."] +name = "mint" +version = "0.1.0" +authors = ["Guy Garcia "] edition = "2018" -description = "A proxy oracle for collaterals in Mirror Protocol" -license = "Apache-2.0" 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. @@ -29,19 +27,18 @@ 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] -cosmwasm-std = { version = "0.10.1", features = ["iterator"] } -cosmwasm-storage = { version = "0.10.1", features = ["iterator"] } -mirror-protocol = { version = "1.1.0", path = "../../packages/mirror_protocol" } +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"] } -terraswap = "1.1.0" -cosmwasm-bignumber = "1.0" -terra-cosmwasm = "1.2.4" - -[dev-dependencies] -cosmwasm-schema = "0.10.1" \ No newline at end of file +snafu = { version = "0.6.3" } diff --git a/contracts/mint/Makefile b/contracts/mint/Makefile new file mode 100644 index 000000000..2493c22f4 --- /dev/null +++ b/contracts/mint/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/mint/README.md b/contracts/mint/README.md new file mode 100644 index 000000000..79290095c --- /dev/null +++ b/contracts/mint/README.md @@ -0,0 +1,175 @@ +# Mint Contract +* [Introduction](#Introduction) +* [Sections](#Sections) + * [Init](#Init) + * [Admin](#Admin) + * Messages + * [Migrate](#Migrate) + * [UpdateConfig](#UpdateConfig) + * [RegisterAsset](#RegisterAsset) + * [UpdateAsset](#UpdateAsset) + * Queries + * [GetConfig](#GetConfig) + * [SupportedAssets](#SupportedAssets) + * [GetAsset](#getAsset) + * [User](#User) + * Messages + * [Receive](#Receive) +# Introduction +The minting contract is used as a way to acquire newly minted Silk, sending a set amount from any supported contract will result in receiving x amount of silk. + +# Sections + +## 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 | + +## Admin + +### Messages +### Migrate +Migrates all the contracts state and data into a new contract +#### Request +| Name | Type | Description | optional | +|----------| -------|---------------------|----------| +|label | String | Contract label name | no | +|code_id | u64 | Contract ID | no | +|code_hash | String | Contract code hash | no | +##### Response +```json +{ + "update_config": { + "status": "success" + } +} +``` + +#### 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" + } +} +``` + +#### RegisterAsset +Registers 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 there's an asset with that address already registered. +##### Request +|Name |Type |Description | optional | +|------------|--------|-----------------------------------------------------------------------------------------------------------------------|----------| +|contract | Contract | Type explained [here](#Contract) | no | +##### Response +```json +{ + "register_asset": { + "status": "success" + } +} +``` + +#### 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 +Gets the contract's configuration variables +##### Response +```json +{ + "config": { + "config": { + "owner": "Owner address", + "silk": { + "address": "Asset contract address", + "code_hash": "Asset callback code hash" + }, + "oracle": { + "address": "Asset contract address", + "code_hash": "Asset callback code hash" + }, + "activated": "Boolean of contract's actviation status" + } + } +} +``` + +#### SupportedAssets +Get all the contract's supported assets. +##### Response +```json +{ + "supported_assets": { + "assets": ["asset address"] + } +} +``` + +#### GetAsset +Get specific information on a supported asset. +##### Request +|Name |Type |Description | optional | +|------------|--------|-----------------------------------------------------------------------------------------------------------------------|----------| +|contract | string | Snip20 contract address; SHOULD be a valid bech32 address, but contracts may use a different naming scheme as well | no | +##### Response +```json +{ + "asset": { + "asset": { + "contract": { + "address": "Asset contract address", + "code_hash": "Asset callback code hash" + }, + "burned_tokens": "Total burned on this contract" + } + } +} +``` + +##User + +### Messages + +#### 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. + +## Contract +Type used in many of the admin commands +```json +{ + "config": { + "address": "Asset contract address", + "code_hash": "Asset callback code hash" + } +} +``` \ No newline at end of file diff --git a/contracts/mirror_lock/examples/schema.rs b/contracts/mint/examples/schema.rs similarity index 54% rename from contracts/mirror_lock/examples/schema.rs rename to contracts/mint/examples/schema.rs index f9a4fe207..54d7d06fb 100644 --- a/contracts/mirror_lock/examples/schema.rs +++ b/contracts/mint/examples/schema.rs @@ -3,9 +3,8 @@ use std::fs::create_dir_all; use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; -use mirror_protocol::lock::{ - ConfigResponse, HandleMsg, InitMsg, PositionLockInfoResponse, QueryMsg, -}; +use mint::msg::{SupportedAssetsResponse, AssetResponse, HandleMsg, InitMsg, QueryMsg}; +use mint::state::{MintConfig, BurnableAsset}; fn main() { let mut out_dir = current_dir().unwrap(); @@ -16,6 +15,8 @@ fn main() { export_schema(&schema_for!(InitMsg), &out_dir); export_schema(&schema_for!(HandleMsg), &out_dir); export_schema(&schema_for!(QueryMsg), &out_dir); - export_schema(&schema_for!(ConfigResponse), &out_dir); - export_schema(&schema_for!(PositionLockInfoResponse), &out_dir); + export_schema(&schema_for!(MintConfig), &out_dir); + export_schema(&schema_for!(BurnableAsset), &out_dir); + export_schema(&schema_for!(SupportedAssetsResponse), &out_dir); + export_schema(&schema_for!(AssetResponse), &out_dir); } diff --git a/contracts/mirror_collateral_oracle/rustfmt.toml b/contracts/mint/rustfmt.toml similarity index 100% rename from contracts/mirror_collateral_oracle/rustfmt.toml rename to contracts/mint/rustfmt.toml diff --git a/contracts/mirror_community/schema/init_msg.json b/contracts/mint/schema/asset.json similarity index 66% rename from contracts/mirror_community/schema/init_msg.json rename to contracts/mint/schema/asset.json index 1fa2f91af..cf4d9b949 100644 --- a/contracts/mirror_community/schema/init_msg.json +++ b/contracts/mint/schema/asset.json @@ -1,21 +1,21 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InitMsg", + "title": "Asset", "type": "object", "required": [ - "mirror_token", - "owner", - "spend_limit" + "burned_tokens", + "code_hash", + "contract" ], "properties": { - "mirror_token": { - "$ref": "#/definitions/HumanAddr" + "burned_tokens": { + "$ref": "#/definitions/Uint128" }, - "owner": { - "$ref": "#/definitions/HumanAddr" + "code_hash": { + "type": "string" }, - "spend_limit": { - "$ref": "#/definitions/Uint128" + "contract": { + "$ref": "#/definitions/HumanAddr" } }, "definitions": { diff --git a/contracts/mint/schema/asset_response.json b/contracts/mint/schema/asset_response.json new file mode 100644 index 000000000..f74a6b34d --- /dev/null +++ b/contracts/mint/schema/asset_response.json @@ -0,0 +1,40 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AssetResponse", + "type": "object", + "required": [ + "asset" + ], + "properties": { + "asset": { + "$ref": "#/definitions/Asset" + } + }, + "definitions": { + "Asset": { + "type": "object", + "required": [ + "burned_tokens", + "code_hash", + "contract" + ], + "properties": { + "burned_tokens": { + "$ref": "#/definitions/Uint128" + }, + "code_hash": { + "type": "string" + }, + "contract": { + "$ref": "#/definitions/HumanAddr" + } + } + }, + "HumanAddr": { + "type": "string" + }, + "Uint128": { + "type": "string" + } + } +} diff --git a/contracts/mirror_collector/schema/init_msg.json b/contracts/mint/schema/config.json similarity index 54% rename from contracts/mirror_collector/schema/init_msg.json rename to contracts/mint/schema/config.json index 3bb10c0cf..a39eab5f0 100644 --- a/contracts/mirror_collector/schema/init_msg.json +++ b/contracts/mint/schema/config.json @@ -1,25 +1,29 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InitMsg", + "title": "Config", "type": "object", "required": [ - "base_denom", - "distribution_contract", - "mirror_token", - "terraswap_factory" + "oracle_contract", + "oracle_contract_code_hash", + "owner", + "silk_contract", + "silk_contract_code_hash" ], "properties": { - "base_denom": { + "oracle_contract": { + "$ref": "#/definitions/HumanAddr" + }, + "oracle_contract_code_hash": { "type": "string" }, - "distribution_contract": { + "owner": { "$ref": "#/definitions/HumanAddr" }, - "mirror_token": { + "silk_contract": { "$ref": "#/definitions/HumanAddr" }, - "terraswap_factory": { - "$ref": "#/definitions/HumanAddr" + "silk_contract_code_hash": { + "type": "string" } }, "definitions": { diff --git a/contracts/mirror_mint/schema/handle_response_for__empty.json b/contracts/mint/schema/handle_msg.json similarity index 64% rename from contracts/mirror_mint/schema/handle_response_for__empty.json rename to contracts/mint/schema/handle_msg.json index 9e41be313..07181e1e3 100644 --- a/contracts/mirror_mint/schema/handle_response_for__empty.json +++ b/contracts/mint/schema/handle_msg.json @@ -1,35 +1,130 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "HandleResponse_for_Empty", - "type": "object", - "required": [ - "log", - "messages" - ], - "properties": { - "data": { - "anyOf": [ - { - "$ref": "#/definitions/Binary" - }, - { - "type": "null" + "title": "HandleMsg", + "anyOf": [ + { + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "required": [ + "oracle_contract", + "oracle_contract_code_hash", + "owner", + "silk_contract", + "silk_contract_code_hash" + ], + "properties": { + "oracle_contract": { + "$ref": "#/definitions/HumanAddr" + }, + "oracle_contract_code_hash": { + "type": "string" + }, + "owner": { + "$ref": "#/definitions/HumanAddr" + }, + "silk_contract": { + "$ref": "#/definitions/HumanAddr" + }, + "silk_contract_code_hash": { + "type": "string" + } + } } - ] + } }, - "log": { - "type": "array", - "items": { - "$ref": "#/definitions/LogAttribute" + { + "type": "object", + "required": [ + "register_asset" + ], + "properties": { + "register_asset": { + "type": "object", + "required": [ + "code_hash", + "contract" + ], + "properties": { + "code_hash": { + "type": "string" + }, + "contract": { + "$ref": "#/definitions/HumanAddr" + } + } + } } }, - "messages": { - "type": "array", - "items": { - "$ref": "#/definitions/CosmosMsg_for_Empty" + { + "type": "object", + "required": [ + "update_asset" + ], + "properties": { + "update_asset": { + "type": "object", + "required": [ + "asset", + "code_hash", + "contract" + ], + "properties": { + "asset": { + "$ref": "#/definitions/HumanAddr" + }, + "code_hash": { + "type": "string" + }, + "contract": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "type": "object", + "required": [ + "receive" + ], + "properties": { + "receive": { + "type": "object", + "required": [ + "amount", + "from", + "sender" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "from": { + "$ref": "#/definitions/HumanAddr" + }, + "msg": { + "anyOf": [ + { + "$ref": "#/definitions/CosmosMsg_for_Empty" + }, + { + "type": "null" + } + ] + }, + "sender": { + "$ref": "#/definitions/HumanAddr" + } + } + } } } - }, + ], "definitions": { "BankMsg": { "anyOf": [ @@ -129,6 +224,17 @@ "$ref": "#/definitions/WasmMsg" } } + }, + { + "type": "object", + "required": [ + "gov" + ], + "properties": { + "gov": { + "$ref": "#/definitions/GovMsg" + } + } } ] }, @@ -136,24 +242,38 @@ "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", "type": "object" }, + "GovMsg": { + "anyOf": [ + { + "type": "object", + "required": [ + "vote" + ], + "properties": { + "vote": { + "type": "object", + "required": [ + "proposal", + "vote_option" + ], + "properties": { + "proposal": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vote_option": { + "$ref": "#/definitions/VoteOption" + } + } + } + } + } + ] + }, "HumanAddr": { "type": "string" }, - "LogAttribute": { - "type": "object", - "required": [ - "key", - "value" - ], - "properties": { - "key": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, "StakingMsg": { "anyOf": [ { @@ -264,6 +384,15 @@ "Uint128": { "type": "string" }, + "VoteOption": { + "type": "string", + "enum": [ + "Yes", + "No", + "Abstain", + "NoWithVeto" + ] + }, "WasmMsg": { "anyOf": [ { @@ -276,11 +405,16 @@ "execute": { "type": "object", "required": [ + "callback_code_hash", "contract_addr", "msg", "send" ], "properties": { + "callback_code_hash": { + "description": "callback_code_hash is the hex encoded hash of the code. This is used by Secret Network to harden against replaying the contract It is used to bind the request to a destination contract in a stronger way than just the contract address which can be faked", + "type": "string" + }, "contract_addr": { "$ref": "#/definitions/HumanAddr" }, @@ -312,22 +446,25 @@ "instantiate": { "type": "object", "required": [ + "callback_code_hash", "code_id", + "label", "msg", "send" ], "properties": { + "callback_code_hash": { + "description": "callback_code_hash is the hex encoded hash of the code. This is used by Secret Network to harden against replaying the contract It is used to bind the request to a destination contract in a stronger way than just the contract address which can be faked", + "type": "string" + }, "code_id": { "type": "integer", "format": "uint64", "minimum": 0.0 }, "label": { - "description": "optional human-readbale label for the contract", - "type": [ - "string", - "null" - ] + "description": "mandatory human-readbale label for the contract", + "type": "string" }, "msg": { "description": "msg is the json-encoded InitMsg struct (as raw Binary)", diff --git a/contracts/mirror_lock/schema/init_msg.json b/contracts/mint/schema/init_msg.json similarity index 58% rename from contracts/mirror_lock/schema/init_msg.json rename to contracts/mint/schema/init_msg.json index 2606bf3ee..7a9f29da9 100644 --- a/contracts/mirror_lock/schema/init_msg.json +++ b/contracts/mint/schema/init_msg.json @@ -3,25 +3,23 @@ "title": "InitMsg", "type": "object", "required": [ - "base_denom", - "lockup_period", - "mint_contract", - "owner" + "oracle_contract", + "oracle_contract_code_hash", + "silk_contract", + "silk_contract_code_hash" ], "properties": { - "base_denom": { - "type": "string" + "oracle_contract": { + "$ref": "#/definitions/HumanAddr" }, - "lockup_period": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 + "oracle_contract_code_hash": { + "type": "string" }, - "mint_contract": { + "silk_contract": { "$ref": "#/definitions/HumanAddr" }, - "owner": { - "$ref": "#/definitions/HumanAddr" + "silk_contract_code_hash": { + "type": "string" } }, "definitions": { diff --git a/contracts/mirror_lock/schema/query_msg.json b/contracts/mint/schema/query_msg.json similarity index 62% rename from contracts/mirror_lock/schema/query_msg.json rename to contracts/mint/schema/query_msg.json index b32e33d52..73e8d9378 100644 --- a/contracts/mirror_lock/schema/query_msg.json +++ b/contracts/mint/schema/query_msg.json @@ -5,10 +5,10 @@ { "type": "object", "required": [ - "config" + "get_supported_assets" ], "properties": { - "config": { + "get_supported_assets": { "type": "object" } } @@ -16,26 +16,21 @@ { "type": "object", "required": [ - "position_lock_info" + "get_asset" ], "properties": { - "position_lock_info": { + "get_asset": { "type": "object", "required": [ - "position_idx" + "contract" ], "properties": { - "position_idx": { - "$ref": "#/definitions/Uint128" + "contract": { + "type": "string" } } } } } - ], - "definitions": { - "Uint128": { - "type": "string" - } - } + ] } diff --git a/contracts/mint/schema/supported_assets_response.json b/contracts/mint/schema/supported_assets_response.json new file mode 100644 index 000000000..dd63512ae --- /dev/null +++ b/contracts/mint/schema/supported_assets_response.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SupportedAssetsResponse", + "type": "object", + "required": [ + "assets" + ], + "properties": { + "assets": { + "type": "array", + "items": { + "type": "string" + } + } + } +} diff --git a/contracts/mint/src/contract.rs b/contracts/mint/src/contract.rs new file mode 100644 index 000000000..7b4937921 --- /dev/null +++ b/contracts/mint/src/contract.rs @@ -0,0 +1,791 @@ +use cosmwasm_std::{debug_print, to_binary, Api, Binary, Env, Extern, HandleResponse, InitResponse, Querier, StdError, StdResult, Storage, CosmosMsg, HumanAddr, Uint128}; +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}, +}; +use shade_protocol::{ + mint::{InitMsg, HandleMsg, HandleAnswer, QueryMsg, QueryAnswer, AssetMsg, MintConfig, BurnableAsset}, + asset::{Contract}, + msg_traits::{Init, Query}, +}; +use shade_protocol::generic_response::ResponseStatus; + +// 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, + msg: InitMsg, +) -> StdResult { + let state = MintConfig { + owner: match msg.admin { + None => { env.message.sender.clone() } + Some(admin) => { admin } + }, + silk: msg.silk, + oracle: msg.oracle, + activated: true, + }; + + config(&mut deps.storage).save(&state)?; + + 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); + } + } + debug_print!("Contract was initialized by {}", env.message.sender); + + Ok(InitResponse { + messages: vec![], + log: vec![] + }) +} + +pub fn handle( + deps: &mut Extern, + env: Env, + msg: HandleMsg, +) -> StdResult { + match msg { + HandleMsg::Migrate { + code_id, + code_hash, + label + } => try_migrate(deps, env, label, code_id, code_hash), + HandleMsg::UpdateConfig { + owner, + silk, + oracle + } => try_update_config(deps, env, owner, silk, oracle), + HandleMsg::RegisterAsset { + contract, + } => try_register_asset(deps, &env, contract, None), + HandleMsg::UpdateAsset { + asset, + contract, + } => try_update_asset(deps, env, asset, contract), + HandleMsg::Receive { + sender, + from, + amount, + msg, + ..} => try_burn(deps, env, sender, from, amount, msg), + } +} + +pub fn try_migrate( + deps: &mut Extern, + env: Env, + label: String, + code_id: u64, + code_hash: String, +) -> StdResult { + if !authorized(deps, &env, AllowedAccess::Admin)? { + return Err(StdError::Unauthorized { backtrace: None }); + } + + let mut config = config(&mut deps.storage); + config.update(|mut state| { + state.activated = false; + Ok(state) + })?; + + 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), + }) + } + }; + + let init_msg = InitMsg { + admin: Option::from(config_read.owner), + silk: config_read.silk, + oracle: config_read.oracle, + initial_assets: Some(initial_assets) + }; + + Ok(HandleResponse { + messages: vec![init_msg.to_cosmos_msg(1, code_id, code_hash, label)?], + log: vec![], + data: Some( to_binary( &HandleAnswer::Migrate { + status: ResponseStatus::Success } )? ) + }) +} + +pub fn try_update_config( + deps: &mut Extern, + env: Env, + owner: Option, + silk: Option, + oracle: Option, +) -> 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; + } + if let Some(silk) = silk { + state.silk = silk; + } + if let Some(oracle) = oracle { + state.oracle = oracle; + } + Ok(state) + })?; + + Ok(HandleResponse { + messages: vec![], + log: vec![], + data: Some( to_binary( &HandleAnswer::UpdateAsset { + status: ResponseStatus::Success } )? ) + }) +} + +pub fn try_register_asset( + deps: &mut Extern, + env: &Env, + contract: Contract, + burned_amount: 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); + } + } + + 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 }), + } + + Ok(HandleResponse { + messages, + log: vec![], + data: Some( to_binary( &HandleAnswer::UpdateAsset { + status: ResponseStatus::Success } )? ) + }) +} + +pub fn try_burn( + deps: &mut Extern, + env: Env, + _sender: HumanAddr, + from: HumanAddr, + amount: Uint128, + _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) + })?; + }, + + None => return Err(StdError::NotFound { kind: env.message.sender.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 + + // TODO: make this a function that way it can be tested + + // Returned value is x * 10**18 + let token_value = Uint128(call_oracle(deps)?.into()); + + // 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 mint_msg = mint_silk(deps, from, value_to_mint)?; + messages.push(mint_msg); + + Ok(HandleResponse { + messages, + log: vec![], + data: Some( to_binary( &HandleAnswer::Burn { + status: ResponseStatus::Success, + mint_amount: value_to_mint + } )? ), + }) +} + +// 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()?; + // Check if contract is still activated + if !config.activated { + return Ok(false) + } + + if access == AllowedAccess::Admin { + // Check if admin + if env.message.sender != config.owner { + return Ok(false) + } + } + 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, +) -> StdResult { + let cosmos_msg = register_receive_msg( + env.contract_code_hash.clone(), + None, + 256, + code_hash, + contract, + ); + + cosmos_msg +} + +fn mint_silk( + deps: &Extern, + sender: HumanAddr, + amount: Uint128, +) -> StdResult { + let config = config_read(&deps.storage).load()?; + + let cosmos_msg = mint_msg( + sender, + amount, + None, + 256, + config.silk.code_hash, + config.silk.address, + ); + + cosmos_msg +} + +fn call_oracle( + deps: &mut Extern, +) -> 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, + config.oracle.code_hash, + config.oracle.address)?; + + let value = answer.price; + Ok(value) +} + +pub fn query( + deps: &Extern, + msg: QueryMsg, +) -> StdResult { + match msg { + QueryMsg::GetSupportedAssets {} => to_binary(&query_supported_assets(deps)?), + QueryMsg::GetAsset { contract } => to_binary(&query_asset(deps, contract)?), + QueryMsg::GetConfig {} => to_binary(&query_config(deps)?), + } +} + +fn query_supported_assets(deps: &Extern) -> StdResult { + Ok(QueryAnswer::SupportedAssets { assets: asset_list_read(&deps.storage).load()? }) +} + +fn query_asset(deps: &Extern, contract: String) -> StdResult { + let assets = assets_r(&deps.storage); + + return match assets.may_load(contract.as_bytes())? { + Some(asset) => Ok(QueryAnswer::Asset { asset }), + None => Err(StdError::NotFound { kind: contract, backtrace: None }), + }; +} + +fn query_config(deps: &Extern) -> StdResult { + Ok(QueryAnswer::Config { config: config_read(&deps.storage).load()? }) +} + +#[cfg(test)] +mod tests { + use super::*; + use cosmwasm_std::testing::{mock_dependencies, mock_env, MockStorage, MockApi, MockQuerier}; + use cosmwasm_std::{coins, from_binary, StdError}; + use shade_protocol::mint::QueryAnswer; + + 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: String, silk: Contract, oracle: Contract) -> Extern { + let mut deps = mock_dependencies(20, &[]); + let msg = InitMsg { + admin: None, + silk, + oracle, + initial_assets: None + }; + let env = mock_env(admin, &coins(1000, "earth")); + let _res = init(&mut deps, env, msg).unwrap(); + + return deps + } + + #[test] + fn proper_initialization() { + let mut deps = mock_dependencies(20, &[]); + let msg = InitMsg { + admin: None, + silk: create_contract("", ""), + oracle: create_contract("", ""), + initial_assets: None + }; + 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()); + } + + #[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); + + // 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); + + // 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); + + } + _ => { panic!("Received wrong answer") } + } + + } + + #[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 { + contract: dummy_contract, + }; + let res = handle(&mut deps, user_env, msg); + match res { + Err(StdError::Unauthorized { .. }) => {} + _ => panic!("Must return unauthorized error"), + } + + // Response should be an empty array + let res = query(&deps, QueryMsg::GetSupportedAssets {}).unwrap(); + let value: QueryAnswer = from_binary(&res).unwrap(); + match value { + QueryAnswer::SupportedAssets { assets } => { assert_eq!(0, assets.len()) } + _ => { panic!("Received wrong answer") } + } + } + + #[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 { + contract: dummy_contract, + }; + let _res = handle(&mut deps, env, msg).unwrap(); + + // Response should be an array of size 1 + let res = query(&deps, QueryMsg::GetSupportedAssets {}).unwrap(); + let value: QueryAnswer = from_binary(&res).unwrap(); + match value { + QueryAnswer::SupportedAssets { assets } => { assert_eq!(1, assets.len()) } + _ => { panic!("Received wrong answer") } + } + } + + #[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 { + contract: dummy_contract, + }; + 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 _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 value: QueryAnswer = from_binary(&res).unwrap(); + match value { + QueryAnswer::Asset { asset } => { assert_eq!("some_other_contract".to_string(), asset.contract.address.to_string()) } + _ => { 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 expected_value = Uint128(1_000); + let value = calculate_mint(price, sent_value, 6, 3); + + assert_eq!(value, expected_value); + } + + #[test] + fn mint_algorithm_complex() { + // 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 expected_value = Uint128(3_600_000_000_000); + let value = calculate_mint(price, sent_value, 6, 12); + + assert_eq!(value, expected_value); + } +} diff --git a/contracts/mint/src/lib.rs b/contracts/mint/src/lib.rs new file mode 100644 index 000000000..18d548e3f --- /dev/null +++ b/contracts/mint/src/lib.rs @@ -0,0 +1,39 @@ +pub mod contract; +pub mod state; + +#[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/mint/src/state.rs b/contracts/mint/src/state.rs new file mode 100644 index 000000000..c00b9cd48 --- /dev/null +++ b/contracts/mint/src/state.rs @@ -0,0 +1,34 @@ +use cosmwasm_std::{Storage}; +use cosmwasm_storage::{singleton, singleton_read, ReadonlySingleton, Singleton, bucket, Bucket, bucket_read, ReadonlyBucket}; +use shade_protocol::{ + mint::{MintConfig, BurnableAsset}, +}; + +pub static CONFIG_KEY: &[u8] = b"config"; +pub static NATIVE_COIN_KEY: &[u8] = b"native_coin"; +pub static ASSET_KEY: &[u8] = b"assets"; +pub static ASSET_LIST_KEY: &[u8] = b"asset_list"; + +pub fn config(storage: &mut S) -> Singleton { + singleton(storage, CONFIG_KEY) +} + +pub fn config_read(storage: &S) -> ReadonlySingleton { + singleton_read(storage, CONFIG_KEY) +} + +pub fn asset_list(storage: &mut S) -> Singleton> { + singleton(storage, ASSET_LIST_KEY) +} + +pub fn asset_list_read(storage: &S) -> ReadonlySingleton> { + singleton_read(storage, ASSET_LIST_KEY) +} + +pub fn assets_r(storage: & S) -> ReadonlyBucket { + bucket_read(ASSET_KEY, storage) +} + +pub fn assets_w(storage: &mut S) -> Bucket { + bucket(ASSET_KEY, storage) +} \ No newline at end of file diff --git a/contracts/mint/tests/integration.rs b/contracts/mint/tests/integration.rs new file mode 100644 index 000000000..6c26b034f --- /dev/null +++ b/contracts/mint/tests/integration.rs @@ -0,0 +1,18 @@ +//! This integration test tries to run and call the generated wasm. +//! It depends on a Wasm build being available, which you can create with `cargo wasm`. +//! Then running `cargo integration-test` will validate we can properly call into that generated Wasm. +//! +//! You can easily convert unit tests to integration tests. +//! 1. First copy them over verbatum, +//! 2. Then change +//! let mut deps = mock_dependencies(20, &[]); +//! to +//! let mut deps = mock_instance(WASM, &[]); +//! 3. If you access raw storage, where ever you see something like: +//! deps.storage.get(CONFIG_KEY).expect("no data stored"); +//! replace it with: +//! deps.with_storage(|store| { +//! let data = store.get(CONFIG_KEY).expect("no data stored"); +//! //... +//! }); +//! 4. Anywhere you see query(&deps, ...) you must replace it with query(&mut deps, ...) diff --git a/contracts/mirror_collateral_oracle/.editorconfig b/contracts/mirror_collateral_oracle/.editorconfig deleted file mode 100644 index 3d36f20b1..000000000 --- a/contracts/mirror_collateral_oracle/.editorconfig +++ /dev/null @@ -1,11 +0,0 @@ -root = true - -[*] -indent_style = space -indent_size = 2 -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true - -[*.rs] -indent_size = 4 diff --git a/contracts/mirror_collateral_oracle/.gitignore b/contracts/mirror_collateral_oracle/.gitignore deleted file mode 100644 index 10fe5d615..000000000 --- a/contracts/mirror_collateral_oracle/.gitignore +++ /dev/null @@ -1,12 +0,0 @@ -# Build results -/target - -# Text file backups -**/*.rs.bk - -# macOS -.DS_Store - -# IDEs -*.iml -.idea diff --git a/contracts/mirror_collateral_oracle/README.md b/contracts/mirror_collateral_oracle/README.md deleted file mode 100644 index 9b442f635..000000000 --- a/contracts/mirror_collateral_oracle/README.md +++ /dev/null @@ -1 +0,0 @@ -# Mirror Collateral Oracle diff --git a/contracts/mirror_collateral_oracle/examples/schema.rs b/contracts/mirror_collateral_oracle/examples/schema.rs deleted file mode 100644 index 43a96679f..000000000 --- a/contracts/mirror_collateral_oracle/examples/schema.rs +++ /dev/null @@ -1,24 +0,0 @@ -use std::env::current_dir; -use std::fs::create_dir_all; - -use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; -use mirror_protocol::collateral_oracle::{ - CollateralInfoResponse, CollateralInfosResponse, CollateralPriceResponse, ConfigResponse, - HandleMsg, InitMsg, MigrateMsg, QueryMsg, -}; - -fn main() { - let mut out_dir = current_dir().unwrap(); - out_dir.push("schema"); - create_dir_all(&out_dir).unwrap(); - remove_schemas(&out_dir).unwrap(); - - export_schema(&schema_for!(InitMsg), &out_dir); - export_schema(&schema_for!(HandleMsg), &out_dir); - export_schema(&schema_for!(QueryMsg), &out_dir); - export_schema(&schema_for!(MigrateMsg), &out_dir); - export_schema(&schema_for!(CollateralInfoResponse), &out_dir); - export_schema(&schema_for!(ConfigResponse), &out_dir); - export_schema(&schema_for!(CollateralInfosResponse), &out_dir); - export_schema(&schema_for!(CollateralPriceResponse), &out_dir); -} diff --git a/contracts/mirror_collateral_oracle/schema/collateral_info_response.json b/contracts/mirror_collateral_oracle/schema/collateral_info_response.json deleted file mode 100644 index e8f0aab19..000000000 --- a/contracts/mirror_collateral_oracle/schema/collateral_info_response.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "CollateralInfoResponse", - "type": "object", - "required": [ - "asset", - "is_revoked", - "multiplier", - "source_type" - ], - "properties": { - "asset": { - "type": "string" - }, - "is_revoked": { - "type": "boolean" - }, - "multiplier": { - "$ref": "#/definitions/Decimal" - }, - "source_type": { - "type": "string" - } - }, - "definitions": { - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - } - } -} diff --git a/contracts/mirror_collateral_oracle/schema/collateral_infos_response.json b/contracts/mirror_collateral_oracle/schema/collateral_infos_response.json deleted file mode 100644 index 6d2b20d68..000000000 --- a/contracts/mirror_collateral_oracle/schema/collateral_infos_response.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "CollateralInfosResponse", - "type": "object", - "required": [ - "collaterals" - ], - "properties": { - "collaterals": { - "type": "array", - "items": { - "$ref": "#/definitions/CollateralInfoResponse" - } - } - }, - "definitions": { - "CollateralInfoResponse": { - "type": "object", - "required": [ - "asset", - "is_revoked", - "multiplier", - "source_type" - ], - "properties": { - "asset": { - "type": "string" - }, - "is_revoked": { - "type": "boolean" - }, - "multiplier": { - "$ref": "#/definitions/Decimal" - }, - "source_type": { - "type": "string" - } - } - }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - } - } -} diff --git a/contracts/mirror_collateral_oracle/schema/collateral_price_response.json b/contracts/mirror_collateral_oracle/schema/collateral_price_response.json deleted file mode 100644 index 44c9eaccf..000000000 --- a/contracts/mirror_collateral_oracle/schema/collateral_price_response.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "CollateralPriceResponse", - "type": "object", - "required": [ - "asset", - "is_revoked", - "last_updated", - "multiplier", - "rate" - ], - "properties": { - "asset": { - "type": "string" - }, - "is_revoked": { - "type": "boolean" - }, - "last_updated": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "multiplier": { - "$ref": "#/definitions/Decimal" - }, - "rate": { - "$ref": "#/definitions/Decimal" - } - }, - "definitions": { - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - } - } -} diff --git a/contracts/mirror_collateral_oracle/schema/config_response.json b/contracts/mirror_collateral_oracle/schema/config_response.json deleted file mode 100644 index ccb201f63..000000000 --- a/contracts/mirror_collateral_oracle/schema/config_response.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ConfigResponse", - "type": "object", - "required": [ - "anchor_oracle", - "band_oracle", - "base_denom", - "factory_contract", - "mint_contract", - "mirror_oracle", - "owner" - ], - "properties": { - "anchor_oracle": { - "$ref": "#/definitions/HumanAddr" - }, - "band_oracle": { - "$ref": "#/definitions/HumanAddr" - }, - "base_denom": { - "type": "string" - }, - "factory_contract": { - "$ref": "#/definitions/HumanAddr" - }, - "mint_contract": { - "$ref": "#/definitions/HumanAddr" - }, - "mirror_oracle": { - "$ref": "#/definitions/HumanAddr" - }, - "owner": { - "$ref": "#/definitions/HumanAddr" - } - }, - "definitions": { - "HumanAddr": { - "type": "string" - } - } -} diff --git a/contracts/mirror_collateral_oracle/schema/handle_msg.json b/contracts/mirror_collateral_oracle/schema/handle_msg.json deleted file mode 100644 index 62a0c30c0..000000000 --- a/contracts/mirror_collateral_oracle/schema/handle_msg.json +++ /dev/null @@ -1,347 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "HandleMsg", - "anyOf": [ - { - "type": "object", - "required": [ - "update_config" - ], - "properties": { - "update_config": { - "type": "object", - "properties": { - "anchor_oracle": { - "anyOf": [ - { - "$ref": "#/definitions/HumanAddr" - }, - { - "type": "null" - } - ] - }, - "band_oracle": { - "anyOf": [ - { - "$ref": "#/definitions/HumanAddr" - }, - { - "type": "null" - } - ] - }, - "base_denom": { - "type": [ - "string", - "null" - ] - }, - "factory_contract": { - "anyOf": [ - { - "$ref": "#/definitions/HumanAddr" - }, - { - "type": "null" - } - ] - }, - "mint_contract": { - "anyOf": [ - { - "$ref": "#/definitions/HumanAddr" - }, - { - "type": "null" - } - ] - }, - "mirror_oracle": { - "anyOf": [ - { - "$ref": "#/definitions/HumanAddr" - }, - { - "type": "null" - } - ] - }, - "owner": { - "anyOf": [ - { - "$ref": "#/definitions/HumanAddr" - }, - { - "type": "null" - } - ] - } - } - } - } - }, - { - "type": "object", - "required": [ - "register_collateral_asset" - ], - "properties": { - "register_collateral_asset": { - "type": "object", - "required": [ - "asset", - "multiplier", - "price_source" - ], - "properties": { - "asset": { - "$ref": "#/definitions/AssetInfo" - }, - "multiplier": { - "$ref": "#/definitions/Decimal" - }, - "price_source": { - "$ref": "#/definitions/SourceType" - } - } - } - } - }, - { - "type": "object", - "required": [ - "revoke_collateral_asset" - ], - "properties": { - "revoke_collateral_asset": { - "type": "object", - "required": [ - "asset" - ], - "properties": { - "asset": { - "$ref": "#/definitions/AssetInfo" - } - } - } - } - }, - { - "type": "object", - "required": [ - "update_collateral_price_source" - ], - "properties": { - "update_collateral_price_source": { - "type": "object", - "required": [ - "asset", - "price_source" - ], - "properties": { - "asset": { - "$ref": "#/definitions/AssetInfo" - }, - "price_source": { - "$ref": "#/definitions/SourceType" - } - } - } - } - }, - { - "type": "object", - "required": [ - "update_collateral_multiplier" - ], - "properties": { - "update_collateral_multiplier": { - "type": "object", - "required": [ - "asset", - "multiplier" - ], - "properties": { - "asset": { - "$ref": "#/definitions/AssetInfo" - }, - "multiplier": { - "$ref": "#/definitions/Decimal" - } - } - } - } - } - ], - "definitions": { - "AssetInfo": { - "anyOf": [ - { - "type": "object", - "required": [ - "token" - ], - "properties": { - "token": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "$ref": "#/definitions/HumanAddr" - } - } - } - } - }, - { - "type": "object", - "required": [ - "native_token" - ], - "properties": { - "native_token": { - "type": "object", - "required": [ - "denom" - ], - "properties": { - "denom": { - "type": "string" - } - } - } - } - } - ] - }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "HumanAddr": { - "type": "string" - }, - "SourceType": { - "anyOf": [ - { - "type": "object", - "required": [ - "mirror_oracle" - ], - "properties": { - "mirror_oracle": { - "type": "object" - } - } - }, - { - "type": "object", - "required": [ - "anchor_oracle" - ], - "properties": { - "anchor_oracle": { - "type": "object" - } - } - }, - { - "type": "object", - "required": [ - "band_oracle" - ], - "properties": { - "band_oracle": { - "type": "object" - } - } - }, - { - "type": "object", - "required": [ - "fixed_price" - ], - "properties": { - "fixed_price": { - "type": "object", - "required": [ - "price" - ], - "properties": { - "price": { - "$ref": "#/definitions/Decimal" - } - } - } - } - }, - { - "type": "object", - "required": [ - "terraswap" - ], - "properties": { - "terraswap": { - "type": "object", - "required": [ - "terraswap_pair_addr" - ], - "properties": { - "intermediate_denom": { - "type": [ - "string", - "null" - ] - }, - "terraswap_pair_addr": { - "$ref": "#/definitions/HumanAddr" - } - } - } - } - }, - { - "type": "object", - "required": [ - "anchor_market" - ], - "properties": { - "anchor_market": { - "type": "object", - "required": [ - "anchor_market_addr" - ], - "properties": { - "anchor_market_addr": { - "$ref": "#/definitions/HumanAddr" - } - } - } - } - }, - { - "type": "object", - "required": [ - "native" - ], - "properties": { - "native": { - "type": "object", - "required": [ - "native_denom" - ], - "properties": { - "native_denom": { - "type": "string" - } - } - } - } - } - ] - } - } -} diff --git a/contracts/mirror_collateral_oracle/schema/init_msg.json b/contracts/mirror_collateral_oracle/schema/init_msg.json deleted file mode 100644 index 992250af5..000000000 --- a/contracts/mirror_collateral_oracle/schema/init_msg.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InitMsg", - "type": "object", - "required": [ - "anchor_oracle", - "band_oracle", - "base_denom", - "factory_contract", - "mint_contract", - "mirror_oracle", - "owner" - ], - "properties": { - "anchor_oracle": { - "$ref": "#/definitions/HumanAddr" - }, - "band_oracle": { - "$ref": "#/definitions/HumanAddr" - }, - "base_denom": { - "type": "string" - }, - "factory_contract": { - "$ref": "#/definitions/HumanAddr" - }, - "mint_contract": { - "$ref": "#/definitions/HumanAddr" - }, - "mirror_oracle": { - "$ref": "#/definitions/HumanAddr" - }, - "owner": { - "$ref": "#/definitions/HumanAddr" - } - }, - "definitions": { - "HumanAddr": { - "type": "string" - } - } -} diff --git a/contracts/mirror_collateral_oracle/schema/migrate_msg.json b/contracts/mirror_collateral_oracle/schema/migrate_msg.json deleted file mode 100644 index 666fb7e9f..000000000 --- a/contracts/mirror_collateral_oracle/schema/migrate_msg.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "MigrateMsg", - "description": "We currently take no arguments for migrations", - "type": "object" -} diff --git a/contracts/mirror_collateral_oracle/schema/query_msg.json b/contracts/mirror_collateral_oracle/schema/query_msg.json deleted file mode 100644 index 0bc4ceeeb..000000000 --- a/contracts/mirror_collateral_oracle/schema/query_msg.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryMsg", - "anyOf": [ - { - "type": "object", - "required": [ - "config" - ], - "properties": { - "config": { - "type": "object" - } - } - }, - { - "type": "object", - "required": [ - "collateral_price" - ], - "properties": { - "collateral_price": { - "type": "object", - "required": [ - "asset" - ], - "properties": { - "asset": { - "type": "string" - } - } - } - } - }, - { - "type": "object", - "required": [ - "collateral_asset_info" - ], - "properties": { - "collateral_asset_info": { - "type": "object", - "required": [ - "asset" - ], - "properties": { - "asset": { - "type": "string" - } - } - } - } - }, - { - "type": "object", - "required": [ - "collateral_asset_infos" - ], - "properties": { - "collateral_asset_infos": { - "type": "object" - } - } - } - ] -} diff --git a/contracts/mirror_collateral_oracle/src/contract.rs b/contracts/mirror_collateral_oracle/src/contract.rs deleted file mode 100644 index 1a746f05d..000000000 --- a/contracts/mirror_collateral_oracle/src/contract.rs +++ /dev/null @@ -1,333 +0,0 @@ -use crate::querier::query_price; -use crate::state::{ - read_collateral_info, read_collateral_infos, read_config, store_collateral_info, store_config, - CollateralAssetInfo, Config, -}; -use cosmwasm_std::{ - to_binary, Api, Binary, CanonicalAddr, Decimal, Env, Extern, HandleResponse, HandleResult, - HumanAddr, InitResponse, MigrateResponse, MigrateResult, Querier, StdError, StdResult, Storage, -}; - -use mirror_protocol::collateral_oracle::{ - CollateralInfoResponse, CollateralInfosResponse, CollateralPriceResponse, ConfigResponse, - HandleMsg, InitMsg, MigrateMsg, QueryMsg, SourceType, -}; - -use terraswap::asset::AssetInfo; - -pub fn init( - deps: &mut Extern, - _env: Env, - msg: InitMsg, -) -> StdResult { - store_config( - &mut deps.storage, - &Config { - owner: deps.api.canonical_address(&msg.owner)?, - mint_contract: deps.api.canonical_address(&msg.mint_contract)?, - factory_contract: deps.api.canonical_address(&msg.factory_contract)?, - base_denom: msg.base_denom, - mirror_oracle: deps.api.canonical_address(&msg.mirror_oracle)?, - anchor_oracle: deps.api.canonical_address(&msg.anchor_oracle)?, - band_oracle: deps.api.canonical_address(&msg.band_oracle)?, - }, - )?; - - Ok(InitResponse::default()) -} - -pub fn handle( - deps: &mut Extern, - env: Env, - msg: HandleMsg, -) -> HandleResult { - match msg { - HandleMsg::UpdateConfig { - owner, - mint_contract, - factory_contract, - base_denom, - mirror_oracle, - anchor_oracle, - band_oracle, - } => update_config( - deps, - env, - owner, - mint_contract, - factory_contract, - base_denom, - mirror_oracle, - anchor_oracle, - band_oracle, - ), - HandleMsg::RegisterCollateralAsset { - asset, - price_source, - multiplier, - } => register_collateral(deps, env, asset, price_source, multiplier), - HandleMsg::RevokeCollateralAsset { asset } => revoke_collateral(deps, env, asset), - HandleMsg::UpdateCollateralPriceSource { - asset, - price_source, - } => update_collateral_source(deps, env, asset, price_source), - HandleMsg::UpdateCollateralMultiplier { asset, multiplier } => { - update_collateral_multiplier(deps, env, asset, multiplier) - } - } -} - -pub fn update_config( - deps: &mut Extern, - env: Env, - owner: Option, - mint_contract: Option, - factory_contract: Option, - base_denom: Option, - mirror_oracle: Option, - anchor_oracle: Option, - band_oracle: Option, -) -> HandleResult { - let mut config: Config = read_config(&deps.storage)?; - if deps.api.canonical_address(&env.message.sender)? != config.owner { - return Err(StdError::unauthorized()); - } - - if let Some(owner) = owner { - config.owner = deps.api.canonical_address(&owner)?; - } - - if let Some(mint_contract) = mint_contract { - config.mint_contract = deps.api.canonical_address(&mint_contract)?; - } - - if let Some(factory_contract) = factory_contract { - config.factory_contract = deps.api.canonical_address(&factory_contract)?; - } - - if let Some(base_denom) = base_denom { - config.base_denom = base_denom; - } - - if let Some(mirror_oracle) = mirror_oracle { - config.mirror_oracle = deps.api.canonical_address(&mirror_oracle)?; - } - - if let Some(anchor_oracle) = anchor_oracle { - config.anchor_oracle = deps.api.canonical_address(&anchor_oracle)?; - } - - if let Some(band_oracle) = band_oracle { - config.band_oracle = deps.api.canonical_address(&band_oracle)?; - } - - store_config(&mut deps.storage, &config)?; - Ok(HandleResponse::default()) -} - -pub fn register_collateral( - deps: &mut Extern, - env: Env, - asset: AssetInfo, - price_source: SourceType, - multiplier: Decimal, -) -> HandleResult { - let config: Config = read_config(&deps.storage)?; - let sender_address_raw: CanonicalAddr = deps.api.canonical_address(&env.message.sender)?; - // only contract onwner and mint contract can register a new collateral - if config.owner != sender_address_raw && config.mint_contract != sender_address_raw { - return Err(StdError::unauthorized()); - } - - if read_collateral_info(&deps.storage, &asset.to_string()).is_ok() { - return Err(StdError::generic_err("Collateral was already registered")); - } - - if multiplier.is_zero() { - return Err(StdError::generic_err("Multiplier must be bigger than 0")); - } - - store_collateral_info( - &mut deps.storage, - &CollateralAssetInfo { - asset: asset.to_string(), - multiplier, - price_source, - is_revoked: false, - }, - )?; - - Ok(HandleResponse::default()) -} - -pub fn revoke_collateral( - deps: &mut Extern, - env: Env, - asset: AssetInfo, -) -> HandleResult { - let config: Config = read_config(&deps.storage)?; - let sender_address_raw: CanonicalAddr = deps.api.canonical_address(&env.message.sender)?; - // only owner and mint contract can revoke a collateral assets - if config.owner != sender_address_raw && config.mint_contract != sender_address_raw { - return Err(StdError::unauthorized()); - } - - let mut collateral_info: CollateralAssetInfo = - if let Ok(collateral) = read_collateral_info(&deps.storage, &asset.to_string()) { - collateral - } else { - return Err(StdError::generic_err("Collateral not found")); - }; - - collateral_info.is_revoked = true; - - store_collateral_info(&mut deps.storage, &collateral_info)?; - - Ok(HandleResponse::default()) -} - -pub fn update_collateral_source( - deps: &mut Extern, - env: Env, - asset: AssetInfo, - price_source: SourceType, -) -> HandleResult { - let config: Config = read_config(&deps.storage)?; - let sender_address_raw: CanonicalAddr = deps.api.canonical_address(&env.message.sender)?; - // only contract onwner can update collateral query - if config.owner != sender_address_raw { - return Err(StdError::unauthorized()); - } - - let mut collateral_info: CollateralAssetInfo = - if let Ok(collateral) = read_collateral_info(&deps.storage, &asset.to_string()) { - collateral - } else { - return Err(StdError::generic_err("Collateral not found")); - }; - - collateral_info.price_source = price_source; - - store_collateral_info(&mut deps.storage, &collateral_info)?; - - Ok(HandleResponse::default()) -} - -pub fn update_collateral_multiplier( - deps: &mut Extern, - env: Env, - asset: AssetInfo, - multiplier: Decimal, -) -> HandleResult { - let config: Config = read_config(&deps.storage)?; - let sender_address_raw: CanonicalAddr = deps.api.canonical_address(&env.message.sender)?; - // only factory contract can update collateral premium - if config.factory_contract != sender_address_raw { - return Err(StdError::unauthorized()); - } - - let mut collateral_info: CollateralAssetInfo = - if let Ok(collateral) = read_collateral_info(&deps.storage, &asset.to_string()) { - collateral - } else { - return Err(StdError::generic_err("Collateral not found")); - }; - - if multiplier.is_zero() { - return Err(StdError::generic_err("Multiplier must be bigger than 0")); - } - - collateral_info.multiplier = multiplier; - store_collateral_info(&mut deps.storage, &collateral_info)?; - - Ok(HandleResponse::default()) -} - -pub fn query( - deps: &Extern, - msg: QueryMsg, -) -> StdResult { - match msg { - QueryMsg::Config {} => to_binary(&query_config(deps)?), - QueryMsg::CollateralPrice { asset } => to_binary(&query_collateral_price(deps, asset)?), - QueryMsg::CollateralAssetInfo { asset } => to_binary(&query_collateral_info(deps, asset)?), - QueryMsg::CollateralAssetInfos {} => to_binary(&query_collateral_infos(deps)?), - } -} - -pub fn query_config( - deps: &Extern, -) -> StdResult { - let config = read_config(&deps.storage)?; - let resp = ConfigResponse { - owner: deps.api.human_address(&config.owner)?, - mint_contract: deps.api.human_address(&config.mint_contract)?, - factory_contract: deps.api.human_address(&config.factory_contract)?, - base_denom: config.base_denom, - mirror_oracle: deps.api.human_address(&config.mirror_oracle)?, - anchor_oracle: deps.api.human_address(&config.anchor_oracle)?, - band_oracle: deps.api.human_address(&config.band_oracle)?, - }; - - Ok(resp) -} - -pub fn query_collateral_price( - deps: &Extern, - quote_asset: String, -) -> StdResult { - let config: Config = read_config(&deps.storage)?; - - let collateral: CollateralAssetInfo = - if let Ok(res) = read_collateral_info(&deps.storage, "e_asset) { - res - } else { - return Err(StdError::generic_err("Collateral asset not found")); - }; - - let (price, last_updated): (Decimal, u64) = - query_price(deps, &config, "e_asset, &collateral.price_source)?; - - Ok(CollateralPriceResponse { - asset: collateral.asset, - rate: price, - last_updated, - multiplier: collateral.multiplier, - is_revoked: collateral.is_revoked, - }) -} - -pub fn query_collateral_info( - deps: &Extern, - quote_asset: String, -) -> StdResult { - let collateral: CollateralAssetInfo = - if let Ok(res) = read_collateral_info(&deps.storage, "e_asset) { - res - } else { - return Err(StdError::generic_err("Collateral asset not found")); - }; - - Ok(CollateralInfoResponse { - asset: collateral.asset, - source_type: collateral.price_source.to_string(), - multiplier: collateral.multiplier, - is_revoked: collateral.is_revoked, - }) -} - -pub fn query_collateral_infos( - deps: &Extern, -) -> StdResult { - let infos: Vec = read_collateral_infos(&deps.storage)?; - - Ok(CollateralInfosResponse { collaterals: infos }) -} - -pub fn migrate( - _deps: &mut Extern, - _env: Env, - _msg: MigrateMsg, -) -> MigrateResult { - Ok(MigrateResponse::default()) -} diff --git a/contracts/mirror_collateral_oracle/src/lib.rs b/contracts/mirror_collateral_oracle/src/lib.rs deleted file mode 100644 index 6b2695c89..000000000 --- a/contracts/mirror_collateral_oracle/src/lib.rs +++ /dev/null @@ -1,13 +0,0 @@ -pub mod contract; -pub mod math; -pub mod querier; -pub mod state; - -#[cfg(test)] -mod testing; - -#[cfg(test)] -mod mock_querier; - -#[cfg(target_arch = "wasm32")] -cosmwasm_std::create_entry_points_with_migration!(contract); diff --git a/contracts/mirror_collateral_oracle/src/math.rs b/contracts/mirror_collateral_oracle/src/math.rs deleted file mode 100644 index 7cafbb1bf..000000000 --- a/contracts/mirror_collateral_oracle/src/math.rs +++ /dev/null @@ -1,12 +0,0 @@ -use cosmwasm_std::{Decimal, Uint128}; - -const DECIMAL_FRACTIONAL: Uint128 = Uint128(1_000_000_000u128); - -/// return a / b -pub fn decimal_division(a: Decimal, b: Decimal) -> Decimal { - Decimal::from_ratio(DECIMAL_FRACTIONAL * a, b * DECIMAL_FRACTIONAL) -} - -pub fn decimal_multiplication(a: Decimal, b: Decimal) -> Decimal { - Decimal::from_ratio(a * DECIMAL_FRACTIONAL * b, DECIMAL_FRACTIONAL) -} diff --git a/contracts/mirror_collateral_oracle/src/mock_querier.rs b/contracts/mirror_collateral_oracle/src/mock_querier.rs deleted file mode 100644 index 6f3ddc1be..000000000 --- a/contracts/mirror_collateral_oracle/src/mock_querier.rs +++ /dev/null @@ -1,249 +0,0 @@ -use cosmwasm_std::testing::{MockApi, MockQuerier, MockStorage, MOCK_CONTRACT_ADDR}; -use cosmwasm_std::{ - from_binary, from_slice, to_binary, Coin, Decimal, Extern, HumanAddr, Querier, QuerierResult, - QueryRequest, SystemError, Uint128, WasmQuery, -}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::str::FromStr; - -use crate::math::decimal_division; -use cosmwasm_bignumber::{Decimal256, Uint256}; -use mirror_protocol::oracle::PriceResponse; -use terra_cosmwasm::{ - ExchangeRateItem, ExchangeRatesResponse, TerraQuery, TerraQueryWrapper, TerraRoute, -}; -use terraswap::asset::{Asset, AssetInfo}; -use terraswap::pair::PoolResponse; - -/// mock_dependencies is a drop-in replacement for cosmwasm_std::testing::mock_dependencies -/// this uses our CustomQuerier. -pub fn mock_dependencies( - canonical_length: usize, - contract_balance: &[Coin], -) -> Extern { - let contract_addr = HumanAddr::from(MOCK_CONTRACT_ADDR); - let custom_querier: WasmMockQuerier = - WasmMockQuerier::new(MockQuerier::new(&[(&contract_addr, contract_balance)])); - - Extern { - storage: MockStorage::default(), - api: MockApi::new(canonical_length), - querier: custom_querier, - } -} - -pub struct WasmMockQuerier { - base: MockQuerier, - oracle_price_querier: OraclePriceQuerier, - terraswap_pools_querier: TerraswapPoolsQuerier, -} - -#[derive(Clone, Default)] -pub struct OraclePriceQuerier { - // this lets us iterate over all pairs that match the first string - oracle_price: HashMap, -} - -impl OraclePriceQuerier { - pub fn new(oracle_price: &[(&String, &Decimal)]) -> Self { - OraclePriceQuerier { - oracle_price: oracle_price_to_map(oracle_price), - } - } -} - -pub(crate) fn oracle_price_to_map( - oracle_price: &[(&String, &Decimal)], -) -> HashMap { - let mut oracle_price_map: HashMap = HashMap::new(); - for (base_quote, oracle_price) in oracle_price.iter() { - oracle_price_map.insert((*base_quote).clone(), **oracle_price); - } - - oracle_price_map -} - -#[derive(Clone, Default)] -pub struct TerraswapPoolsQuerier { - pools: HashMap, -} - -impl TerraswapPoolsQuerier { - pub fn new(pools: &[(&HumanAddr, (&String, &Uint128, &String, &Uint128))]) -> Self { - TerraswapPoolsQuerier { - pools: pools_to_map(pools), - } - } -} - -pub(crate) fn pools_to_map( - pools: &[(&HumanAddr, (&String, &Uint128, &String, &Uint128))], -) -> HashMap { - let mut pools_map: HashMap = HashMap::new(); - for (key, pool) in pools.into_iter() { - pools_map.insert( - HumanAddr::from(key), - (pool.0.clone(), *pool.1, pool.2.clone(), *pool.3), - ); - } - pools_map -} - -impl Querier for WasmMockQuerier { - fn raw_query(&self, bin_request: &[u8]) -> QuerierResult { - // MockQuerier doesn't support Custom, so we ignore it completely here - let request: QueryRequest = match from_slice(bin_request) { - Ok(v) => v, - Err(e) => { - return Err(SystemError::InvalidRequest { - error: format!("Parsing query request: {}", e), - request: bin_request.into(), - }) - } - }; - self.handle_query(&request) - } -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub struct ReferenceData { - rate: Uint128, - last_updated_base: u64, - last_updated_quote: u64, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub struct EpochStateResponse { - exchange_rate: Decimal256, - aterra_supply: Uint256, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum QueryMsg { - Price { - base_asset: String, - quote_asset: String, - }, - Pool {}, - GetReferenceData { - base_symbol: String, - quote_symbol: String, - }, - EpochState { - block_heigth: Option, - distributed_interest: Option, - }, -} - -impl WasmMockQuerier { - pub fn handle_query(&self, request: &QueryRequest) -> QuerierResult { - match &request { - QueryRequest::Custom(TerraQueryWrapper { route, query_data }) => { - if &TerraRoute::Oracle == route { - match query_data { - TerraQuery::ExchangeRates { - base_denom: _, - quote_denoms: _, - } => { - let res = ExchangeRatesResponse { - exchange_rates: vec![ExchangeRateItem { - quote_denom: "uusd".to_string(), - exchange_rate: Decimal::from_ratio(5u128, 1u128), - }], - base_denom: "uluna".to_string(), - }; - Ok(to_binary(&res)) - } - _ => panic!("DO NOT ENTER HERE"), - } - } else { - panic!("DO NOT ENTER HERE") - } - } - QueryRequest::Wasm(WasmQuery::Smart { contract_addr, msg }) => match from_binary(&msg) - .unwrap() - { - QueryMsg::Price { - base_asset, - quote_asset, - } => match self.oracle_price_querier.oracle_price.get(&base_asset) { - Some(base_price) => { - match self.oracle_price_querier.oracle_price.get("e_asset) { - Some(quote_price) => Ok(to_binary(&PriceResponse { - rate: decimal_division(*base_price, *quote_price), - last_updated_base: 1000u64, - last_updated_quote: 1000u64, - })), - None => Err(SystemError::InvalidRequest { - error: "No oracle price exists".to_string(), - request: msg.as_slice().into(), - }), - } - } - None => Err(SystemError::InvalidRequest { - error: "No oracle price exists".to_string(), - request: msg.as_slice().into(), - }), - }, - QueryMsg::Pool {} => match self.terraswap_pools_querier.pools.get(&contract_addr) { - Some(v) => Ok(to_binary(&PoolResponse { - assets: [ - Asset { - amount: v.1, - info: AssetInfo::NativeToken { denom: v.0.clone() }, - }, - Asset { - amount: v.3, - info: AssetInfo::Token { - contract_addr: HumanAddr::from(v.2.clone()), - }, - }, - ], - total_share: Uint128::zero(), - })), - None => Err(SystemError::InvalidRequest { - error: "No pair info exists".to_string(), - request: msg.as_slice().into(), - }), - }, - QueryMsg::GetReferenceData { .. } => Ok(to_binary(&ReferenceData { - rate: Uint128(3465211050000000000000), - last_updated_base: 100u64, - last_updated_quote: 100u64, - })), - QueryMsg::EpochState { .. } => Ok(to_binary(&EpochStateResponse { - exchange_rate: Decimal256::from_ratio(10, 3), - aterra_supply: Uint256::from_str("123123123").unwrap(), - })), - }, - _ => self.base.handle_query(request), - } - } -} - -impl WasmMockQuerier { - pub fn new(base: MockQuerier) -> Self { - WasmMockQuerier { - base, - oracle_price_querier: OraclePriceQuerier::default(), - terraswap_pools_querier: TerraswapPoolsQuerier::default(), - } - } - - // configure the oracle price mock querier - pub fn with_oracle_price(&mut self, oracle_price: &[(&String, &Decimal)]) { - self.oracle_price_querier = OraclePriceQuerier::new(oracle_price); - } - - pub fn with_terraswap_pools( - &mut self, - pairs: &[(&HumanAddr, (&String, &Uint128, &String, &Uint128))], - ) { - self.terraswap_pools_querier = TerraswapPoolsQuerier::new(pairs); - } -} diff --git a/contracts/mirror_collateral_oracle/src/querier.rs b/contracts/mirror_collateral_oracle/src/querier.rs deleted file mode 100644 index 82e121063..000000000 --- a/contracts/mirror_collateral_oracle/src/querier.rs +++ /dev/null @@ -1,230 +0,0 @@ -use cosmwasm_std::{ - to_binary, Api, Decimal, Extern, HumanAddr, Querier, QueryRequest, StdError, StdResult, - Storage, Uint128, WasmQuery, -}; -use std::str::FromStr; - -use crate::math::decimal_multiplication; -use crate::state::Config; -use cosmwasm_bignumber::Decimal256; -use cosmwasm_bignumber::Uint256; -use mirror_protocol::collateral_oracle::SourceType; -use serde::{Deserialize, Serialize}; -use terra_cosmwasm::{ExchangeRatesResponse, TerraQuerier}; -use terraswap::asset::{Asset, AssetInfo}; -use terraswap::pair::QueryMsg as TerraswapPairQueryMsg; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -#[serde(rename_all = "snake_case")] -pub enum SourceQueryMsg { - Price { - base_asset: String, - quote_asset: String, - }, - Pool {}, - GetReferenceData { - base_symbol: String, - quote_symbol: String, - }, - EpochState { - block_heigth: Option, - distributed_interest: Option, - }, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -pub struct TerraOracleResponse { - // oracle queries returns rate - pub rate: Decimal, - pub last_updated_base: u64, -} -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -pub struct TerraswapResponse { - // terraswap queries return pool assets - pub assets: [Asset; 2], -} -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -pub struct BandOracleResponse { - // band oracle queries returns rate (uint128) - pub rate: Uint128, - pub last_updated_base: u64, -} -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -pub struct AnchorMarketResponse { - // anchor market queries return exchange rate in Decimal256 - pub exchange_rate: Decimal256, -} - -pub fn query_price( - deps: &Extern, - config: &Config, - asset: &String, - price_source: &SourceType, -) -> StdResult<(Decimal, u64)> { - match price_source { - SourceType::BandOracle {} => { - let res: BandOracleResponse = - deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: deps.api.human_address(&config.band_oracle)?, - msg: to_binary(&SourceQueryMsg::GetReferenceData { - base_symbol: asset.to_string(), - quote_symbol: config.base_denom.clone(), - }) - .unwrap(), - }))?; - let rate: Decimal = parse_band_rate(res.rate)?; - - Ok((rate, res.last_updated_base)) - } - SourceType::FixedPrice { price } => return Ok((*price, u64::MAX)), - SourceType::MirrorOracle {} => { - let res: TerraOracleResponse = - deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: deps.api.human_address(&config.mirror_oracle)?, - msg: to_binary(&SourceQueryMsg::Price { - base_asset: asset.to_string(), - quote_asset: config.base_denom.clone(), - }) - .unwrap(), - }))?; - - Ok((res.rate, res.last_updated_base)) - } - SourceType::AnchorOracle {} => { - let res: TerraOracleResponse = - deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: deps.api.human_address(&config.anchor_oracle)?, - msg: to_binary(&SourceQueryMsg::Price { - base_asset: asset.to_string(), - quote_asset: config.base_denom.clone(), - }) - .unwrap(), - }))?; - - Ok((res.rate, res.last_updated_base)) - } - SourceType::Terraswap { - terraswap_pair_addr, - intermediate_denom, - } => { - let res: TerraswapResponse = - deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: HumanAddr::from(terraswap_pair_addr), - msg: to_binary(&TerraswapPairQueryMsg::Pool {}).unwrap(), - }))?; - let assets: [Asset; 2] = res.assets; - - // query intermediate denom if it exists - let query_denom: String = match intermediate_denom.clone() { - Some(v) => v, - None => config.base_denom.clone(), - }; - - let queried_rate: Decimal = if assets[0].info.equal(&AssetInfo::NativeToken { - denom: query_denom.clone(), - }) { - Decimal::from_ratio(assets[0].amount, assets[1].amount) - } else if assets[1].info.equal(&AssetInfo::NativeToken { - denom: query_denom.clone(), - }) { - Decimal::from_ratio(assets[1].amount, assets[0].amount) - } else { - return Err(StdError::generic_err("Invalid pool")); - }; - - // if intermediate denom exists, calculate final rate - let rate: Decimal = if intermediate_denom.is_some() { - // (query_denom / intermediate_denom) * (intermedaite_denom / base_denom) = (query_denom / base_denom) - let native_rate: Decimal = - query_native_rate(&deps.querier, query_denom, config.base_denom.clone())?; - decimal_multiplication(queried_rate, native_rate) - } else { - queried_rate - }; - - Ok((rate, u64::MAX)) - } - SourceType::AnchorMarket { anchor_market_addr } => { - let res: AnchorMarketResponse = - deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: HumanAddr::from(anchor_market_addr), - msg: to_binary(&SourceQueryMsg::EpochState { - block_heigth: None, - distributed_interest: None, - }) - .unwrap(), - }))?; - let rate: Decimal = res.exchange_rate.into(); - - Ok((rate, u64::MAX)) - } - SourceType::Native { native_denom } => { - let rate: Decimal = query_native_rate( - &deps.querier, - native_denom.clone(), - config.base_denom.clone(), - )?; - - Ok((rate, u64::MAX)) - } - } -} - -/// Parses a uint that contains the price multiplied by 1e18 -fn parse_band_rate(uint_rate: Uint128) -> StdResult { - // manipulate the uint as a string to prevent overflow - let mut rate_uint_string: String = uint_rate.to_string(); - - let uint_len = rate_uint_string.len(); - if uint_len > 18 { - let dec_point = rate_uint_string.len() - 18; - rate_uint_string.insert(dec_point, '.'); - } else { - let mut prefix: String = "0.".to_owned(); - let dec_zeros = 18 - uint_len; - for _ in 0..dec_zeros { - prefix.push('0'); - } - rate_uint_string = prefix + rate_uint_string.as_str(); - } - - Decimal::from_str(rate_uint_string.as_str()) -} - -fn query_native_rate( - querier: &Q, - base_denom: String, - quote_denom: String, -) -> StdResult { - let terra_querier = TerraQuerier::new(querier); - let res: ExchangeRatesResponse = - terra_querier.query_exchange_rates(base_denom, vec![quote_denom])?; - - Ok(res.exchange_rates[0].exchange_rate) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_parse_band_rate() { - let rate_dec_1: Decimal = parse_band_rate(Uint128(3493968700000000000000u128)).unwrap(); - assert_eq!( - rate_dec_1, - Decimal::from_str("3493.968700000000000000").unwrap() - ); - - let rate_dec_2: Decimal = parse_band_rate(Uint128(1234u128)).unwrap(); - assert_eq!( - rate_dec_2, - Decimal::from_str("0.000000000000001234").unwrap() - ); - - let rate_dec_3: Decimal = parse_band_rate(Uint128(100000000000000001u128)).unwrap(); - assert_eq!( - rate_dec_3, - Decimal::from_str("0.100000000000000001").unwrap() - ); - } -} diff --git a/contracts/mirror_collateral_oracle/src/state.rs b/contracts/mirror_collateral_oracle/src/state.rs deleted file mode 100644 index 7628ec5cd..000000000 --- a/contracts/mirror_collateral_oracle/src/state.rs +++ /dev/null @@ -1,73 +0,0 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use cosmwasm_std::{CanonicalAddr, Decimal, Order, StdResult, Storage}; -use cosmwasm_storage::{singleton, singleton_read, Bucket, ReadonlyBucket}; - -use mirror_protocol::collateral_oracle::{CollateralInfoResponse, SourceType}; - -static PREFIX_COLLATERAL_ASSET_INFO: &[u8] = b"collateral_asset_info"; -static KEY_CONFIG: &[u8] = b"config"; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct Config { - pub owner: CanonicalAddr, - pub mint_contract: CanonicalAddr, - pub factory_contract: CanonicalAddr, - pub base_denom: String, - pub mirror_oracle: CanonicalAddr, - pub anchor_oracle: CanonicalAddr, - pub band_oracle: CanonicalAddr, -} - -pub fn store_config(storage: &mut S, config: &Config) -> StdResult<()> { - singleton(storage, KEY_CONFIG).save(config) -} - -pub fn read_config(storage: &S) -> StdResult { - singleton_read(storage, KEY_CONFIG).load() -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct CollateralAssetInfo { - pub asset: String, - pub price_source: SourceType, - pub multiplier: Decimal, - pub is_revoked: bool, -} - -pub fn store_collateral_info( - storage: &mut S, - collateral: &CollateralAssetInfo, -) -> StdResult<()> { - let mut collaterals_bucket: Bucket = - Bucket::new(PREFIX_COLLATERAL_ASSET_INFO, storage); - collaterals_bucket.save(collateral.asset.as_bytes(), collateral) -} - -pub fn read_collateral_info( - storage: &S, - id: &String, -) -> StdResult { - let price_bucket: ReadonlyBucket = - ReadonlyBucket::new(PREFIX_COLLATERAL_ASSET_INFO, storage); - price_bucket.load(id.as_bytes()) -} - -pub fn read_collateral_infos(storage: &S) -> StdResult> { - let price_bucket: ReadonlyBucket = - ReadonlyBucket::new(PREFIX_COLLATERAL_ASSET_INFO, storage); - - price_bucket - .range(None, None, Order::Ascending) - .map(|item| { - let (_, v) = item?; - Ok(CollateralInfoResponse { - asset: v.asset, - source_type: v.price_source.to_string(), - multiplier: v.multiplier, - is_revoked: v.is_revoked, - }) - }) - .collect() -} diff --git a/contracts/mirror_collateral_oracle/src/testing.rs b/contracts/mirror_collateral_oracle/src/testing.rs deleted file mode 100644 index 229fed156..000000000 --- a/contracts/mirror_collateral_oracle/src/testing.rs +++ /dev/null @@ -1,674 +0,0 @@ -use crate::contract::{handle, init, query_collateral_info, query_collateral_price, query_config}; -use crate::mock_querier::mock_dependencies; -use cosmwasm_std::testing::mock_env; -use cosmwasm_std::{Decimal, HumanAddr, StdError, Uint128}; -use mirror_protocol::collateral_oracle::{ - CollateralInfoResponse, CollateralPriceResponse, HandleMsg, InitMsg, SourceType, -}; -use std::str::FromStr; -use terraswap::asset::AssetInfo; - -#[test] -fn proper_initialization() { - let mut deps = mock_dependencies(20, &[]); - - let msg = InitMsg { - owner: HumanAddr("owner0000".to_string()), - mint_contract: HumanAddr("mint0000".to_string()), - factory_contract: HumanAddr("factory0000".to_string()), - base_denom: "uusd".to_string(), - mirror_oracle: HumanAddr("mirrororacle0000".to_string()), - anchor_oracle: HumanAddr("anchororacle0000".to_string()), - band_oracle: HumanAddr("bandoracle0000".to_string()), - }; - - let env = mock_env("addr0000", &[]); - - // 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()); - - // it worked, let's query the state - let value = query_config(&deps).unwrap(); - assert_eq!("owner0000", value.owner.as_str()); - assert_eq!("mint0000", value.mint_contract.as_str()); - assert_eq!("factory0000", value.factory_contract.as_str()); - assert_eq!("uusd", value.base_denom.as_str()); -} - -#[test] -fn update_config() { - let mut deps = mock_dependencies(20, &[]); - - let msg = InitMsg { - owner: HumanAddr("owner0000".to_string()), - mint_contract: HumanAddr("mint0000".to_string()), - factory_contract: HumanAddr("factory0000".to_string()), - base_denom: "uusd".to_string(), - mirror_oracle: HumanAddr("mirrororacle0000".to_string()), - anchor_oracle: HumanAddr("anchororacle0000".to_string()), - band_oracle: HumanAddr("bandoracle0000".to_string()), - }; - - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env, msg).unwrap(); - - // update owner - let env = mock_env("owner0000", &[]); - let msg = HandleMsg::UpdateConfig { - owner: Some(HumanAddr("owner0001".to_string())), - mint_contract: Some(HumanAddr("mint0001".to_string())), - factory_contract: Some(HumanAddr("factory0001".to_string())), - base_denom: Some("uluna".to_string()), - mirror_oracle: Some(HumanAddr("mirrororacle0001".to_string())), - anchor_oracle: Some(HumanAddr("anchororacle0001".to_string())), - band_oracle: Some(HumanAddr("bandoracle0001".to_string())), - }; - - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!(0, res.messages.len()); - - // it worked, let's query the state - let value = query_config(&deps).unwrap(); - assert_eq!("owner0001", value.owner.as_str()); - assert_eq!("mint0001", value.mint_contract.as_str()); - assert_eq!("factory0001", value.factory_contract.as_str()); - assert_eq!("uluna", value.base_denom.as_str()); - assert_eq!("mirrororacle0001", value.mirror_oracle.as_str()); - assert_eq!("anchororacle0001", value.anchor_oracle.as_str()); - assert_eq!("bandoracle0001", value.band_oracle.as_str()); - - // Unauthorized err - let env = mock_env("owner0000", &[]); - let msg = HandleMsg::UpdateConfig { - owner: None, - mint_contract: None, - factory_contract: None, - base_denom: None, - mirror_oracle: None, - anchor_oracle: None, - band_oracle: None, - }; - - let res = handle(&mut deps, env, msg); - match res { - Err(StdError::Unauthorized { .. }) => {} - _ => panic!("Must return unauthorized error"), - } -} - -#[test] -fn register_collateral() { - let mut deps = mock_dependencies(20, &[]); - deps.querier.with_oracle_price(&[ - (&"uusd".to_string(), &Decimal::one()), - (&"mTSLA".to_string(), &Decimal::percent(100)), - ]); - - let msg = InitMsg { - owner: HumanAddr("owner0000".to_string()), - mint_contract: HumanAddr("mint0000".to_string()), - factory_contract: HumanAddr("factory0000".to_string()), - base_denom: "uusd".to_string(), - mirror_oracle: HumanAddr("mirrororacle0000".to_string()), - anchor_oracle: HumanAddr("anchororacle0000".to_string()), - band_oracle: HumanAddr("bandoracle0000".to_string()), - }; - - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterCollateralAsset { - asset: AssetInfo::Token { - contract_addr: HumanAddr::from("mTSLA"), - }, - multiplier: Decimal::percent(100), - price_source: SourceType::MirrorOracle {}, - }; - - // unauthorized attempt - let env = mock_env("addr0000", &[]); - let res = handle(&mut deps, env, msg.clone()).unwrap_err(); - assert_eq!(res, StdError::unauthorized()); - - // successfull attempt - let env = mock_env("owner0000", &[]); - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!(0, res.messages.len()); - - // query collateral info - let query_res = query_collateral_info(&deps, "mTSLA".to_string()).unwrap(); - assert_eq!( - query_res, - CollateralInfoResponse { - asset: "mTSLA".to_string(), - source_type: "mirror_oracle".to_string(), - multiplier: Decimal::percent(100), - is_revoked: false, - } - ) -} - -#[test] -fn update_collateral() { - let mut deps = mock_dependencies(20, &[]); - deps.querier.with_oracle_price(&[ - (&"uusd".to_string(), &Decimal::one()), - (&"mTSLA".to_string(), &Decimal::percent(100)), - ]); - - let msg = InitMsg { - owner: HumanAddr("owner0000".to_string()), - mint_contract: HumanAddr("mint0000".to_string()), - factory_contract: HumanAddr("factory0000".to_string()), - base_denom: "uusd".to_string(), - mirror_oracle: HumanAddr("mirrororacle0000".to_string()), - anchor_oracle: HumanAddr("anchororacle0000".to_string()), - band_oracle: HumanAddr("bandoracle0000".to_string()), - }; - - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterCollateralAsset { - asset: AssetInfo::Token { - contract_addr: HumanAddr::from("mTSLA"), - }, - multiplier: Decimal::percent(100), - price_source: SourceType::MirrorOracle {}, - }; - - // successfull attempt - let env = mock_env("owner0000", &[]); - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!(0, res.messages.len()); - - // query collateral info - let query_res = query_collateral_info(&deps, "mTSLA".to_string()).unwrap(); - assert_eq!( - query_res, - CollateralInfoResponse { - asset: "mTSLA".to_string(), - source_type: "mirror_oracle".to_string(), - multiplier: Decimal::percent(100), - is_revoked: false, - } - ); - - // update collateral query - let msg = HandleMsg::UpdateCollateralPriceSource { - asset: AssetInfo::Token { - contract_addr: HumanAddr::from("mTSLA"), - }, - price_source: SourceType::FixedPrice { - price: Decimal::zero(), - }, - }; - - // unauthorized attempt - let env = mock_env("factory0000", &[]); - let res = handle(&mut deps, env, msg.clone()).unwrap_err(); - assert_eq!(res, StdError::unauthorized()); - - // successfull attempt - let env = mock_env("owner0000", &[]); - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!(0, res.messages.len()); - - // query the updated collateral - let query_res = query_collateral_info(&deps, "mTSLA".to_string()).unwrap(); - assert_eq!( - query_res, - CollateralInfoResponse { - asset: "mTSLA".to_string(), - source_type: "fixed_price".to_string(), - multiplier: Decimal::percent(100), - is_revoked: false, - } - ); - - // update collateral premium - invalid msg - let msg = HandleMsg::UpdateCollateralMultiplier { - asset: AssetInfo::Token { - contract_addr: HumanAddr::from("mTSLA"), - }, - multiplier: Decimal::zero(), - }; - - // invalid multiplier - let env = mock_env("factory0000", &[]); - let res = handle(&mut deps, env, msg).unwrap_err(); - assert_eq!( - res, - StdError::generic_err("Multiplier must be bigger than 0") - ); - - // update collateral premium - valid msg - let msg = HandleMsg::UpdateCollateralMultiplier { - asset: AssetInfo::Token { - contract_addr: HumanAddr::from("mTSLA"), - }, - multiplier: Decimal::percent(120), - }; - - // unauthorized attempt - let env = mock_env("owner0000", &[]); - let res = handle(&mut deps, env, msg.clone()).unwrap_err(); - assert_eq!(res, StdError::unauthorized()); - - // successfull attempt - let env = mock_env("factory0000", &[]); - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!(0, res.messages.len()); - - // query the updated collateral - let query_res = query_collateral_info(&deps, "mTSLA".to_string()).unwrap(); - assert_eq!( - query_res, - CollateralInfoResponse { - asset: "mTSLA".to_string(), - source_type: "fixed_price".to_string(), - multiplier: Decimal::percent(120), - is_revoked: false, - } - ) -} - -#[test] -fn get_oracle_price() { - let mut deps = mock_dependencies(20, &[]); - deps.querier.with_oracle_price(&[ - (&"uusd".to_string(), &Decimal::one()), - (&"mTSLA".to_string(), &Decimal::percent(100)), - ]); - - let msg = InitMsg { - owner: HumanAddr("owner0000".to_string()), - mint_contract: HumanAddr("mint0000".to_string()), - factory_contract: HumanAddr("factory0000".to_string()), - base_denom: "uusd".to_string(), - mirror_oracle: HumanAddr("mirrororacle0000".to_string()), - anchor_oracle: HumanAddr("anchororacle0000".to_string()), - band_oracle: HumanAddr("bandoracle0000".to_string()), - }; - - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterCollateralAsset { - asset: AssetInfo::Token { - contract_addr: HumanAddr::from("mTSLA"), - }, - multiplier: Decimal::percent(100), - price_source: SourceType::MirrorOracle {}, - }; - - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // attempt to query price - let query_res = query_collateral_price(&deps, "mTSLA".to_string()).unwrap(); - assert_eq!( - query_res, - CollateralPriceResponse { - asset: "mTSLA".to_string(), - rate: Decimal::percent(100), - last_updated: 1000u64, - multiplier: Decimal::percent(100), - is_revoked: false, - } - ); -} - -#[test] -fn get_terraswap_price() { - let mut deps = mock_dependencies(20, &[]); - deps.querier.with_terraswap_pools(&[ - ( - &HumanAddr::from("ustancpair0000"), - ( - &"uusd".to_string(), - &Uint128(1u128), - &"anc0000".to_string(), - &Uint128(100u128), - ), - ), - ( - &HumanAddr::from("lunablunapair0000"), - ( - &"uluna".to_string(), - &Uint128(18u128), - &"bluna0000".to_string(), - &Uint128(2u128), - ), - ), - ]); - - let msg = InitMsg { - owner: HumanAddr("owner0000".to_string()), - mint_contract: HumanAddr("mint0000".to_string()), - factory_contract: HumanAddr("factory0000".to_string()), - base_denom: "uusd".to_string(), - mirror_oracle: HumanAddr("mirrororacle0000".to_string()), - anchor_oracle: HumanAddr("anchororacle0000".to_string()), - band_oracle: HumanAddr("bandoracle0000".to_string()), - }; - - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterCollateralAsset { - asset: AssetInfo::Token { - contract_addr: HumanAddr::from("anc0000"), - }, - multiplier: Decimal::percent(100), - price_source: SourceType::Terraswap { - terraswap_pair_addr: HumanAddr::from("ustancpair0000"), - intermediate_denom: None, - }, - }; - - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // attempt to query price - let query_res = query_collateral_price(&deps, "anc0000".to_string()).unwrap(); - assert_eq!( - query_res, - CollateralPriceResponse { - asset: "anc0000".to_string(), - rate: Decimal::from_ratio(1u128, 100u128), - last_updated: u64::MAX, - multiplier: Decimal::percent(100), - is_revoked: false, - } - ); - - // register collateral with intermediate denom - let msg = HandleMsg::RegisterCollateralAsset { - asset: AssetInfo::Token { - contract_addr: HumanAddr::from("bluna0000"), - }, - multiplier: Decimal::percent(100), - price_source: SourceType::Terraswap { - terraswap_pair_addr: HumanAddr::from("lunablunapair0000"), - intermediate_denom: Some("uluna".to_string()), - }, - }; - - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // attempt to query price - let query_res = query_collateral_price(&deps, "bluna0000".to_string()).unwrap(); - assert_eq!( - query_res, - CollateralPriceResponse { - asset: "bluna0000".to_string(), - rate: Decimal::from_ratio(45u128, 1u128), // 9 / 1 * 5 / 1 - last_updated: u64::MAX, - multiplier: Decimal::percent(100), - is_revoked: false, - } - ); -} - -#[test] -fn get_fixed_price() { - let mut deps = mock_dependencies(20, &[]); - - let msg = InitMsg { - owner: HumanAddr("owner0000".to_string()), - mint_contract: HumanAddr("mint0000".to_string()), - factory_contract: HumanAddr("factory0000".to_string()), - base_denom: "uusd".to_string(), - mirror_oracle: HumanAddr("mirrororacle0000".to_string()), - anchor_oracle: HumanAddr("anchororacle0000".to_string()), - band_oracle: HumanAddr("bandoracle0000".to_string()), - }; - - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterCollateralAsset { - asset: AssetInfo::Token { - contract_addr: HumanAddr::from("aUST"), - }, - multiplier: Decimal::percent(100), - price_source: SourceType::FixedPrice { - price: Decimal::from_ratio(1u128, 2u128), - }, - }; - - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // attempt to query price - let query_res = query_collateral_price(&deps, "aUST".to_string()).unwrap(); - assert_eq!( - query_res, - CollateralPriceResponse { - asset: "aUST".to_string(), - rate: Decimal::from_ratio(1u128, 2u128), - last_updated: u64::MAX, - multiplier: Decimal::percent(100), - is_revoked: false, - } - ); -} - -#[test] -fn get_band_oracle_price() { - let mut deps = mock_dependencies(20, &[]); - - let msg = InitMsg { - owner: HumanAddr("owner0000".to_string()), - mint_contract: HumanAddr("mint0000".to_string()), - factory_contract: HumanAddr("factory0000".to_string()), - base_denom: "uusd".to_string(), - mirror_oracle: HumanAddr("mirrororacle0000".to_string()), - anchor_oracle: HumanAddr("anchororacle0000".to_string()), - band_oracle: HumanAddr("bandoracle0000".to_string()), - }; - - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterCollateralAsset { - asset: AssetInfo::NativeToken { - denom: "uluna".to_string(), - }, - multiplier: Decimal::percent(100), - price_source: SourceType::BandOracle {}, - }; - - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // attempt to query price - let query_res = query_collateral_price(&deps, "uluna".to_string()).unwrap(); - assert_eq!( - query_res, - CollateralPriceResponse { - asset: "uluna".to_string(), - rate: Decimal::from_str("3465.211050000000000000").unwrap(), - last_updated: 100u64, - multiplier: Decimal::percent(100), - is_revoked: false, - } - ); -} - -#[test] -fn get_anchor_market_price() { - let mut deps = mock_dependencies(20, &[]); - - let msg = InitMsg { - owner: HumanAddr("owner0000".to_string()), - mint_contract: HumanAddr("mint0000".to_string()), - factory_contract: HumanAddr("factory0000".to_string()), - base_denom: "uusd".to_string(), - mirror_oracle: HumanAddr("mirrororacle0000".to_string()), - anchor_oracle: HumanAddr("anchororacle0000".to_string()), - band_oracle: HumanAddr("bandoracle0000".to_string()), - }; - - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterCollateralAsset { - asset: AssetInfo::Token { - contract_addr: HumanAddr::from("aust0000"), - }, - multiplier: Decimal::percent(100), - price_source: SourceType::AnchorMarket { - anchor_market_addr: HumanAddr::from("anchormarket0000"), - }, - }; - - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // attempt to query price - let query_res = query_collateral_price(&deps, "aust0000".to_string()).unwrap(); - assert_eq!( - query_res, - CollateralPriceResponse { - asset: "aust0000".to_string(), - rate: Decimal::from_ratio(10u128, 3u128), - last_updated: u64::MAX, - multiplier: Decimal::percent(100), - is_revoked: false, - } - ); -} - -#[test] -fn get_native_price() { - let mut deps = mock_dependencies(20, &[]); - - let msg = InitMsg { - owner: HumanAddr("owner0000".to_string()), - mint_contract: HumanAddr("mint0000".to_string()), - factory_contract: HumanAddr("factory0000".to_string()), - base_denom: "uusd".to_string(), - mirror_oracle: HumanAddr("mirrororacle0000".to_string()), - anchor_oracle: HumanAddr("anchororacle0000".to_string()), - band_oracle: HumanAddr("bandoracle0000".to_string()), - }; - - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterCollateralAsset { - asset: AssetInfo::NativeToken { - denom: "uluna".to_string(), - }, - multiplier: Decimal::percent(100), - price_source: SourceType::Native { - native_denom: "uluna".to_string(), - }, - }; - - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // attempt to query price - let query_res = query_collateral_price(&deps, "uluna".to_string()).unwrap(); - assert_eq!( - query_res, - CollateralPriceResponse { - asset: "uluna".to_string(), - rate: Decimal::from_ratio(5u128, 1u128), - last_updated: u64::MAX, - multiplier: Decimal::percent(100), - is_revoked: false, - } - ); -} - -#[test] -fn revoke_collateral() { - let mut deps = mock_dependencies(20, &[]); - - let msg = InitMsg { - owner: HumanAddr("owner0000".to_string()), - mint_contract: HumanAddr("mint0000".to_string()), - factory_contract: HumanAddr("factory0000".to_string()), - base_denom: "uusd".to_string(), - mirror_oracle: HumanAddr("mirrororacle0000".to_string()), - anchor_oracle: HumanAddr("anchororacle0000".to_string()), - band_oracle: HumanAddr("bandoracle0000".to_string()), - }; - - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterCollateralAsset { - asset: AssetInfo::Token { - contract_addr: HumanAddr::from("aUST"), - }, - multiplier: Decimal::percent(100), - price_source: SourceType::FixedPrice { - price: Decimal::one(), - }, - }; - - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // attempt to query price - let query_res = query_collateral_price(&deps, "aUST".to_string()).unwrap(); - assert_eq!( - query_res, - CollateralPriceResponse { - asset: "aUST".to_string(), - rate: Decimal::one(), - last_updated: u64::MAX, - multiplier: Decimal::percent(100), - is_revoked: false, - } - ); - - // revoke the asset - let msg = HandleMsg::RevokeCollateralAsset { - asset: AssetInfo::Token { - contract_addr: HumanAddr::from("aUST"), - }, - }; - - // unauthorized attempt - let env = mock_env("factory0000", &[]); - let res = handle(&mut deps, env, msg.clone()).unwrap_err(); - assert_eq!(res, StdError::unauthorized()); - - // successfull attempt - let env = mock_env("owner0000", &[]); - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!(0, res.messages.len()); - - // query the revoked collateral - let query_res = query_collateral_info(&deps, "aUST".to_string()).unwrap(); - assert_eq!( - query_res, - CollateralInfoResponse { - asset: "aUST".to_string(), - source_type: "fixed_price".to_string(), - multiplier: Decimal::percent(100), - is_revoked: true, - } - ); - - // attempt to query price of revoked asset - let query_res = query_collateral_price(&deps, "aUST".to_string()).unwrap(); - assert_eq!( - query_res, - CollateralPriceResponse { - asset: "aUST".to_string(), - rate: Decimal::one(), - last_updated: u64::MAX, - multiplier: Decimal::percent(100), - is_revoked: true, - } - ); -} diff --git a/contracts/mirror_collector/.editorconfig b/contracts/mirror_collector/.editorconfig deleted file mode 100644 index 3d36f20b1..000000000 --- a/contracts/mirror_collector/.editorconfig +++ /dev/null @@ -1,11 +0,0 @@ -root = true - -[*] -indent_style = space -indent_size = 2 -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true - -[*.rs] -indent_size = 4 diff --git a/contracts/mirror_collector/.gitignore b/contracts/mirror_collector/.gitignore deleted file mode 100644 index 10fe5d615..000000000 --- a/contracts/mirror_collector/.gitignore +++ /dev/null @@ -1,12 +0,0 @@ -# Build results -/target - -# Text file backups -**/*.rs.bk - -# macOS -.DS_Store - -# IDEs -*.iml -.idea diff --git a/contracts/mirror_collector/Cargo.toml b/contracts/mirror_collector/Cargo.toml deleted file mode 100644 index 9c4127c5b..000000000 --- a/contracts/mirror_collector/Cargo.toml +++ /dev/null @@ -1,51 +0,0 @@ -[package] -name = "mirror-collector" -version = "1.1.0" -authors = ["Terraform Labs, PTE."] -edition = "2018" -description = "A Collector contract for Mirror Protocol - collect all swap rewards and send it to staking contract" -license = "Apache-2.0" - -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 = ["cranelift"] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces", "cosmwasm-vm/backtraces"] -cranelift = ["cosmwasm-vm/default-cranelift"] -singlepass = ["cosmwasm-vm/default-singlepass"] - -[dependencies] -cw20 = "0.2" -cosmwasm-std = { version = "0.10.1" } -cosmwasm-storage = { version = "0.10.1" } -mirror-protocol = { version = "1.1.0", path = "../../packages/mirror_protocol" } -terraswap = "1.1.0" -schemars = "0.7" -serde = { version = "1.0.103", default-features = false, features = ["derive"] } - -[dev-dependencies] -cosmwasm-vm = { version = "0.10.1", default-features = false } -cosmwasm-schema = "0.10.1" -terra-cosmwasm = "1.2.2" diff --git a/contracts/mirror_collector/README.md b/contracts/mirror_collector/README.md deleted file mode 100644 index f9b0b8ac2..000000000 --- a/contracts/mirror_collector/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Mirror Collector - -**NOTE**: Reference documentation for this contract is available [here](https://docs.mirror.finance/contracts/collector). - -The Collector accumulates fee rewards generated from CDP withdrawal within the protocol, and converts them into UST in order to purchase MIR from the MIR-UST Terraswap pool. The MIR is then sent to the Gov Contract to supply trading fee rewards for MIR stakers. diff --git a/contracts/mirror_collector/schema/config_response.json b/contracts/mirror_collector/schema/config_response.json deleted file mode 100644 index 002b34aca..000000000 --- a/contracts/mirror_collector/schema/config_response.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ConfigResponse", - "type": "object", - "required": [ - "base_denom", - "distribution_contract", - "mirror_token", - "terraswap_factory" - ], - "properties": { - "base_denom": { - "type": "string" - }, - "distribution_contract": { - "$ref": "#/definitions/HumanAddr" - }, - "mirror_token": { - "$ref": "#/definitions/HumanAddr" - }, - "terraswap_factory": { - "$ref": "#/definitions/HumanAddr" - } - }, - "definitions": { - "HumanAddr": { - "type": "string" - } - } -} diff --git a/contracts/mirror_collector/schema/query_msg.json b/contracts/mirror_collector/schema/query_msg.json deleted file mode 100644 index 8af77727d..000000000 --- a/contracts/mirror_collector/schema/query_msg.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryMsg", - "anyOf": [ - { - "type": "object", - "required": [ - "config" - ], - "properties": { - "config": { - "type": "object" - } - } - } - ] -} diff --git a/contracts/mirror_collector/src/contract.rs b/contracts/mirror_collector/src/contract.rs deleted file mode 100644 index 96a1aaa33..000000000 --- a/contracts/mirror_collector/src/contract.rs +++ /dev/null @@ -1,187 +0,0 @@ -use cosmwasm_std::{ - log, to_binary, Api, Binary, Coin, CosmosMsg, Env, Extern, HandleResponse, HandleResult, - HumanAddr, InitResponse, MigrateResponse, MigrateResult, Querier, StdResult, Storage, WasmMsg, -}; - -use crate::state::{read_config, store_config, Config}; - -use cw20::Cw20HandleMsg; -use mirror_protocol::collector::{ConfigResponse, HandleMsg, InitMsg, MigrateMsg, QueryMsg}; -use mirror_protocol::gov::Cw20HookMsg::DepositReward; -use terraswap::asset::{Asset, AssetInfo, PairInfo}; -use terraswap::pair::{Cw20HookMsg as TerraswapCw20HookMsg, HandleMsg as TerraswapHandleMsg}; -use terraswap::querier::{query_balance, query_pair_info, query_token_balance}; - -pub fn init( - deps: &mut Extern, - _env: Env, - msg: InitMsg, -) -> StdResult { - store_config( - &mut deps.storage, - &Config { - distribution_contract: deps.api.canonical_address(&msg.distribution_contract)?, - terraswap_factory: deps.api.canonical_address(&msg.terraswap_factory)?, - mirror_token: deps.api.canonical_address(&msg.mirror_token)?, - base_denom: msg.base_denom, - }, - )?; - - Ok(InitResponse::default()) -} - -pub fn handle( - deps: &mut Extern, - env: Env, - msg: HandleMsg, -) -> StdResult { - match msg { - HandleMsg::Convert { asset_token } => convert(deps, env, asset_token), - HandleMsg::Distribute {} => distribute(deps, env), - } -} - -/// Convert -/// Anyone can execute convert function to swap -/// asset token => collateral token -/// collateral token => MIR token -pub fn convert( - deps: &mut Extern, - env: Env, - asset_token: HumanAddr, -) -> HandleResult { - let config: Config = read_config(&deps.storage)?; - let asset_token_raw = deps.api.canonical_address(&asset_token)?; - let terraswap_factory_raw = deps.api.human_address(&config.terraswap_factory)?; - - let pair_info: PairInfo = query_pair_info( - &deps, - &terraswap_factory_raw, - &[ - AssetInfo::NativeToken { - denom: config.base_denom.to_string(), - }, - AssetInfo::Token { - contract_addr: asset_token.clone(), - }, - ], - )?; - - let messages: Vec; - if config.mirror_token == asset_token_raw { - // collateral token => MIR token - let amount = query_balance(&deps, &env.contract.address, config.base_denom.to_string())?; - let swap_asset = Asset { - info: AssetInfo::NativeToken { - denom: config.base_denom.clone(), - }, - amount, - }; - - // deduct tax first - let amount = (swap_asset.deduct_tax(&deps)?).amount; - messages = vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: pair_info.contract_addr, - msg: to_binary(&TerraswapHandleMsg::Swap { - offer_asset: Asset { - amount, - ..swap_asset - }, - max_spread: None, - belief_price: None, - to: None, - })?, - send: vec![Coin { - denom: config.base_denom, - amount, - }], - })]; - } else { - // asset token => collateral token - let amount = query_token_balance(&deps, &asset_token, &env.contract.address)?; - - messages = vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: asset_token.clone(), - msg: to_binary(&Cw20HandleMsg::Send { - contract: pair_info.contract_addr, - amount, - msg: Some(to_binary(&TerraswapCw20HookMsg::Swap { - max_spread: None, - belief_price: None, - to: None, - })?), - })?, - send: vec![], - })]; - } - - Ok(HandleResponse { - messages, - log: vec![ - log("action", "convert"), - log("asset_token", asset_token.as_str()), - ], - data: None, - }) -} - -// Anyone can execute send function to receive staking token rewards -pub fn distribute( - deps: &mut Extern, - env: Env, -) -> HandleResult { - let config: Config = read_config(&deps.storage)?; - let amount = query_token_balance( - &deps, - &deps.api.human_address(&config.mirror_token)?, - &env.contract.address, - )?; - - Ok(HandleResponse { - messages: vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: deps.api.human_address(&config.mirror_token)?, - msg: to_binary(&Cw20HandleMsg::Send { - contract: deps.api.human_address(&config.distribution_contract)?, - amount, - msg: Some(to_binary(&DepositReward {})?), - })?, - send: vec![], - })], - log: vec![ - log("action", "distribute"), - log("amount", amount.to_string()), - ], - data: None, - }) -} - -pub fn query( - deps: &Extern, - msg: QueryMsg, -) -> StdResult { - match msg { - QueryMsg::Config {} => to_binary(&query_config(deps)?), - } -} - -pub fn query_config( - deps: &Extern, -) -> StdResult { - let state = read_config(&deps.storage)?; - let resp = ConfigResponse { - distribution_contract: deps.api.human_address(&state.distribution_contract)?, - terraswap_factory: deps.api.human_address(&state.terraswap_factory)?, - mirror_token: deps.api.human_address(&state.mirror_token)?, - base_denom: state.base_denom, - }; - - Ok(resp) -} - -pub fn migrate( - _deps: &mut Extern, - _env: Env, - _msg: MigrateMsg, -) -> MigrateResult { - Ok(MigrateResponse::default()) -} diff --git a/contracts/mirror_collector/src/lib.rs b/contracts/mirror_collector/src/lib.rs deleted file mode 100644 index 750eebc46..000000000 --- a/contracts/mirror_collector/src/lib.rs +++ /dev/null @@ -1,11 +0,0 @@ -pub mod contract; -pub mod state; - -#[cfg(test)] -mod testing; - -#[cfg(test)] -mod mock_querier; - -#[cfg(target_arch = "wasm32")] -cosmwasm_std::create_entry_points_with_migration!(contract); diff --git a/contracts/mirror_collector/src/mock_querier.rs b/contracts/mirror_collector/src/mock_querier.rs deleted file mode 100644 index 5f30697fd..000000000 --- a/contracts/mirror_collector/src/mock_querier.rs +++ /dev/null @@ -1,276 +0,0 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use cosmwasm_std::testing::{MockApi, MockQuerier, MockStorage, MOCK_CONTRACT_ADDR}; -use cosmwasm_std::{ - from_binary, from_slice, to_binary, Api, CanonicalAddr, Coin, Decimal, Extern, HumanAddr, - Querier, QuerierResult, QueryRequest, SystemError, Uint128, WasmQuery, -}; -use cosmwasm_storage::to_length_prefixed; - -use std::collections::HashMap; - -use terra_cosmwasm::{TaxCapResponse, TaxRateResponse, TerraQuery, TerraQueryWrapper, TerraRoute}; -use terraswap::asset::{AssetInfo, PairInfo}; - -/// mock_dependencies is a drop-in replacement for cosmwasm_std::testing::mock_dependencies -/// this uses our CustomQuerier. -pub fn mock_dependencies( - canonical_length: usize, - contract_balance: &[Coin], -) -> Extern { - let contract_addr = HumanAddr::from(MOCK_CONTRACT_ADDR); - let custom_querier: WasmMockQuerier = WasmMockQuerier::new( - MockQuerier::new(&[(&contract_addr, contract_balance)]), - MockApi::new(canonical_length), - canonical_length, - ); - - Extern { - storage: MockStorage::default(), - api: MockApi::new(canonical_length), - querier: custom_querier, - } -} - -pub struct WasmMockQuerier { - base: MockQuerier, - token_querier: TokenQuerier, - tax_querier: TaxQuerier, - terraswap_factory_querier: TerraswapFactoryQuerier, - canonical_length: usize, -} - -#[derive(Clone, Default)] -pub struct TokenQuerier { - // this lets us iterate over all pairs that match the first string - balances: HashMap>, -} - -impl TokenQuerier { - pub fn new(balances: &[(&HumanAddr, &[(&HumanAddr, &Uint128)])]) -> Self { - TokenQuerier { - balances: balances_to_map(balances), - } - } -} - -pub(crate) fn balances_to_map( - balances: &[(&HumanAddr, &[(&HumanAddr, &Uint128)])], -) -> HashMap> { - let mut balances_map: HashMap> = HashMap::new(); - for (contract_addr, balances) in balances.iter() { - let mut contract_balances_map: HashMap = HashMap::new(); - for (addr, balance) in balances.iter() { - contract_balances_map.insert(HumanAddr::from(addr), **balance); - } - - balances_map.insert(HumanAddr::from(contract_addr), contract_balances_map); - } - balances_map -} - -#[derive(Clone, Default)] -pub struct TaxQuerier { - rate: Decimal, - // this lets us iterate over all pairs that match the first string - caps: HashMap, -} - -impl TaxQuerier { - pub fn new(rate: Decimal, caps: &[(&String, &Uint128)]) -> Self { - TaxQuerier { - rate, - caps: caps_to_map(caps), - } - } -} - -pub(crate) fn caps_to_map(caps: &[(&String, &Uint128)]) -> HashMap { - let mut owner_map: HashMap = HashMap::new(); - for (denom, cap) in caps.iter() { - owner_map.insert(denom.to_string(), **cap); - } - owner_map -} - -#[derive(Clone, Default)] -pub struct TerraswapFactoryQuerier { - pairs: HashMap, -} - -impl TerraswapFactoryQuerier { - pub fn new(pairs: &[(&String, &HumanAddr)]) -> Self { - TerraswapFactoryQuerier { - pairs: pairs_to_map(pairs), - } - } -} - -pub(crate) fn pairs_to_map(pairs: &[(&String, &HumanAddr)]) -> HashMap { - let mut pairs_map: HashMap = HashMap::new(); - for (key, pair) in pairs.iter() { - pairs_map.insert(key.to_string(), HumanAddr::from(pair)); - } - pairs_map -} - -impl Querier for WasmMockQuerier { - fn raw_query(&self, bin_request: &[u8]) -> QuerierResult { - // MockQuerier doesn't support Custom, so we ignore it completely here - let request: QueryRequest = match from_slice(bin_request) { - Ok(v) => v, - Err(e) => { - return Err(SystemError::InvalidRequest { - error: format!("Parsing query request: {}", e), - request: bin_request.into(), - }) - } - }; - self.handle_query(&request) - } -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum QueryMsg { - Pair { asset_infos: [AssetInfo; 2] }, -} - -impl WasmMockQuerier { - pub fn handle_query(&self, request: &QueryRequest) -> QuerierResult { - match &request { - QueryRequest::Custom(TerraQueryWrapper { route, query_data }) => { - if route == &TerraRoute::Treasury { - match query_data { - TerraQuery::TaxRate {} => { - let res = TaxRateResponse { - rate: self.tax_querier.rate, - }; - Ok(to_binary(&res)) - } - TerraQuery::TaxCap { denom } => { - let cap = self - .tax_querier - .caps - .get(denom) - .copied() - .unwrap_or_default(); - let res = TaxCapResponse { cap }; - Ok(to_binary(&res)) - } - _ => panic!("DO NOT ENTER HERE"), - } - } else { - panic!("DO NOT ENTER HERE") - } - } - QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: _, - msg, - }) => match from_binary(&msg).unwrap() { - QueryMsg::Pair { asset_infos } => { - let key = asset_infos[0].to_string() + asset_infos[1].to_string().as_str(); - match self.terraswap_factory_querier.pairs.get(&key) { - Some(v) => Ok(to_binary(&PairInfo { - contract_addr: v.clone(), - liquidity_token: HumanAddr::from("liquidity"), - asset_infos: [ - AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - ], - })), - None => Err(SystemError::InvalidRequest { - error: "No pair info exists".to_string(), - request: msg.as_slice().into(), - }), - } - } - }, - QueryRequest::Wasm(WasmQuery::Raw { contract_addr, key }) => { - let key: &[u8] = key.as_slice(); - let prefix_balance = to_length_prefixed(b"balance").to_vec(); - - let balances: &HashMap = - match self.token_querier.balances.get(contract_addr) { - Some(balances) => balances, - None => { - return Err(SystemError::InvalidRequest { - error: format!( - "No balance info exists for the contract {}", - contract_addr - ), - request: key.into(), - }) - } - }; - - if key[..prefix_balance.len()].to_vec() == prefix_balance { - let key_address: &[u8] = &key[prefix_balance.len()..]; - let address_raw: CanonicalAddr = CanonicalAddr::from(key_address); - - let api: MockApi = MockApi::new(self.canonical_length); - let address: HumanAddr = match api.human_address(&address_raw) { - Ok(v) => v, - Err(e) => { - return Err(SystemError::InvalidRequest { - error: format!("Parsing query request: {}", e), - request: key.into(), - }) - } - }; - - let balance = match balances.get(&address) { - Some(v) => v, - None => { - return Err(SystemError::InvalidRequest { - error: "Balance not found".to_string(), - request: key.into(), - }) - } - }; - - Ok(to_binary(&to_binary(&balance).unwrap())) - } else { - panic!("DO NOT ENTER HERE") - } - } - _ => self.base.handle_query(request), - } - } -} - -impl WasmMockQuerier { - pub fn new( - base: MockQuerier, - _api: A, - canonical_length: usize, - ) -> Self { - WasmMockQuerier { - base, - token_querier: TokenQuerier::default(), - tax_querier: TaxQuerier::default(), - terraswap_factory_querier: TerraswapFactoryQuerier::default(), - canonical_length, - } - } - - // configure the mint whitelist mock querier - pub fn with_token_balances(&mut self, balances: &[(&HumanAddr, &[(&HumanAddr, &Uint128)])]) { - self.token_querier = TokenQuerier::new(balances); - } - - // configure the token owner mock querier - pub fn with_tax(&mut self, rate: Decimal, caps: &[(&String, &Uint128)]) { - self.tax_querier = TaxQuerier::new(rate, caps); - } - - // configure the terraswap pair - pub fn with_terraswap_pairs(&mut self, pairs: &[(&String, &HumanAddr)]) { - self.terraswap_factory_querier = TerraswapFactoryQuerier::new(pairs); - } -} diff --git a/contracts/mirror_collector/src/state.rs b/contracts/mirror_collector/src/state.rs deleted file mode 100644 index 0bc00bd5e..000000000 --- a/contracts/mirror_collector/src/state.rs +++ /dev/null @@ -1,23 +0,0 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use cosmwasm_std::{CanonicalAddr, StdResult, Storage}; -use cosmwasm_storage::{singleton, singleton_read}; - -static KEY_CONFIG: &[u8] = b"config"; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct Config { - pub distribution_contract: CanonicalAddr, // collected rewards receiver - pub terraswap_factory: CanonicalAddr, // terraswap factory contract - pub mirror_token: CanonicalAddr, - pub base_denom: String, -} - -pub fn store_config(storage: &mut S, config: &Config) -> StdResult<()> { - singleton(storage, KEY_CONFIG).save(config) -} - -pub fn read_config(storage: &S) -> StdResult { - singleton_read(storage, KEY_CONFIG).load() -} diff --git a/contracts/mirror_collector/src/testing.rs b/contracts/mirror_collector/src/testing.rs deleted file mode 100644 index 00dffe073..000000000 --- a/contracts/mirror_collector/src/testing.rs +++ /dev/null @@ -1,164 +0,0 @@ -use crate::contract::{handle, init, query_config}; -use crate::mock_querier::mock_dependencies; -use cosmwasm_std::testing::{mock_env, MOCK_CONTRACT_ADDR}; -use cosmwasm_std::{to_binary, Coin, CosmosMsg, Decimal, HumanAddr, Uint128, WasmMsg}; -use cw20::Cw20HandleMsg; -use mirror_protocol::collector::{ConfigResponse, HandleMsg, InitMsg}; -use mirror_protocol::gov::Cw20HookMsg::DepositReward; -use terraswap::asset::{Asset, AssetInfo}; -use terraswap::pair::{Cw20HookMsg as TerraswapCw20HookMsg, HandleMsg as TerraswapHandleMsg}; - -#[test] -fn proper_initialization() { - let mut deps = mock_dependencies(20, &[]); - - let msg = InitMsg { - terraswap_factory: HumanAddr("terraswapfactory".to_string()), - distribution_contract: HumanAddr("gov0000".to_string()), - mirror_token: HumanAddr("mirror0000".to_string()), - base_denom: "uusd".to_string(), - }; - - let env = mock_env("addr0000", &[]); - - // we can just call .unwrap() to assert this was a success - let _res = init(&mut deps, env, msg).unwrap(); - - // it worked, let's query the state - let config: ConfigResponse = query_config(&deps).unwrap(); - assert_eq!("terraswapfactory", config.terraswap_factory.as_str()); - assert_eq!("uusd", config.base_denom.as_str()); -} - -#[test] -fn test_convert() { - let mut deps = mock_dependencies( - 20, - &[Coin { - denom: "uusd".to_string(), - amount: Uint128(100u128), - }], - ); - deps.querier.with_token_balances(&[( - &HumanAddr::from("tokenAPPL"), - &[(&HumanAddr::from(MOCK_CONTRACT_ADDR), &Uint128(100u128))], - )]); - - deps.querier.with_tax( - Decimal::percent(1), - &[(&"uusd".to_string(), &Uint128(1000000u128))], - ); - - deps.querier.with_terraswap_pairs(&[ - (&"uusdtokenAPPL".to_string(), &HumanAddr::from("pairAPPL")), - ( - &"uusdtokenMIRROR".to_string(), - &HumanAddr::from("pairMIRROR"), - ), - ]); - - let msg = InitMsg { - terraswap_factory: HumanAddr("terraswapfactory".to_string()), - distribution_contract: HumanAddr("gov0000".to_string()), - mirror_token: HumanAddr("tokenMIRROR".to_string()), - base_denom: "uusd".to_string(), - }; - - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::Convert { - asset_token: HumanAddr::from("tokenAPPL"), - }; - - let env = mock_env("addr0000", &[]); - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!( - res.messages, - vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("tokenAPPL"), - msg: to_binary(&Cw20HandleMsg::Send { - contract: HumanAddr::from("pairAPPL"), - amount: Uint128(100u128), - msg: Some( - to_binary(&TerraswapCw20HookMsg::Swap { - max_spread: None, - belief_price: None, - to: None, - }) - .unwrap() - ), - }) - .unwrap(), - send: vec![], - })] - ); - - let msg = HandleMsg::Convert { - asset_token: HumanAddr::from("tokenMIRROR"), - }; - - let env = mock_env("addr0000", &[]); - let res = handle(&mut deps, env, msg).unwrap(); - - // tax deduct 100 => 99 - assert_eq!( - res.messages, - vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("pairMIRROR"), - msg: to_binary(&TerraswapHandleMsg::Swap { - offer_asset: Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string() - }, - amount: Uint128(99u128), - }, - max_spread: None, - belief_price: None, - to: None, - }) - .unwrap(), - send: vec![Coin { - amount: Uint128(99u128), - denom: "uusd".to_string(), - }], - })] - ); -} - -#[test] -fn test_send() { - let mut deps = mock_dependencies(20, &[]); - deps.querier.with_token_balances(&[( - &HumanAddr::from("mirror0000"), - &[(&HumanAddr::from(MOCK_CONTRACT_ADDR), &Uint128(100u128))], - )]); - - let msg = InitMsg { - terraswap_factory: HumanAddr("terraswapfactory".to_string()), - distribution_contract: HumanAddr("gov0000".to_string()), - mirror_token: HumanAddr("mirror0000".to_string()), - base_denom: "uusd".to_string(), - }; - - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env, msg).unwrap(); - let msg = HandleMsg::Distribute {}; - - let env = mock_env("addr0000", &[]); - let res = handle(&mut deps, env, msg).unwrap(); - - assert_eq!( - res.messages, - vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("mirror0000"), - msg: to_binary(&Cw20HandleMsg::Send { - contract: HumanAddr::from("gov0000"), - amount: Uint128(100u128), - msg: Some(to_binary(&DepositReward {}).unwrap()), - }) - .unwrap(), - send: vec![], - })] - ) -} diff --git a/contracts/mirror_collector/tests/integration.rs b/contracts/mirror_collector/tests/integration.rs deleted file mode 100644 index 2470aa18f..000000000 --- a/contracts/mirror_collector/tests/integration.rs +++ /dev/null @@ -1,66 +0,0 @@ -//! This integration test tries to run and call the generated wasm. -//! It depends on a Wasm build being available, which you can create with `cargo wasm`. -//! Then running `cargo integration-test` will validate we can properly call into that generated Wasm. -//! -//! You can easily convert unit tests to integration tests as follows: -//! 1. Copy them over verbatim -//! 2. Then change -//! let mut deps = mock_dependencies(20, &[]); -//! to -//! let mut deps = mock_instance(WASM, &[]); -//! 3. If you access raw storage, where ever you see something like: -//! deps.storage.get(CONFIG_KEY).expect("no data stored"); -//! replace it with: -//! deps.with_storage(|store| { -//! let data = store.get(CONFIG_KEY).expect("no data stored"); -//! //... -//! }); -//! 4. Anywhere you see query(&deps, ...) you must replace it with query(&mut deps, ...) -use cosmwasm_std::{from_binary, Coin, HumanAddr, InitResponse}; -use cosmwasm_vm::testing::{ - init, mock_dependencies, mock_env, query, MockApi, MockQuerier, MockStorage, -}; -use cosmwasm_vm::Instance; -use mirror_protocol::collector::{ConfigResponse, InitMsg, QueryMsg}; - -// This line will test the output of cargo wasm -static WASM: &[u8] = - include_bytes!("../../../target/wasm32-unknown-unknown/release/mirror_collector.wasm"); -// You can uncomment this line instead to test productionified build from rust-optimizer -// static WASM: &[u8] = include_bytes!("../contract.wasm"); - -const DEFAULT_GAS_LIMIT: u64 = 500_000; - -pub fn mock_instance( - wasm: &[u8], - contract_balance: &[Coin], -) -> Instance { - // TODO: check_wasm is not exported from cosmwasm_vm - // let terra_features = features_from_csv("staking,terra"); - // check_wasm(wasm, &terra_features).unwrap(); - let deps = mock_dependencies(20, contract_balance); - Instance::from_code(wasm, deps, DEFAULT_GAS_LIMIT).unwrap() -} - -#[test] -fn proper_initialization() { - let mut deps = mock_instance(WASM, &[]); - - let msg = InitMsg { - terraswap_factory: HumanAddr("terraswapfactory".to_string()), - distribution_contract: HumanAddr("gov0000".to_string()), - mirror_token: HumanAddr("mirror0000".to_string()), - base_denom: "uusd".to_string(), - }; - - let env = mock_env("addr0000", &[]); - let _res: InitResponse = init(&mut deps, env, msg).unwrap(); - - // it worked, let's query the state - let res = query(&mut deps, QueryMsg::Config {}).unwrap(); - let config: ConfigResponse = from_binary(&res).unwrap(); - assert_eq!("terraswapfactory", config.terraswap_factory.as_str()); - assert_eq!("gov0000", config.distribution_contract.as_str()); - assert_eq!("mirror0000", config.mirror_token.as_str()); - assert_eq!("uusd", config.base_denom.as_str()); -} diff --git a/contracts/mirror_community/.cargo/config b/contracts/mirror_community/.cargo/config deleted file mode 100644 index 7c115322a..000000000 --- a/contracts/mirror_community/.cargo/config +++ /dev/null @@ -1,6 +0,0 @@ -[alias] -wasm = "build --release --target wasm32-unknown-unknown" -wasm-debug = "build --target wasm32-unknown-unknown" -unit-test = "test --lib --features backtraces" -integration-test = "test --test integration" -schema = "run --example schema" diff --git a/contracts/mirror_community/.editorconfig b/contracts/mirror_community/.editorconfig deleted file mode 100644 index 3d36f20b1..000000000 --- a/contracts/mirror_community/.editorconfig +++ /dev/null @@ -1,11 +0,0 @@ -root = true - -[*] -indent_style = space -indent_size = 2 -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true - -[*.rs] -indent_size = 4 diff --git a/contracts/mirror_community/.gitignore b/contracts/mirror_community/.gitignore deleted file mode 100644 index 10fe5d615..000000000 --- a/contracts/mirror_community/.gitignore +++ /dev/null @@ -1,12 +0,0 @@ -# Build results -/target - -# Text file backups -**/*.rs.bk - -# macOS -.DS_Store - -# IDEs -*.iml -.idea diff --git a/contracts/mirror_community/Cargo.toml b/contracts/mirror_community/Cargo.toml deleted file mode 100644 index 371799e2b..000000000 --- a/contracts/mirror_community/Cargo.toml +++ /dev/null @@ -1,49 +0,0 @@ -[package] -name = "mirror-community" -version = "1.1.0" -authors = ["Terraform Labs, PTE."] -edition = "2018" -description = "A Community contract for Mirror Protocol - Keeps premined MIR token which can be used through Gov proposal" -license = "Apache-2.0" - -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 = ["cranelift"] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces", "cosmwasm-vm/backtraces"] -cranelift = ["cosmwasm-vm/default-cranelift"] -singlepass = ["cosmwasm-vm/default-singlepass"] - -[dependencies] -cw20 = "0.2" -cosmwasm-std = { version = "0.10.1" } -cosmwasm-storage = { version = "0.10.1" } -mirror-protocol = { version = "1.1.0", path = "../../packages/mirror_protocol" } -schemars = "0.7" -serde = { version = "1.0.103", default-features = false, features = ["derive"] } - -[dev-dependencies] -cosmwasm-vm = { version = "0.10.1", default-features = false } -cosmwasm-schema = "0.10.1" diff --git a/contracts/mirror_community/README.md b/contracts/mirror_community/README.md deleted file mode 100644 index 4040d8d68..000000000 --- a/contracts/mirror_community/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Mirror Community - -**NOTE**: Reference documentation for this contract is available [here](https://docs.mirror.finance/contracts/community). - -The Community Contract holds the funds of the Community Pool, which can be spent through a governance poll. diff --git a/contracts/mirror_community/examples/schema.rs b/contracts/mirror_community/examples/schema.rs deleted file mode 100644 index cadca0610..000000000 --- a/contracts/mirror_community/examples/schema.rs +++ /dev/null @@ -1,17 +0,0 @@ -use std::env::current_dir; -use std::fs::create_dir_all; - -use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; -use mirror_protocol::community::{ConfigResponse, HandleMsg, InitMsg, QueryMsg}; - -fn main() { - let mut out_dir = current_dir().unwrap(); - out_dir.push("schema"); - create_dir_all(&out_dir).unwrap(); - remove_schemas(&out_dir).unwrap(); - - export_schema(&schema_for!(InitMsg), &out_dir); - export_schema(&schema_for!(HandleMsg), &out_dir); - export_schema(&schema_for!(QueryMsg), &out_dir); - export_schema(&schema_for!(ConfigResponse), &out_dir); -} diff --git a/contracts/mirror_community/rustfmt.toml b/contracts/mirror_community/rustfmt.toml deleted file mode 100644 index 11a85e6a9..000000000 --- a/contracts/mirror_community/rustfmt.toml +++ /dev/null @@ -1,15 +0,0 @@ -# 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/mirror_community/schema/config_response.json b/contracts/mirror_community/schema/config_response.json deleted file mode 100644 index a22a2a192..000000000 --- a/contracts/mirror_community/schema/config_response.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ConfigResponse", - "type": "object", - "required": [ - "mirror_token", - "owner", - "spend_limit" - ], - "properties": { - "mirror_token": { - "$ref": "#/definitions/HumanAddr" - }, - "owner": { - "$ref": "#/definitions/HumanAddr" - }, - "spend_limit": { - "$ref": "#/definitions/Uint128" - } - }, - "definitions": { - "HumanAddr": { - "type": "string" - }, - "Uint128": { - "type": "string" - } - } -} diff --git a/contracts/mirror_community/schema/handle_msg.json b/contracts/mirror_community/schema/handle_msg.json deleted file mode 100644 index 4cffb8a89..000000000 --- a/contracts/mirror_community/schema/handle_msg.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "HandleMsg", - "anyOf": [ - { - "type": "object", - "required": [ - "update_config" - ], - "properties": { - "update_config": { - "type": "object", - "properties": { - "owner": { - "anyOf": [ - { - "$ref": "#/definitions/HumanAddr" - }, - { - "type": "null" - } - ] - }, - "spend_limit": { - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - } - } - } - } - }, - { - "type": "object", - "required": [ - "spend" - ], - "properties": { - "spend": { - "type": "object", - "required": [ - "amount", - "recipient" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "recipient": { - "$ref": "#/definitions/HumanAddr" - } - } - } - } - } - ], - "definitions": { - "HumanAddr": { - "type": "string" - }, - "Uint128": { - "type": "string" - } - } -} diff --git a/contracts/mirror_community/src/contract.rs b/contracts/mirror_community/src/contract.rs deleted file mode 100644 index cecf7199e..000000000 --- a/contracts/mirror_community/src/contract.rs +++ /dev/null @@ -1,136 +0,0 @@ -use crate::state::{read_config, store_config, Config}; - -use cosmwasm_std::{ - log, to_binary, Api, Binary, CosmosMsg, Env, Extern, HandleResponse, HandleResult, HumanAddr, - InitResponse, MigrateResponse, MigrateResult, Querier, StdError, StdResult, Storage, Uint128, - WasmMsg, -}; - -use mirror_protocol::community::{ConfigResponse, HandleMsg, InitMsg, MigrateMsg, QueryMsg}; - -use cw20::Cw20HandleMsg; - -pub fn init( - deps: &mut Extern, - _env: Env, - msg: InitMsg, -) -> StdResult { - store_config( - &mut deps.storage, - &Config { - owner: deps.api.canonical_address(&msg.owner)?, - mirror_token: deps.api.canonical_address(&msg.mirror_token)?, - spend_limit: msg.spend_limit, - }, - )?; - - Ok(InitResponse::default()) -} - -pub fn handle( - deps: &mut Extern, - env: Env, - msg: HandleMsg, -) -> StdResult { - match msg { - HandleMsg::UpdateConfig { owner, spend_limit } => { - udpate_config(deps, env, owner, spend_limit) - } - HandleMsg::Spend { recipient, amount } => spend(deps, env, recipient, amount), - } -} - -pub fn udpate_config( - deps: &mut Extern, - env: Env, - owner: Option, - spend_limit: Option, -) -> HandleResult { - let mut config: Config = read_config(&deps.storage)?; - if config.owner != deps.api.canonical_address(&env.message.sender)? { - return Err(StdError::unauthorized()); - } - - if let Some(owner) = owner { - config.owner = deps.api.canonical_address(&owner)?; - } - - if let Some(spend_limit) = spend_limit { - config.spend_limit = spend_limit; - } - - store_config(&mut deps.storage, &config)?; - - Ok(HandleResponse { - messages: vec![], - log: vec![log("action", "update_config")], - data: None, - }) -} - -/// Spend -/// Owner can execute spend operation to send -/// `amount` of MIR token to `recipient` for community purpose -pub fn spend( - deps: &mut Extern, - env: Env, - recipient: HumanAddr, - amount: Uint128, -) -> HandleResult { - let config: Config = read_config(&deps.storage)?; - if config.owner != deps.api.canonical_address(&env.message.sender)? { - return Err(StdError::unauthorized()); - } - - if config.spend_limit < amount { - return Err(StdError::generic_err("Cannot spend more than spend_limit")); - } - - let mirror_token = deps.api.human_address(&config.mirror_token)?; - Ok(HandleResponse { - messages: vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: mirror_token, - send: vec![], - msg: to_binary(&Cw20HandleMsg::Transfer { - recipient: recipient.clone(), - amount, - })?, - })], - log: vec![ - log("action", "spend"), - log("recipient", recipient), - log("amount", amount), - ], - data: None, - }) -} - -pub fn query( - deps: &Extern, - msg: QueryMsg, -) -> StdResult { - match msg { - QueryMsg::Config {} => to_binary(&query_config(deps)?), - } -} - -pub fn query_config( - deps: &Extern, -) -> StdResult { - let state = read_config(&deps.storage)?; - let resp = ConfigResponse { - owner: deps.api.human_address(&state.owner)?, - mirror_token: deps.api.human_address(&state.mirror_token)?, - spend_limit: state.spend_limit, - }; - - Ok(resp) -} - -pub fn migrate( - _deps: &mut Extern, - _env: Env, - _msg: MigrateMsg, -) -> MigrateResult { - Ok(MigrateResponse::default()) -} diff --git a/contracts/mirror_community/src/lib.rs b/contracts/mirror_community/src/lib.rs deleted file mode 100644 index 3f8a0889c..000000000 --- a/contracts/mirror_community/src/lib.rs +++ /dev/null @@ -1,8 +0,0 @@ -pub mod contract; -pub mod state; - -#[cfg(test)] -mod testing; - -#[cfg(target_arch = "wasm32")] -cosmwasm_std::create_entry_points_with_migration!(contract); diff --git a/contracts/mirror_community/src/state.rs b/contracts/mirror_community/src/state.rs deleted file mode 100644 index 58a96c52a..000000000 --- a/contracts/mirror_community/src/state.rs +++ /dev/null @@ -1,22 +0,0 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use cosmwasm_std::{CanonicalAddr, StdResult, Storage, Uint128}; -use cosmwasm_storage::{singleton, singleton_read}; - -static KEY_CONFIG: &[u8] = b"config"; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct Config { - pub owner: CanonicalAddr, // mirror gov address - pub mirror_token: CanonicalAddr, // mirror token address - pub spend_limit: Uint128, // spend limit per each `spend` request -} - -pub fn store_config(storage: &mut S, config: &Config) -> StdResult<()> { - singleton(storage, KEY_CONFIG).save(config) -} - -pub fn read_config(storage: &S) -> StdResult { - singleton_read(storage, KEY_CONFIG).load() -} diff --git a/contracts/mirror_community/src/testing.rs b/contracts/mirror_community/src/testing.rs deleted file mode 100644 index 7f67870ea..000000000 --- a/contracts/mirror_community/src/testing.rs +++ /dev/null @@ -1,155 +0,0 @@ -use crate::contract::{handle, init, query}; - -use cosmwasm_std::testing::{mock_dependencies, mock_env}; -use cosmwasm_std::{from_binary, to_binary, CosmosMsg, HumanAddr, StdError, Uint128, WasmMsg}; -use cw20::Cw20HandleMsg; -use mirror_protocol::community::{ConfigResponse, HandleMsg, InitMsg, QueryMsg}; - -#[test] -fn proper_initialization() { - let mut deps = mock_dependencies(20, &[]); - - let msg = InitMsg { - owner: HumanAddr("owner0000".to_string()), - mirror_token: HumanAddr("mirror0000".to_string()), - spend_limit: Uint128::from(1000000u128), - }; - - let env = mock_env("addr0000", &[]); - - // we can just call .unwrap() to assert this was a success - let _res = init(&mut deps, env, msg).unwrap(); - - // it worked, let's query the state - let config: ConfigResponse = from_binary(&query(&deps, QueryMsg::Config {}).unwrap()).unwrap(); - assert_eq!("owner0000", config.owner.as_str()); - assert_eq!("mirror0000", config.mirror_token.as_str()); - assert_eq!(Uint128::from(1000000u128), config.spend_limit); -} - -#[test] -fn update_config() { - let mut deps = mock_dependencies(20, &[]); - - let msg = InitMsg { - owner: HumanAddr("owner0000".to_string()), - mirror_token: HumanAddr("mirror0000".to_string()), - spend_limit: Uint128::from(1000000u128), - }; - - let env = mock_env("addr0000", &[]); - - // we can just call .unwrap() to assert this was a success - let _res = init(&mut deps, env, msg).unwrap(); - - // it worked, let's query the state - let config: ConfigResponse = from_binary(&query(&deps, QueryMsg::Config {}).unwrap()).unwrap(); - assert_eq!("owner0000", config.owner.as_str()); - assert_eq!("mirror0000", config.mirror_token.as_str()); - assert_eq!(Uint128::from(1000000u128), config.spend_limit); - - let msg = HandleMsg::UpdateConfig { - owner: Some(HumanAddr::from("owner0001")), - spend_limit: None, - }; - let env = mock_env("addr0000", &[]); - let res = handle(&mut deps, env, msg.clone()); - - match res { - Err(StdError::Unauthorized { .. }) => {} - _ => panic!("DO NOT ENTER HERE"), - } - - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - let config: ConfigResponse = from_binary(&query(&deps, QueryMsg::Config {}).unwrap()).unwrap(); - assert_eq!( - config, - ConfigResponse { - owner: HumanAddr::from("owner0001"), - mirror_token: HumanAddr::from("mirror0000"), - spend_limit: Uint128::from(1000000u128), - } - ); - - // Update spend_limit - let msg = HandleMsg::UpdateConfig { - owner: None, - spend_limit: Some(Uint128::from(2000000u128)), - }; - let env = mock_env("owner0001", &[]); - let _res = handle(&mut deps, env, msg); - let config: ConfigResponse = from_binary(&query(&deps, QueryMsg::Config {}).unwrap()).unwrap(); - assert_eq!( - config, - ConfigResponse { - owner: HumanAddr::from("owner0001"), - mirror_token: HumanAddr::from("mirror0000"), - spend_limit: Uint128::from(2000000u128), - } - ); -} - -#[test] -fn test_spend() { - let mut deps = mock_dependencies(20, &[]); - - let msg = InitMsg { - owner: HumanAddr("owner0000".to_string()), - mirror_token: HumanAddr("mirror0000".to_string()), - spend_limit: Uint128::from(1000000u128), - }; - - let env = mock_env("addr0000", &[]); - - // we can just call .unwrap() to assert this was a success - let _res = init(&mut deps, env, msg).unwrap(); - - // permission failed - let msg = HandleMsg::Spend { - recipient: HumanAddr::from("addr0000"), - amount: Uint128::from(1000000u128), - }; - - let env = mock_env("addr0000", &[]); - let res = handle(&mut deps, env, msg); - match res { - Err(StdError::Unauthorized { .. }) => {} - _ => panic!("DO NOT ENTER HERE"), - } - - // failed due to spend limit - let msg = HandleMsg::Spend { - recipient: HumanAddr::from("addr0000"), - amount: Uint128::from(2000000u128), - }; - - let env = mock_env("owner0000", &[]); - let res = handle(&mut deps, env, msg); - match res { - Err(StdError::GenericErr { msg, .. }) => { - assert_eq!(msg, "Cannot spend more than spend_limit") - } - _ => panic!("DO NOT ENTER HERE"), - } - - let msg = HandleMsg::Spend { - recipient: HumanAddr::from("addr0000"), - amount: Uint128::from(1000000u128), - }; - - let env = mock_env("owner0000", &[]); - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!( - res.messages, - vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("mirror0000"), - send: vec![], - msg: to_binary(&Cw20HandleMsg::Transfer { - recipient: HumanAddr::from("addr0000"), - amount: Uint128::from(1000000u128), - }) - .unwrap(), - })] - ); -} diff --git a/contracts/mirror_community/tests/integration.rs b/contracts/mirror_community/tests/integration.rs deleted file mode 100644 index a82321b4c..000000000 --- a/contracts/mirror_community/tests/integration.rs +++ /dev/null @@ -1,66 +0,0 @@ -//! This integration test tries to run and call the generated wasm. -//! It depends on a Wasm build being available, which you can create with `cargo wasm`. -//! Then running `cargo integration-test` will validate we can properly call into that generated Wasm. -//! -//! You can easily convert unit tests to integration tests as follows: -//! 1. Copy them over verbatim -//! 2. Then change -//! let mut deps = mock_dependencies(20, &[]); -//! to -//! let mut deps = mock_instance(WASM, &[]); -//! 3. If you access raw storage, where ever you see something like: -//! deps.storage.get(CONFIG_KEY).expect("no data stored"); -//! replace it with: -//! deps.with_storage(|store| { -//! let data = store.get(CONFIG_KEY).expect("no data stored"); -//! //... -//! }); -//! 4. Anywhere you see query(&deps, ...) you must replace it with query(&mut deps, ...) -use cosmwasm_std::{from_binary, Coin, HumanAddr, InitResponse, Uint128}; -use cosmwasm_vm::testing::{ - init, mock_dependencies, mock_env, query, MockApi, MockQuerier, MockStorage, -}; -use cosmwasm_vm::Instance; -use mirror_protocol::community::{ConfigResponse, InitMsg, QueryMsg}; - -// This line will test the output of cargo wasm -static WASM: &[u8] = - include_bytes!("../../../target/wasm32-unknown-unknown/release/mirror_community.wasm"); -// You can uncomment this line instead to test productionified build from rust-optimizer -// static WASM: &[u8] = include_bytes!("../contract.wasm"); - -const DEFAULT_GAS_LIMIT: u64 = 500_000; - -pub fn mock_instance( - wasm: &[u8], - contract_balance: &[Coin], -) -> Instance { - // TODO: check_wasm is not exported from cosmwasm_vm - // let terra_features = features_from_csv("staking,terra"); - // check_wasm(wasm, &terra_features).unwrap(); - let deps = mock_dependencies(20, contract_balance); - Instance::from_code(wasm, deps, DEFAULT_GAS_LIMIT).unwrap() -} - -#[test] -fn proper_initialization() { - let mut deps = mock_instance(WASM, &[]); - - let msg = InitMsg { - owner: HumanAddr("owner0000".to_string()), - mirror_token: HumanAddr("mirror0000".to_string()), - spend_limit: Uint128::from(1000000u128), - }; - - let env = mock_env("addr0000", &[]); - - // we can just call .unwrap() to assert this was a success - let _res: InitResponse = init(&mut deps, env, msg).unwrap(); - - // it worked, let's query the state - let config: ConfigResponse = - from_binary(&query(&mut deps, QueryMsg::Config {}).unwrap()).unwrap(); - assert_eq!("owner0000", config.owner.as_str()); - assert_eq!("mirror0000", config.mirror_token.as_str()); - assert_eq!(Uint128::from(1000000u128), config.spend_limit); -} diff --git a/contracts/mirror_factory/.cargo/config b/contracts/mirror_factory/.cargo/config deleted file mode 100644 index 7c115322a..000000000 --- a/contracts/mirror_factory/.cargo/config +++ /dev/null @@ -1,6 +0,0 @@ -[alias] -wasm = "build --release --target wasm32-unknown-unknown" -wasm-debug = "build --target wasm32-unknown-unknown" -unit-test = "test --lib --features backtraces" -integration-test = "test --test integration" -schema = "run --example schema" diff --git a/contracts/mirror_factory/.editorconfig b/contracts/mirror_factory/.editorconfig deleted file mode 100644 index 3d36f20b1..000000000 --- a/contracts/mirror_factory/.editorconfig +++ /dev/null @@ -1,11 +0,0 @@ -root = true - -[*] -indent_style = space -indent_size = 2 -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true - -[*.rs] -indent_size = 4 diff --git a/contracts/mirror_factory/.gitignore b/contracts/mirror_factory/.gitignore deleted file mode 100644 index 10fe5d615..000000000 --- a/contracts/mirror_factory/.gitignore +++ /dev/null @@ -1,12 +0,0 @@ -# Build results -/target - -# Text file backups -**/*.rs.bk - -# macOS -.DS_Store - -# IDEs -*.iml -.idea diff --git a/contracts/mirror_factory/Cargo.toml b/contracts/mirror_factory/Cargo.toml deleted file mode 100644 index 3c00d7e29..000000000 --- a/contracts/mirror_factory/Cargo.toml +++ /dev/null @@ -1,50 +0,0 @@ -[package] -name = "mirror-factory" -version = "1.1.0" -authors = ["Terraform Labs, PTE."] -edition = "2018" -description = "A Factory contract for Mirror Protocol - mint mirror token and distribute it to registered staking contracts" -license = "Apache-2.0" - -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 = ["cranelift"] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces", "cosmwasm-vm/backtraces"] -cranelift = ["cosmwasm-vm/default-cranelift"] -singlepass = ["cosmwasm-vm/default-singlepass"] - -[dependencies] -cw20 = "0.2" -cosmwasm-std = { version = "0.10.1", features = ["iterator"] } -cosmwasm-storage = { version = "0.10.1", features = ["iterator"] } -mirror-protocol = { version = "1.1.0", path = "../../packages/mirror_protocol" } -terraswap = "1.1.0" -schemars = "0.7" -serde = { version = "1.0.103", default-features = false, features = ["derive"] } - -[dev-dependencies] -cosmwasm-vm = { version = "0.10.1", default-features = false, features = ["iterator"] } -cosmwasm-schema = "0.10.1" diff --git a/contracts/mirror_factory/README.md b/contracts/mirror_factory/README.md deleted file mode 100644 index 49238db60..000000000 --- a/contracts/mirror_factory/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Mirror Factory - -**NOTE**: Reference documentation for this contract is available [here](https://docs.mirror.finance/contracts/factory). - -The Factory contract is Mirror Protocol's central directory and organizes information related to mAssets and the Mirror Token (MIR). It is also responsible for minting new MIR tokens each block and distributing them to the Staking Contract for rewarding LP Token stakers. - -After the initial bootstrapping of Mirror Protocol contracts, the Factory is assigned to be the owner for the Mint, Oracle, Staking, and Collector contracts. The Factory is owned by the Gov Contract. diff --git a/contracts/mirror_factory/examples/schema.rs b/contracts/mirror_factory/examples/schema.rs deleted file mode 100644 index b0a47e164..000000000 --- a/contracts/mirror_factory/examples/schema.rs +++ /dev/null @@ -1,21 +0,0 @@ -use std::env::current_dir; -use std::fs::create_dir_all; - -use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; -use mirror_protocol::factory::{ - ConfigResponse, DistributionInfoResponse, HandleMsg, InitMsg, MigrateMsg, QueryMsg, -}; - -fn main() { - let mut out_dir = current_dir().unwrap(); - out_dir.push("schema"); - create_dir_all(&out_dir).unwrap(); - remove_schemas(&out_dir).unwrap(); - - export_schema(&schema_for!(InitMsg), &out_dir); - export_schema(&schema_for!(HandleMsg), &out_dir); - export_schema(&schema_for!(QueryMsg), &out_dir); - export_schema(&schema_for!(MigrateMsg), &out_dir); - export_schema(&schema_for!(ConfigResponse), &out_dir); - export_schema(&schema_for!(DistributionInfoResponse), &out_dir); -} diff --git a/contracts/mirror_factory/rustfmt.toml b/contracts/mirror_factory/rustfmt.toml deleted file mode 100644 index 11a85e6a9..000000000 --- a/contracts/mirror_factory/rustfmt.toml +++ /dev/null @@ -1,15 +0,0 @@ -# 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/mirror_factory/schema/config_response.json b/contracts/mirror_factory/schema/config_response.json deleted file mode 100644 index e62b81c10..000000000 --- a/contracts/mirror_factory/schema/config_response.json +++ /dev/null @@ -1,85 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ConfigResponse", - "type": "object", - "required": [ - "base_denom", - "commission_collector", - "distribution_schedule", - "genesis_time", - "mint_contract", - "mirror_token", - "oracle_contract", - "owner", - "staking_contract", - "terraswap_factory", - "token_code_id" - ], - "properties": { - "base_denom": { - "type": "string" - }, - "commission_collector": { - "$ref": "#/definitions/HumanAddr" - }, - "distribution_schedule": { - "type": "array", - "items": { - "type": "array", - "items": [ - { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - { - "$ref": "#/definitions/Uint128" - } - ], - "maxItems": 3, - "minItems": 3 - } - }, - "genesis_time": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "mint_contract": { - "$ref": "#/definitions/HumanAddr" - }, - "mirror_token": { - "$ref": "#/definitions/HumanAddr" - }, - "oracle_contract": { - "$ref": "#/definitions/HumanAddr" - }, - "owner": { - "$ref": "#/definitions/HumanAddr" - }, - "staking_contract": { - "$ref": "#/definitions/HumanAddr" - }, - "terraswap_factory": { - "$ref": "#/definitions/HumanAddr" - }, - "token_code_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "definitions": { - "HumanAddr": { - "type": "string" - }, - "Uint128": { - "type": "string" - } - } -} diff --git a/contracts/mirror_factory/schema/distribution_info_response.json b/contracts/mirror_factory/schema/distribution_info_response.json deleted file mode 100644 index 01dfeb45c..000000000 --- a/contracts/mirror_factory/schema/distribution_info_response.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "DistributionInfoResponse", - "type": "object", - "required": [ - "last_distributed", - "weights" - ], - "properties": { - "last_distributed": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "weights": { - "type": "array", - "items": { - "type": "array", - "items": [ - { - "$ref": "#/definitions/HumanAddr" - }, - { - "type": "integer", - "format": "uint32", - "minimum": 0.0 - } - ], - "maxItems": 2, - "minItems": 2 - } - } - }, - "definitions": { - "HumanAddr": { - "type": "string" - } - } -} diff --git a/contracts/mirror_factory/schema/handle_msg.json b/contracts/mirror_factory/schema/handle_msg.json deleted file mode 100644 index 21e837134..000000000 --- a/contracts/mirror_factory/schema/handle_msg.json +++ /dev/null @@ -1,387 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "HandleMsg", - "anyOf": [ - { - "description": "Owner Operations", - "type": "object", - "required": [ - "post_initialize" - ], - "properties": { - "post_initialize": { - "type": "object", - "required": [ - "commission_collector", - "mint_contract", - "mirror_token", - "oracle_contract", - "owner", - "staking_contract", - "terraswap_factory" - ], - "properties": { - "commission_collector": { - "$ref": "#/definitions/HumanAddr" - }, - "mint_contract": { - "$ref": "#/definitions/HumanAddr" - }, - "mirror_token": { - "$ref": "#/definitions/HumanAddr" - }, - "oracle_contract": { - "$ref": "#/definitions/HumanAddr" - }, - "owner": { - "$ref": "#/definitions/HumanAddr" - }, - "staking_contract": { - "$ref": "#/definitions/HumanAddr" - }, - "terraswap_factory": { - "$ref": "#/definitions/HumanAddr" - } - } - } - } - }, - { - "type": "object", - "required": [ - "update_config" - ], - "properties": { - "update_config": { - "type": "object", - "properties": { - "distribution_schedule": { - "type": [ - "array", - "null" - ], - "items": { - "type": "array", - "items": [ - { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - { - "$ref": "#/definitions/Uint128" - } - ], - "maxItems": 3, - "minItems": 3 - } - }, - "owner": { - "anyOf": [ - { - "$ref": "#/definitions/HumanAddr" - }, - { - "type": "null" - } - ] - }, - "token_code_id": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - } - } - } - } - }, - { - "type": "object", - "required": [ - "update_weight" - ], - "properties": { - "update_weight": { - "type": "object", - "required": [ - "asset_token", - "weight" - ], - "properties": { - "asset_token": { - "$ref": "#/definitions/HumanAddr" - }, - "weight": { - "type": "integer", - "format": "uint32", - "minimum": 0.0 - } - } - } - } - }, - { - "type": "object", - "required": [ - "whitelist" - ], - "properties": { - "whitelist": { - "type": "object", - "required": [ - "name", - "oracle_feeder", - "params", - "symbol" - ], - "properties": { - "name": { - "description": "asset name used to create token contract", - "type": "string" - }, - "oracle_feeder": { - "description": "authorized asset oracle feeder", - "allOf": [ - { - "$ref": "#/definitions/HumanAddr" - } - ] - }, - "params": { - "description": "used to create all necessary contract or register asset", - "allOf": [ - { - "$ref": "#/definitions/Params" - } - ] - }, - "symbol": { - "description": "asset symbol used to create token contract", - "type": "string" - } - } - } - } - }, - { - "description": "Internal use", - "type": "object", - "required": [ - "token_creation_hook" - ], - "properties": { - "token_creation_hook": { - "type": "object", - "required": [ - "oracle_feeder" - ], - "properties": { - "oracle_feeder": { - "$ref": "#/definitions/HumanAddr" - } - } - } - } - }, - { - "description": "Internal use except MIR registration", - "type": "object", - "required": [ - "terraswap_creation_hook" - ], - "properties": { - "terraswap_creation_hook": { - "type": "object", - "required": [ - "asset_token" - ], - "properties": { - "asset_token": { - "$ref": "#/definitions/HumanAddr" - } - } - } - } - }, - { - "type": "object", - "required": [ - "pass_command" - ], - "properties": { - "pass_command": { - "type": "object", - "required": [ - "contract_addr", - "msg" - ], - "properties": { - "contract_addr": { - "$ref": "#/definitions/HumanAddr" - }, - "msg": { - "$ref": "#/definitions/Binary" - } - } - } - } - }, - { - "description": "Feeder Operations ////////////////// Revoke asset from MIR rewards pool and register end_price to mint contract", - "type": "object", - "required": [ - "revoke_asset" - ], - "properties": { - "revoke_asset": { - "type": "object", - "required": [ - "asset_token", - "end_price" - ], - "properties": { - "asset_token": { - "$ref": "#/definitions/HumanAddr" - }, - "end_price": { - "$ref": "#/definitions/Decimal" - } - } - } - } - }, - { - "description": "Migrate asset to new asset by registering end_price to mint contract and add the new asset to MIR rewards pool", - "type": "object", - "required": [ - "migrate_asset" - ], - "properties": { - "migrate_asset": { - "type": "object", - "required": [ - "end_price", - "from_token", - "name", - "symbol" - ], - "properties": { - "end_price": { - "$ref": "#/definitions/Decimal" - }, - "from_token": { - "$ref": "#/definitions/HumanAddr" - }, - "name": { - "type": "string" - }, - "symbol": { - "type": "string" - } - } - } - } - }, - { - "description": "User Operations", - "type": "object", - "required": [ - "distribute" - ], - "properties": { - "distribute": { - "type": "object" - } - } - } - ], - "definitions": { - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", - "type": "string" - }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "HumanAddr": { - "type": "string" - }, - "Params": { - "type": "object", - "required": [ - "auction_discount", - "min_collateral_ratio" - ], - "properties": { - "auction_discount": { - "description": "Auction discount rate applied to asset mint", - "allOf": [ - { - "$ref": "#/definitions/Decimal" - } - ] - }, - "min_collateral_ratio": { - "description": "Minium collateral ratio applied to asset mint", - "allOf": [ - { - "$ref": "#/definitions/Decimal" - } - ] - }, - "min_collateral_ratio_after_ipo": { - "description": "For pre-IPO assets, collateral ratio for the asset after ipo", - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - }, - "mint_period": { - "description": "For pre-IPO assets, time period after asset creation in which minting is enabled", - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - }, - "pre_ipo_price": { - "description": "For pre-IPO assets, fixed price during minting period", - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - }, - "weight": { - "description": "Distribution weight (default is 30, which is 1/10 of MIR distribution weight)", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - } - } - }, - "Uint128": { - "type": "string" - } - } -} diff --git a/contracts/mirror_factory/schema/init_msg.json b/contracts/mirror_factory/schema/init_msg.json deleted file mode 100644 index 02e44d578..000000000 --- a/contracts/mirror_factory/schema/init_msg.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InitMsg", - "type": "object", - "required": [ - "base_denom", - "distribution_schedule", - "token_code_id" - ], - "properties": { - "base_denom": { - "type": "string" - }, - "distribution_schedule": { - "type": "array", - "items": { - "type": "array", - "items": [ - { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - { - "$ref": "#/definitions/Uint128" - } - ], - "maxItems": 3, - "minItems": 3 - } - }, - "token_code_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "definitions": { - "Uint128": { - "type": "string" - } - } -} diff --git a/contracts/mirror_factory/schema/migrate_msg.json b/contracts/mirror_factory/schema/migrate_msg.json deleted file mode 100644 index 666fb7e9f..000000000 --- a/contracts/mirror_factory/schema/migrate_msg.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "MigrateMsg", - "description": "We currently take no arguments for migrations", - "type": "object" -} diff --git a/contracts/mirror_factory/schema/query_msg.json b/contracts/mirror_factory/schema/query_msg.json deleted file mode 100644 index 8139c2619..000000000 --- a/contracts/mirror_factory/schema/query_msg.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryMsg", - "anyOf": [ - { - "type": "object", - "required": [ - "config" - ], - "properties": { - "config": { - "type": "object" - } - } - }, - { - "type": "object", - "required": [ - "distribution_info" - ], - "properties": { - "distribution_info": { - "type": "object" - } - } - } - ] -} diff --git a/contracts/mirror_factory/src/contract.rs b/contracts/mirror_factory/src/contract.rs deleted file mode 100644 index 0631da2dd..000000000 --- a/contracts/mirror_factory/src/contract.rs +++ /dev/null @@ -1,721 +0,0 @@ -use cosmwasm_std::{ - log, to_binary, Api, Binary, CanonicalAddr, CosmosMsg, Decimal, Env, Extern, HandleResponse, - HandleResult, HumanAddr, InitResponse, LogAttribute, MigrateResponse, MigrateResult, Querier, - StdError, StdResult, Storage, Uint128, WasmMsg, -}; - -use crate::querier::{load_mint_asset_config, load_oracle_feeder}; -use crate::state::{ - decrease_total_weight, increase_total_weight, read_all_weight, read_config, - read_last_distributed, read_params, read_total_weight, read_weight, remove_params, - remove_weight, store_config, store_last_distributed, store_params, store_total_weight, - store_weight, Config, -}; - -use mirror_protocol::factory::{ - ConfigResponse, DistributionInfoResponse, HandleMsg, InitMsg, MigrateMsg, Params, QueryMsg, -}; -use mirror_protocol::mint::{HandleMsg as MintHandleMsg, IPOParams}; -use mirror_protocol::oracle::HandleMsg as OracleHandleMsg; -use mirror_protocol::staking::Cw20HookMsg as StakingCw20HookMsg; -use mirror_protocol::staking::HandleMsg as StakingHandleMsg; - -use cw20::{Cw20HandleMsg, MinterResponse}; -use terraswap::asset::{AssetInfo, PairInfo}; -use terraswap::factory::HandleMsg as TerraswapFactoryHandleMsg; -use terraswap::hook::InitHook; -use terraswap::querier::query_pair_info; -use terraswap::token::InitMsg as TokenInitMsg; - -const MIRROR_TOKEN_WEIGHT: u32 = 300u32; -const NORMAL_TOKEN_WEIGHT: u32 = 30u32; -const DISTRIBUTION_INTERVAL: u64 = 60u64; - -pub fn init( - deps: &mut Extern, - env: Env, - msg: InitMsg, -) -> StdResult { - store_config( - &mut deps.storage, - &Config { - owner: CanonicalAddr::default(), - mirror_token: CanonicalAddr::default(), - mint_contract: CanonicalAddr::default(), - oracle_contract: CanonicalAddr::default(), - terraswap_factory: CanonicalAddr::default(), - staking_contract: CanonicalAddr::default(), - commission_collector: CanonicalAddr::default(), - token_code_id: msg.token_code_id, - base_denom: msg.base_denom, - genesis_time: env.block.time, - distribution_schedule: msg.distribution_schedule, - }, - )?; - - store_total_weight(&mut deps.storage, 0u32)?; - store_last_distributed(&mut deps.storage, env.block.time)?; - Ok(InitResponse::default()) -} - -pub fn handle( - deps: &mut Extern, - env: Env, - msg: HandleMsg, -) -> StdResult { - match msg { - HandleMsg::PostInitialize { - owner, - mirror_token, - mint_contract, - oracle_contract, - terraswap_factory, - staking_contract, - commission_collector, - } => post_initialize( - deps, - env, - owner, - mirror_token, - mint_contract, - oracle_contract, - terraswap_factory, - staking_contract, - commission_collector, - ), - HandleMsg::UpdateConfig { - owner, - token_code_id, - distribution_schedule, - } => update_config(deps, env, owner, token_code_id, distribution_schedule), - HandleMsg::UpdateWeight { - asset_token, - weight, - } => update_weight(deps, env, asset_token, weight), - HandleMsg::Whitelist { - name, - symbol, - oracle_feeder, - params, - } => whitelist(deps, env, name, symbol, oracle_feeder, params), - HandleMsg::TokenCreationHook { oracle_feeder } => { - token_creation_hook(deps, env, oracle_feeder) - } - HandleMsg::TerraswapCreationHook { asset_token } => { - terraswap_creation_hook(deps, env, asset_token) - } - HandleMsg::Distribute {} => distribute(deps, env), - HandleMsg::PassCommand { contract_addr, msg } => { - pass_command(deps, env, contract_addr, msg) - } - HandleMsg::RevokeAsset { - asset_token, - end_price, - } => revoke_asset(deps, env, asset_token, end_price), - HandleMsg::MigrateAsset { - name, - symbol, - from_token, - end_price, - } => migrate_asset(deps, env, name, symbol, from_token, end_price), - } -} - -#[allow(clippy::too_many_arguments)] -pub fn post_initialize( - deps: &mut Extern, - _env: Env, - owner: HumanAddr, - mirror_token: HumanAddr, - mint_contract: HumanAddr, - oracle_contract: HumanAddr, - terraswap_factory: HumanAddr, - staking_contract: HumanAddr, - commission_collector: HumanAddr, -) -> HandleResult { - let mut config: Config = read_config(&deps.storage)?; - if config.owner != CanonicalAddr::default() { - return Err(StdError::unauthorized()); - } - - config.owner = deps.api.canonical_address(&owner)?; - config.mirror_token = deps.api.canonical_address(&mirror_token)?; - config.mint_contract = deps.api.canonical_address(&mint_contract)?; - config.oracle_contract = deps.api.canonical_address(&oracle_contract)?; - config.terraswap_factory = deps.api.canonical_address(&terraswap_factory)?; - config.staking_contract = deps.api.canonical_address(&staking_contract)?; - config.commission_collector = deps.api.canonical_address(&commission_collector)?; - store_config(&mut deps.storage, &config)?; - - Ok(HandleResponse::default()) -} - -pub fn update_config( - deps: &mut Extern, - env: Env, - owner: Option, - token_code_id: Option, - distribution_schedule: Option>, -) -> HandleResult { - let mut config: Config = read_config(&deps.storage)?; - if config.owner != deps.api.canonical_address(&env.message.sender)? { - return Err(StdError::unauthorized()); - } - - if let Some(owner) = owner { - config.owner = deps.api.canonical_address(&owner)?; - } - - if let Some(distribution_schedule) = distribution_schedule { - config.distribution_schedule = distribution_schedule; - } - - if let Some(token_code_id) = token_code_id { - config.token_code_id = token_code_id; - } - - store_config(&mut deps.storage, &config)?; - - Ok(HandleResponse { - messages: vec![], - log: vec![log("action", "update_config")], - data: None, - }) -} - -pub fn update_weight( - deps: &mut Extern, - env: Env, - asset_token: HumanAddr, - weight: u32, -) -> HandleResult { - let config: Config = read_config(&deps.storage)?; - if config.owner != deps.api.canonical_address(&env.message.sender)? { - return Err(StdError::unauthorized()); - } - - let asset_token_raw = deps.api.canonical_address(&asset_token)?; - let origin_weight = read_weight(&deps.storage, &asset_token_raw)?; - store_weight(&mut deps.storage, &asset_token_raw, weight)?; - - let origin_total_weight = read_total_weight(&deps.storage)?; - store_total_weight( - &mut deps.storage, - origin_total_weight + weight - origin_weight, - )?; - - Ok(HandleResponse { - messages: vec![], - log: vec![ - log("action", "update_weight"), - log("asset_token", asset_token), - log("weight", weight), - ], - data: None, - }) -} - -// just for by passing command to other contract like update config -pub fn pass_command( - deps: &mut Extern, - env: Env, - contract_addr: HumanAddr, - msg: Binary, -) -> HandleResult { - let config: Config = read_config(&deps.storage)?; - if config.owner != deps.api.canonical_address(&env.message.sender)? { - return Err(StdError::unauthorized()); - } - - Ok(HandleResponse { - messages: vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr, - msg, - send: vec![], - })], - log: vec![], - data: None, - }) -} - -/// Whitelisting process -/// 1. Create asset token contract with `config.token_code_id` with `minter` argument -/// 2. Call `TokenCreationHook` -/// 2-1. Initialize distribution info -/// 2-2. Register asset to mint contract -/// 2-3. Register asset and oracle feeder to oracle contract -/// 2-4. Create terraswap pair through terraswap factory -/// 3. Call `TerraswapCreationHook` -/// 3-1. Register asset to staking contract -pub fn whitelist( - deps: &mut Extern, - env: Env, - name: String, - symbol: String, - oracle_feeder: HumanAddr, - params: Params, -) -> HandleResult { - let config: Config = read_config(&deps.storage)?; - if config.owner != deps.api.canonical_address(&env.message.sender)? { - return Err(StdError::unauthorized()); - } - - if read_params(&deps.storage).is_ok() { - return Err(StdError::generic_err("A whitelist process is in progress")); - } - - store_params(&mut deps.storage, ¶ms)?; - - Ok(HandleResponse { - messages: vec![CosmosMsg::Wasm(WasmMsg::Instantiate { - code_id: config.token_code_id, - send: vec![], - label: None, - msg: to_binary(&TokenInitMsg { - name: name.clone(), - symbol: symbol.to_string(), - decimals: 6u8, - initial_balances: vec![], - mint: Some(MinterResponse { - minter: deps.api.human_address(&config.mint_contract)?, - cap: None, - }), - init_hook: Some(InitHook { - contract_addr: env.contract.address, - msg: to_binary(&HandleMsg::TokenCreationHook { oracle_feeder })?, - }), - })?, - })], - log: vec![ - log("action", "whitelist"), - log("symbol", symbol), - log("name", name), - ], - data: None, - }) -} - -/// TokenCreationHook -/// 1. Initialize distribution info -/// 2. Register asset to mint contract -/// 3. Register asset and oracle feeder to oracle contract -/// 4. Create terraswap pair through terraswap factory with `TerraswapCreationHook` -pub fn token_creation_hook( - deps: &mut Extern, - env: Env, - oracle_feeder: HumanAddr, -) -> HandleResult { - let config: Config = read_config(&deps.storage)?; - - // If the param is not exists, it means there is no whitelist process in progress - let params: Params = match read_params(&deps.storage) { - Ok(v) => v, - Err(_) => return Err(StdError::generic_err("No whitelist process in progress")), - }; - - let asset_token = env.message.sender; - let asset_token_raw = deps.api.canonical_address(&asset_token)?; - - // If weight is given as params, we use that or just use default - let weight = if let Some(weight) = params.weight { - weight - } else { - NORMAL_TOKEN_WEIGHT - }; - - // Increase total weight - store_weight(&mut deps.storage, &asset_token_raw, weight)?; - increase_total_weight(&mut deps.storage, weight)?; - - // Remove params == clear flag - remove_params(&mut deps.storage); - - let mut logs: Vec = vec![]; - - // Check if all IPO params exist - let ipo_params: Option = - if let (Some(mint_period), Some(min_collateral_ratio_after_ipo), Some(pre_ipo_price)) = ( - params.mint_period, - params.min_collateral_ratio_after_ipo, - params.pre_ipo_price, - ) { - let mint_end: u64 = env.block.time + mint_period; - logs = vec![ - log("is_pre_ipo", true), - log("mint_end", mint_end.to_string()), - log( - "min_collateral_ratio_after_ipo", - min_collateral_ratio_after_ipo.to_string(), - ), - log("pre_ipo_price", pre_ipo_price.to_string()), - ]; - Some(IPOParams { - mint_end: mint_end, - min_collateral_ratio_after_ipo, - pre_ipo_price, - }) - } else { - logs.push(log("is_pre_ipo", false)); - None - }; - - // Register asset to mint contract - // Register asset to oracle contract - // Create terraswap pair - Ok(HandleResponse { - messages: vec![ - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: deps.api.human_address(&config.mint_contract)?, - send: vec![], - msg: to_binary(&MintHandleMsg::RegisterAsset { - asset_token: asset_token.clone(), - auction_discount: params.auction_discount, - min_collateral_ratio: params.min_collateral_ratio, - ipo_params, - })?, - }), - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: deps.api.human_address(&config.oracle_contract)?, - send: vec![], - msg: to_binary(&OracleHandleMsg::RegisterAsset { - asset_token: asset_token.clone(), - feeder: oracle_feeder, - })?, - }), - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: deps.api.human_address(&config.terraswap_factory)?, - send: vec![], - msg: to_binary(&TerraswapFactoryHandleMsg::CreatePair { - asset_infos: [ - AssetInfo::NativeToken { - denom: config.base_denom, - }, - AssetInfo::Token { - contract_addr: asset_token.clone(), - }, - ], - init_hook: Some(InitHook { - msg: to_binary(&HandleMsg::TerraswapCreationHook { - asset_token: asset_token.clone(), - })?, - contract_addr: env.contract.address, - }), - })?, - }), - ], - log: vec![vec![log("asset_token_addr", asset_token.as_str())], logs].concat(), - data: None, - }) -} - -/// TerraswapCreationHook -/// 1. Register asset and liquidity(LP) token to staking contract -pub fn terraswap_creation_hook( - deps: &mut Extern, - env: Env, - asset_token: HumanAddr, -) -> HandleResult { - // Now terraswap contract is already created, - // and liquidity token also created - let config: Config = read_config(&deps.storage)?; - let asset_token_raw = deps.api.canonical_address(&asset_token)?; - let sender_raw = deps.api.canonical_address(&env.message.sender)?; - - if config.mirror_token == asset_token_raw { - store_weight(&mut deps.storage, &asset_token_raw, MIRROR_TOKEN_WEIGHT)?; - increase_total_weight(&mut deps.storage, MIRROR_TOKEN_WEIGHT)?; - } else if config.terraswap_factory != sender_raw { - return Err(StdError::unauthorized()); - } - - let asset_infos = [ - AssetInfo::NativeToken { - denom: config.base_denom, - }, - AssetInfo::Token { - contract_addr: asset_token.clone(), - }, - ]; - - // Load terraswap pair info - let pair_info: PairInfo = query_pair_info( - &deps, - &deps.api.human_address(&config.terraswap_factory)?, - &asset_infos, - )?; - - // Execute staking contract to register staking token of newly created asset - Ok(HandleResponse { - messages: vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: deps.api.human_address(&config.staking_contract)?, - send: vec![], - msg: to_binary(&StakingHandleMsg::RegisterAsset { - asset_token, - staking_token: pair_info.liquidity_token, - })?, - })], - log: vec![], - data: None, - }) -} - -/// Distribute -/// Anyone can execute distribute operation to distribute -/// mirror inflation rewards on the staking pool -pub fn distribute( - deps: &mut Extern, - env: Env, -) -> HandleResult { - let last_distributed = read_last_distributed(&deps.storage)?; - if last_distributed + DISTRIBUTION_INTERVAL > env.block.time { - return Err(StdError::generic_err( - "Cannot distribute mirror token before interval", - )); - } - - let config: Config = read_config(&deps.storage)?; - let time_elapsed = env.block.time - config.genesis_time; - let last_time_elapsed = last_distributed - config.genesis_time; - let mut target_distribution_amount: Uint128 = Uint128::zero(); - for s in config.distribution_schedule.iter() { - if s.0 > time_elapsed || s.1 < last_time_elapsed { - continue; - } - - // min(s.1, time_elapsed) - max(s.0, last_time_elapsed) - let time_duration = - std::cmp::min(s.1, time_elapsed) - std::cmp::max(s.0, last_time_elapsed); - - let time_slot = s.1 - s.0; - let distribution_amount_per_sec: Decimal = Decimal::from_ratio(s.2, time_slot); - target_distribution_amount += distribution_amount_per_sec * Uint128(time_duration as u128); - } - - let staking_contract = deps.api.human_address(&config.staking_contract)?; - let mirror_token = deps.api.human_address(&config.mirror_token)?; - - let total_weight: u32 = read_total_weight(&deps.storage)?; - let mut distribution_amount: Uint128 = Uint128::zero(); - let weights: Vec<(CanonicalAddr, u32)> = read_all_weight(&deps.storage)?; - let rewards: Vec<(HumanAddr, Uint128)> = weights - .iter() - .map(|w| { - let amount = Uint128::from( - target_distribution_amount.u128() * (w.1 as u128) / (total_weight as u128), - ); - - if amount.is_zero() { - return Err(StdError::generic_err("cannot distribute zero amount")); - } - - distribution_amount += amount; - Ok((deps.api.human_address(&w.0)?, amount)) - }) - .filter(|m| m.is_ok()) - .collect::>>()?; - - // store last distributed - store_last_distributed(&mut deps.storage, env.block.time)?; - - const SPLIT_UNIT: usize = 10; - let rewards_vec: Vec> = - rewards.chunks(SPLIT_UNIT).map(|v| v.to_vec()).collect(); - - // mint token to self and try send minted tokens to staking contract - Ok(HandleResponse { - messages: rewards_vec - .into_iter() - .map(|rewards| { - Ok(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: mirror_token.clone(), - msg: to_binary(&Cw20HandleMsg::Send { - contract: staking_contract.clone(), - amount: rewards.iter().map(|v| v.1.u128()).sum::().into(), - msg: Some(to_binary(&StakingCw20HookMsg::DepositReward { rewards })?), - })?, - send: vec![], - })) - }) - .collect::>>()?, - log: vec![ - log("action", "distribute"), - log("distribution_amount", distribution_amount.to_string()), - ], - data: None, - }) -} - -pub fn revoke_asset( - deps: &mut Extern, - env: Env, - asset_token: HumanAddr, - end_price: Decimal, -) -> HandleResult { - let config: Config = read_config(&deps.storage)?; - let asset_token_raw: CanonicalAddr = deps.api.canonical_address(&asset_token)?; - let oracle_feeder: HumanAddr = deps.api.human_address(&load_oracle_feeder( - &deps, - &deps.api.human_address(&config.oracle_contract)?, - &asset_token_raw, - )?)?; - let sender_raw = deps.api.canonical_address(&env.message.sender)?; - - // revoke asset can only be executed by the feeder or the owner (gov contract) - if oracle_feeder != env.message.sender && config.owner != sender_raw { - return Err(StdError::unauthorized()); - } - - remove_weight(&mut deps.storage, &asset_token_raw); - decrease_total_weight(&mut deps.storage, NORMAL_TOKEN_WEIGHT)?; - - Ok(HandleResponse { - messages: vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: deps.api.human_address(&config.mint_contract)?, - send: vec![], - msg: to_binary(&MintHandleMsg::RegisterMigration { - asset_token: asset_token.clone(), - end_price, - })?, - })], - log: vec![ - log("action", "revoke_asset"), - log("end_price", end_price.to_string()), - log("asset_token", asset_token.to_string()), - ], - data: None, - }) -} - -pub fn migrate_asset( - deps: &mut Extern, - env: Env, - name: String, - symbol: String, - asset_token: HumanAddr, - end_price: Decimal, -) -> HandleResult { - let config: Config = read_config(&deps.storage)?; - let asset_token_raw: CanonicalAddr = deps.api.canonical_address(&asset_token)?; - let oracle_feeder: HumanAddr = deps.api.human_address(&load_oracle_feeder( - &deps, - &deps.api.human_address(&config.oracle_contract)?, - &asset_token_raw, - )?)?; - - if oracle_feeder != env.message.sender { - return Err(StdError::unauthorized()); - } - - let weight = read_weight(&deps.storage, &asset_token_raw)?; - remove_weight(&mut deps.storage, &asset_token_raw); - decrease_total_weight(&mut deps.storage, NORMAL_TOKEN_WEIGHT)?; - - let mint_contract = deps.api.human_address(&config.mint_contract)?; - let mint_config: (Decimal, Decimal) = - load_mint_asset_config(&deps, &mint_contract, &asset_token_raw)?; - - store_params( - &mut deps.storage, - &Params { - auction_discount: mint_config.0, - min_collateral_ratio: mint_config.1, - weight: Some(weight), - mint_period: None, - min_collateral_ratio_after_ipo: None, - pre_ipo_price: None, - }, - )?; - - Ok(HandleResponse { - messages: vec![ - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: mint_contract, - send: vec![], - msg: to_binary(&MintHandleMsg::RegisterMigration { - asset_token: asset_token.clone(), - end_price, - })?, - }), - CosmosMsg::Wasm(WasmMsg::Instantiate { - code_id: config.token_code_id, - send: vec![], - label: None, - msg: to_binary(&TokenInitMsg { - name, - symbol, - decimals: 6u8, - initial_balances: vec![], - mint: Some(MinterResponse { - minter: deps.api.human_address(&config.mint_contract)?, - cap: None, - }), - init_hook: Some(InitHook { - contract_addr: env.contract.address, - msg: to_binary(&HandleMsg::TokenCreationHook { oracle_feeder })?, - }), - })?, - }), - ], - log: vec![ - log("action", "migration"), - log("end_price", end_price.to_string()), - log("asset_token", asset_token.to_string()), - ], - data: None, - }) -} - -pub fn query( - deps: &Extern, - msg: QueryMsg, -) -> StdResult { - match msg { - QueryMsg::Config {} => to_binary(&query_config(deps)?), - QueryMsg::DistributionInfo {} => to_binary(&query_distribution_info(deps)?), - } -} - -pub fn query_config( - deps: &Extern, -) -> StdResult { - let state = read_config(&deps.storage)?; - let resp = ConfigResponse { - owner: deps.api.human_address(&state.owner)?, - mirror_token: deps.api.human_address(&state.mirror_token)?, - mint_contract: deps.api.human_address(&state.mint_contract)?, - oracle_contract: deps.api.human_address(&state.oracle_contract)?, - terraswap_factory: deps.api.human_address(&state.terraswap_factory)?, - staking_contract: deps.api.human_address(&state.staking_contract)?, - commission_collector: deps.api.human_address(&state.commission_collector)?, - token_code_id: state.token_code_id, - base_denom: state.base_denom, - genesis_time: state.genesis_time, - distribution_schedule: state.distribution_schedule, - }; - - Ok(resp) -} - -pub fn query_distribution_info( - deps: &Extern, -) -> StdResult { - let weights: Vec<(CanonicalAddr, u32)> = read_all_weight(&deps.storage)?; - let last_distributed = read_last_distributed(&deps.storage)?; - let resp = DistributionInfoResponse { - last_distributed, - weights: weights - .iter() - .map(|w| Ok((deps.api.human_address(&w.0)?, w.1))) - .collect::>>()?, - }; - - Ok(resp) -} - -pub fn migrate( - _deps: &mut Extern, - _env: Env, - _msg: MigrateMsg, -) -> MigrateResult { - Ok(MigrateResponse::default()) -} diff --git a/contracts/mirror_factory/src/lib.rs b/contracts/mirror_factory/src/lib.rs deleted file mode 100644 index 6b2695c89..000000000 --- a/contracts/mirror_factory/src/lib.rs +++ /dev/null @@ -1,13 +0,0 @@ -pub mod contract; -pub mod math; -pub mod querier; -pub mod state; - -#[cfg(test)] -mod testing; - -#[cfg(test)] -mod mock_querier; - -#[cfg(target_arch = "wasm32")] -cosmwasm_std::create_entry_points_with_migration!(contract); diff --git a/contracts/mirror_factory/src/math.rs b/contracts/mirror_factory/src/math.rs deleted file mode 100644 index ce7bb0c0e..000000000 --- a/contracts/mirror_factory/src/math.rs +++ /dev/null @@ -1,18 +0,0 @@ -use cosmwasm_std::{Decimal, Uint128}; - -const DECIMAL_FRACTIONAL: Uint128 = Uint128(1_000_000_000u128); - -pub fn reverse_decimal(decimal: Decimal) -> Decimal { - Decimal::from_ratio(DECIMAL_FRACTIONAL, decimal * DECIMAL_FRACTIONAL) -} - -pub fn decimal_multiplication(a: Decimal, b: Decimal) -> Decimal { - Decimal::from_ratio(a * DECIMAL_FRACTIONAL * b, DECIMAL_FRACTIONAL) -} - -pub fn decimal_subtraction(a: Decimal, b: Decimal) -> Decimal { - Decimal::from_ratio( - (DECIMAL_FRACTIONAL * a - DECIMAL_FRACTIONAL * b).unwrap(), - DECIMAL_FRACTIONAL, - ) -} diff --git a/contracts/mirror_factory/src/mock_querier.rs b/contracts/mirror_factory/src/mock_querier.rs deleted file mode 100644 index 19d2039ad..000000000 --- a/contracts/mirror_factory/src/mock_querier.rs +++ /dev/null @@ -1,255 +0,0 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use cosmwasm_std::testing::{MockApi, MockQuerier, MockStorage, MOCK_CONTRACT_ADDR}; -use cosmwasm_std::{ - from_binary, from_slice, to_binary, Api, CanonicalAddr, Coin, Decimal, Empty, Extern, - HumanAddr, Querier, QuerierResult, QueryRequest, SystemError, WasmQuery, -}; -use cosmwasm_storage::to_length_prefixed; - -use crate::querier::MintAssetConfig; -use std::collections::HashMap; -use terraswap::asset::{AssetInfo, PairInfo}; - -/// mock_dependencies is a drop-in replacement for cosmwasm_std::testing::mock_dependencies -/// this uses our CustomQuerier. -pub fn mock_dependencies( - canonical_length: usize, - contract_balance: &[Coin], -) -> Extern { - let contract_addr = HumanAddr::from(MOCK_CONTRACT_ADDR); - let custom_querier: WasmMockQuerier = WasmMockQuerier::new( - MockQuerier::new(&[(&contract_addr, contract_balance)]), - MockApi::new(canonical_length), - canonical_length, - ); - - Extern { - storage: MockStorage::default(), - api: MockApi::new(canonical_length), - querier: custom_querier, - } -} - -pub struct WasmMockQuerier { - base: MockQuerier, - terraswap_factory_querier: TerraswapFactoryQuerier, - oracle_querier: OracleQuerier, - mint_querier: MintQuerier, - canonical_length: usize, -} - -#[derive(Clone, Default)] -pub struct TerraswapFactoryQuerier { - pairs: HashMap, -} - -impl TerraswapFactoryQuerier { - pub fn new(pairs: &[(&String, &HumanAddr)]) -> Self { - TerraswapFactoryQuerier { - pairs: pairs_to_map(pairs), - } - } -} - -pub(crate) fn pairs_to_map(pairs: &[(&String, &HumanAddr)]) -> HashMap { - let mut pairs_map: HashMap = HashMap::new(); - for (key, pair) in pairs.iter() { - pairs_map.insert(key.to_string(), HumanAddr::from(pair)); - } - pairs_map -} - -#[derive(Clone, Default)] -pub struct OracleQuerier { - feeders: HashMap, -} - -impl OracleQuerier { - pub fn new(feeders: &[(&HumanAddr, &HumanAddr)]) -> Self { - OracleQuerier { - feeders: address_pair_to_map(feeders), - } - } -} - -pub(crate) fn address_pair_to_map( - address_pair: &[(&HumanAddr, &HumanAddr)], -) -> HashMap { - let mut address_pair_map: HashMap = HashMap::new(); - for (addr1, addr2) in address_pair.iter() { - address_pair_map.insert(HumanAddr::from(addr1), HumanAddr::from(addr2)); - } - address_pair_map -} - -#[derive(Clone, Default)] -pub struct MintQuerier { - configs: HashMap, -} - -impl MintQuerier { - pub fn new(configs: &[(&HumanAddr, &(Decimal, Decimal))]) -> Self { - MintQuerier { - configs: configs_to_map(configs), - } - } -} - -pub(crate) fn configs_to_map( - configs: &[(&HumanAddr, &(Decimal, Decimal))], -) -> HashMap { - let mut configs_map: HashMap = HashMap::new(); - for (contract_addr, touple) in configs.iter() { - configs_map.insert(HumanAddr::from(contract_addr), (touple.0, touple.1)); - } - configs_map -} - -impl Querier for WasmMockQuerier { - fn raw_query(&self, bin_request: &[u8]) -> QuerierResult { - // MockQuerier doesn't support Custom, so we ignore it completely here - let request: QueryRequest = match from_slice(bin_request) { - Ok(v) => v, - Err(e) => { - return Err(SystemError::InvalidRequest { - error: format!("Parsing query request: {}", e), - request: bin_request.into(), - }) - } - }; - self.handle_query(&request) - } -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum QueryMsg { - Pair { asset_infos: [AssetInfo; 2] }, -} - -impl WasmMockQuerier { - pub fn handle_query(&self, request: &QueryRequest) -> QuerierResult { - match &request { - QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: _, - msg, - }) => match from_binary(&msg).unwrap() { - QueryMsg::Pair { asset_infos } => { - let key = asset_infos[0].to_string() + asset_infos[1].to_string().as_str(); - match self.terraswap_factory_querier.pairs.get(&key) { - Some(v) => Ok(to_binary(&PairInfo { - contract_addr: HumanAddr::from("pair"), - liquidity_token: v.clone(), - asset_infos: [ - AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - ], - })), - None => Err(SystemError::InvalidRequest { - error: "No pair info exists".to_string(), - request: msg.as_slice().into(), - }), - } - } - }, - QueryRequest::Wasm(WasmQuery::Raw { contract_addr, key }) => { - let key: &[u8] = key.as_slice(); - let prefix_asset_config = to_length_prefixed(b"asset_config").to_vec(); - let prefix_feeder = to_length_prefixed(b"feeder").to_vec(); - - let api: MockApi = MockApi::new(self.canonical_length); - if key.len() > prefix_feeder.len() - && key[..prefix_feeder.len()].to_vec() == prefix_feeder - { - let api: MockApi = MockApi::new(self.canonical_length); - let rest_key: &[u8] = &key[prefix_feeder.len()..]; - - if contract_addr == &HumanAddr::from("oracle0000") { - let asset_token: HumanAddr = api - .human_address(&(CanonicalAddr::from(rest_key.to_vec()))) - .unwrap(); - - let feeder = match self.oracle_querier.feeders.get(&asset_token) { - Some(v) => v, - None => { - return Err(SystemError::InvalidRequest { - error: format!( - "Oracle Feeder is not found for {}", - asset_token - ), - request: key.into(), - }) - } - }; - - Ok(to_binary( - &to_binary(&api.canonical_address(&feeder).unwrap()).unwrap(), - )) - } else { - panic!("DO NOT ENTER HERE") - } - } else if key.len() > prefix_asset_config.len() - && key[..prefix_asset_config.len()].to_vec() == prefix_asset_config - { - let rest_key: &[u8] = &key[prefix_asset_config.len()..]; - let asset_token: HumanAddr = api - .human_address(&(CanonicalAddr::from(rest_key.to_vec()))) - .unwrap(); - - let config = match self.mint_querier.configs.get(&asset_token) { - Some(v) => v, - None => { - return Err(SystemError::InvalidRequest { - error: format!("Mint Config is not found for {}", asset_token), - request: key.into(), - }) - } - }; - - Ok(to_binary( - &to_binary(&MintAssetConfig { - token: api.canonical_address(&asset_token).unwrap(), - auction_discount: config.0, - min_collateral_ratio: config.1, - }) - .unwrap(), - )) - } else { - panic!("DO NOT ENTER HERE") - } - } - _ => self.base.handle_query(request), - } - } -} - -impl WasmMockQuerier { - pub fn new(base: MockQuerier, _api: A, canonical_length: usize) -> Self { - WasmMockQuerier { - base, - terraswap_factory_querier: TerraswapFactoryQuerier::default(), - mint_querier: MintQuerier::default(), - oracle_querier: OracleQuerier::default(), - canonical_length, - } - } - - // configure the terraswap pair - pub fn with_terraswap_pairs(&mut self, pairs: &[(&String, &HumanAddr)]) { - self.terraswap_factory_querier = TerraswapFactoryQuerier::new(pairs); - } - - pub fn with_oracle_feeders(&mut self, feeders: &[(&HumanAddr, &HumanAddr)]) { - self.oracle_querier = OracleQuerier::new(feeders); - } - - pub fn with_mint_configs(&mut self, configs: &[(&HumanAddr, &(Decimal, Decimal))]) { - self.mint_querier = MintQuerier::new(configs); - } -} diff --git a/contracts/mirror_factory/src/querier.rs b/contracts/mirror_factory/src/querier.rs deleted file mode 100644 index e26528b30..000000000 --- a/contracts/mirror_factory/src/querier.rs +++ /dev/null @@ -1,90 +0,0 @@ -use cosmwasm_std::{ - from_binary, Api, Binary, CanonicalAddr, Decimal, Extern, HumanAddr, Querier, QueryRequest, - StdError, StdResult, Storage, WasmQuery, -}; - -use cosmwasm_storage::to_length_prefixed; -use serde::{Deserialize, Serialize}; - -pub fn load_oracle_feeder( - deps: &Extern, - contract_addr: &HumanAddr, - asset_token: &CanonicalAddr, -) -> StdResult { - let res: StdResult = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Raw { - contract_addr: HumanAddr::from(contract_addr), - key: Binary::from(concat( - &to_length_prefixed(b"feeder"), - asset_token.as_slice(), - )), - })); - - let res = match res { - Ok(v) => v, - Err(_) => { - return Err(StdError::generic_err("Falied to fetch the oracle feeder")); - } - }; - - let feeder: StdResult = from_binary(&res); - let feeder: CanonicalAddr = match feeder { - Ok(v) => v, - Err(_) => { - return Err(StdError::generic_err("Falied to fetch the oracle feeder")); - } - }; - - Ok(feeder) -} - -#[derive(Serialize, Deserialize)] -pub struct MintAssetConfig { - pub token: CanonicalAddr, - pub auction_discount: Decimal, - pub min_collateral_ratio: Decimal, -} - -pub fn load_mint_asset_config( - deps: &Extern, - contract_addr: &HumanAddr, - asset_token: &CanonicalAddr, -) -> StdResult<(Decimal, Decimal)> { - let res: StdResult = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Raw { - contract_addr: HumanAddr::from(contract_addr), - key: Binary::from(concat( - &to_length_prefixed(b"asset_config"), - asset_token.as_slice(), - )), - })); - - let res = match res { - Ok(v) => v, - Err(_) => { - return Err(StdError::generic_err( - "Falied to fetch the mint asset config", - )); - } - }; - - let asset_config: StdResult = from_binary(&res); - let asset_config: MintAssetConfig = match asset_config { - Ok(v) => v, - Err(_) => { - return Err(StdError::generic_err( - "Falied to fetch the mint asset config", - )); - } - }; - - Ok(( - asset_config.auction_discount, - asset_config.min_collateral_ratio, - )) -} - -#[inline] -fn concat(namespace: &[u8], key: &[u8]) -> Vec { - let mut k = namespace.to_vec(); - k.extend_from_slice(key); - k -} diff --git a/contracts/mirror_factory/src/state.rs b/contracts/mirror_factory/src/state.rs deleted file mode 100644 index d4f273c0f..000000000 --- a/contracts/mirror_factory/src/state.rs +++ /dev/null @@ -1,110 +0,0 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use cosmwasm_std::{CanonicalAddr, Order, StdError, StdResult, Storage, Uint128}; -use cosmwasm_storage::{singleton, singleton_read, Bucket, ReadonlyBucket, Singleton}; - -use mirror_protocol::factory::Params; - -static KEY_CONFIG: &[u8] = b"config"; -static KEY_PARAMS: &[u8] = b"params"; -static KEY_TOTAL_WEIGHT: &[u8] = b"total_weight"; -static KEY_LAST_DISTRIBUTED: &[u8] = b"last_distributed"; - -static PREFIX_WEIGHT: &[u8] = b"weight"; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct Config { - pub owner: CanonicalAddr, - pub mirror_token: CanonicalAddr, - pub mint_contract: CanonicalAddr, - pub oracle_contract: CanonicalAddr, - pub terraswap_factory: CanonicalAddr, - pub staking_contract: CanonicalAddr, - pub commission_collector: CanonicalAddr, - pub token_code_id: u64, // used to create asset token - pub base_denom: String, - pub genesis_time: u64, - pub distribution_schedule: Vec<(u64, u64, Uint128)>, // [[start_time, end_time, distribution_amount], [], ...] -} - -pub fn store_config(storage: &mut S, config: &Config) -> StdResult<()> { - singleton(storage, KEY_CONFIG).save(config) -} - -pub fn read_config(storage: &S) -> StdResult { - singleton_read(storage, KEY_CONFIG).load() -} - -pub fn store_params(storage: &mut S, init_data: &Params) -> StdResult<()> { - singleton(storage, KEY_PARAMS).save(init_data) -} - -pub fn remove_params(storage: &mut S) { - let mut store: Singleton = singleton(storage, KEY_PARAMS); - store.remove() -} - -pub fn read_params(storage: &S) -> StdResult { - singleton_read(storage, KEY_PARAMS).load() -} - -pub fn store_total_weight(storage: &mut S, total_weight: u32) -> StdResult<()> { - singleton(storage, KEY_TOTAL_WEIGHT).save(&total_weight) -} - -pub fn increase_total_weight(storage: &mut S, weight_increase: u32) -> StdResult { - let mut store: Singleton = singleton(storage, KEY_TOTAL_WEIGHT); - store.update(|total_weight| Ok(total_weight + weight_increase)) -} - -pub fn decrease_total_weight(storage: &mut S, weight_decrease: u32) -> StdResult { - let mut store: Singleton = singleton(storage, KEY_TOTAL_WEIGHT); - store.update(|total_weight| Ok(total_weight - weight_decrease)) -} - -pub fn read_total_weight(storage: &S) -> StdResult { - singleton_read(storage, KEY_TOTAL_WEIGHT).load() -} - -pub fn store_last_distributed(storage: &mut S, last_distributed: u64) -> StdResult<()> { - let mut store: Singleton = singleton(storage, KEY_LAST_DISTRIBUTED); - store.save(&last_distributed) -} - -pub fn read_last_distributed(storage: &S) -> StdResult { - singleton_read(storage, KEY_LAST_DISTRIBUTED).load() -} - -pub fn store_weight( - storage: &mut S, - asset_token: &CanonicalAddr, - weight: u32, -) -> StdResult<()> { - let mut weight_bucket: Bucket = Bucket::new(PREFIX_WEIGHT, storage); - weight_bucket.save(asset_token.as_slice(), &weight) -} - -pub fn read_weight(storage: &S, asset_token: &CanonicalAddr) -> StdResult { - let weight_bucket: ReadonlyBucket = ReadonlyBucket::new(PREFIX_WEIGHT, storage); - match weight_bucket.load(asset_token.as_slice()) { - Ok(v) => Ok(v), - _ => Err(StdError::generic_err("No distribution info stored")), - } -} - -pub fn remove_weight(storage: &mut S, asset_token: &CanonicalAddr) { - let mut weight_bucket: Bucket = Bucket::new(PREFIX_WEIGHT, storage); - weight_bucket.remove(asset_token.as_slice()); -} - -pub fn read_all_weight(storage: &S) -> StdResult> { - let weight_bucket: ReadonlyBucket = ReadonlyBucket::new(PREFIX_WEIGHT, storage); - weight_bucket - .range(None, None, Order::Ascending) - .map(|item| { - let (k, v) = item?; - Ok((CanonicalAddr::from(k), v)) - }) - .collect() -} diff --git a/contracts/mirror_factory/src/testing.rs b/contracts/mirror_factory/src/testing.rs deleted file mode 100644 index ad9cfb3a8..000000000 --- a/contracts/mirror_factory/src/testing.rs +++ /dev/null @@ -1,1418 +0,0 @@ -use crate::contract::{handle, init, query}; -use crate::mock_querier::{mock_dependencies, WasmMockQuerier}; - -use crate::state::{read_params, read_total_weight, read_weight, store_total_weight, store_weight}; -use cosmwasm_std::testing::{mock_env, MockApi, MockStorage, MOCK_CONTRACT_ADDR}; -use cosmwasm_std::Api; -use cosmwasm_std::{ - from_binary, log, to_binary, CosmosMsg, Decimal, Env, Extern, HumanAddr, StdError, Uint128, - WasmMsg, -}; -use cw20::{Cw20HandleMsg, MinterResponse}; - -use mirror_protocol::factory::{ - ConfigResponse, DistributionInfoResponse, HandleMsg, InitMsg, Params, QueryMsg, -}; -use mirror_protocol::mint::{HandleMsg as MintHandleMsg, IPOParams}; -use mirror_protocol::oracle::HandleMsg as OracleHandleMsg; -use mirror_protocol::staking::Cw20HookMsg as StakingCw20HookMsg; -use mirror_protocol::staking::HandleMsg as StakingHandleMsg; -use terraswap::asset::AssetInfo; -use terraswap::factory::HandleMsg as TerraswapFactoryHandleMsg; -use terraswap::hook::InitHook; -use terraswap::token::InitMsg as TokenInitMsg; - -fn mock_env_time(signer: &HumanAddr, time: u64) -> Env { - let mut env = mock_env(signer, &[]); - env.block.time = time; - env -} - -static TOKEN_CODE_ID: u64 = 10u64; -static BASE_DENOM: &str = "uusd"; - -#[test] -fn proper_initialization() { - let mut deps = mock_dependencies(20, &[]); - - let msg = InitMsg { - base_denom: BASE_DENOM.to_string(), - token_code_id: TOKEN_CODE_ID, - distribution_schedule: vec![], - }; - - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env.clone(), msg).unwrap(); - - let msg = HandleMsg::PostInitialize { - owner: HumanAddr::from("owner0000"), - mirror_token: HumanAddr::from("mirror0000"), - mint_contract: HumanAddr::from("mint0000"), - staking_contract: HumanAddr::from("staking0000"), - commission_collector: HumanAddr::from("collector0000"), - oracle_contract: HumanAddr::from("oracle0000"), - terraswap_factory: HumanAddr::from("terraswapfactory"), - }; - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - - // cannot update mirror token after initialization - let msg = HandleMsg::PostInitialize { - owner: HumanAddr::from("owner0000"), - mirror_token: HumanAddr::from("mirror0000"), - mint_contract: HumanAddr::from("mint0000"), - staking_contract: HumanAddr::from("staking0000"), - commission_collector: HumanAddr::from("collector0000"), - oracle_contract: HumanAddr::from("oracle0000"), - terraswap_factory: HumanAddr::from("terraswapfactory"), - }; - let _res = handle(&mut deps, env, msg).unwrap_err(); - - // it worked, let's query the state - let res = query(&deps, QueryMsg::Config {}).unwrap(); - let config: ConfigResponse = from_binary(&res).unwrap(); - assert_eq!( - config, - ConfigResponse { - owner: HumanAddr::from("owner0000"), - mirror_token: HumanAddr::from("mirror0000"), - mint_contract: HumanAddr::from("mint0000"), - staking_contract: HumanAddr::from("staking0000"), - commission_collector: HumanAddr::from("collector0000"), - oracle_contract: HumanAddr::from("oracle0000"), - terraswap_factory: HumanAddr::from("terraswapfactory"), - base_denom: BASE_DENOM.to_string(), - token_code_id: TOKEN_CODE_ID, - genesis_time: 1_571_797_419, - distribution_schedule: vec![], - } - ); -} - -#[test] -fn test_update_config() { - let mut deps = mock_dependencies(20, &[]); - - let msg = InitMsg { - base_denom: BASE_DENOM.to_string(), - token_code_id: TOKEN_CODE_ID, - distribution_schedule: vec![], - }; - - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env.clone(), msg).unwrap(); - - let msg = HandleMsg::PostInitialize { - owner: HumanAddr::from("owner0000"), - mirror_token: HumanAddr::from("mirror0000"), - mint_contract: HumanAddr::from("mint0000"), - staking_contract: HumanAddr::from("staking0000"), - commission_collector: HumanAddr::from("collector0000"), - oracle_contract: HumanAddr::from("oracle0000"), - terraswap_factory: HumanAddr::from("terraswapfactory"), - }; - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - - // upate owner - let msg = HandleMsg::UpdateConfig { - owner: Some(HumanAddr::from("owner0001")), - distribution_schedule: None, - token_code_id: None, - }; - - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - let res = query(&deps, QueryMsg::Config {}).unwrap(); - let config: ConfigResponse = from_binary(&res).unwrap(); - assert_eq!( - config, - ConfigResponse { - owner: HumanAddr::from("owner0001"), - mirror_token: HumanAddr::from("mirror0000"), - mint_contract: HumanAddr::from("mint0000"), - staking_contract: HumanAddr::from("staking0000"), - commission_collector: HumanAddr::from("collector0000"), - oracle_contract: HumanAddr::from("oracle0000"), - terraswap_factory: HumanAddr::from("terraswapfactory"), - base_denom: BASE_DENOM.to_string(), - token_code_id: TOKEN_CODE_ID, - genesis_time: 1_571_797_419, - distribution_schedule: vec![], - } - ); - - // update rest part - let msg = HandleMsg::UpdateConfig { - owner: None, - distribution_schedule: Some(vec![(1, 2, Uint128::from(123u128))]), - token_code_id: Some(TOKEN_CODE_ID + 1), - }; - - let env = mock_env("owner0001", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - let res = query(&deps, QueryMsg::Config {}).unwrap(); - let config: ConfigResponse = from_binary(&res).unwrap(); - assert_eq!( - config, - ConfigResponse { - owner: HumanAddr::from("owner0001"), - mirror_token: HumanAddr::from("mirror0000"), - mint_contract: HumanAddr::from("mint0000"), - staking_contract: HumanAddr::from("staking0000"), - commission_collector: HumanAddr::from("collector0000"), - oracle_contract: HumanAddr::from("oracle0000"), - terraswap_factory: HumanAddr::from("terraswapfactory"), - base_denom: BASE_DENOM.to_string(), - token_code_id: TOKEN_CODE_ID + 1, - genesis_time: 1_571_797_419, - distribution_schedule: vec![(1, 2, Uint128::from(123u128))], - } - ); - - // failed unauthoirzed - let msg = HandleMsg::UpdateConfig { - owner: None, - distribution_schedule: None, - token_code_id: Some(TOKEN_CODE_ID + 1), - }; - - let env = mock_env("owner0000", &[]); - let res = handle(&mut deps, env, msg).unwrap_err(); - match res { - StdError::Unauthorized { .. } => {} - _ => panic!("DO NOT ENTER HERE"), - } -} - -#[test] -fn test_update_weight() { - let mut deps = mock_dependencies(20, &[]); - let msg = InitMsg { - base_denom: BASE_DENOM.to_string(), - token_code_id: TOKEN_CODE_ID, - distribution_schedule: vec![], - }; - - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env.clone(), msg).unwrap(); - - let msg = HandleMsg::PostInitialize { - owner: HumanAddr::from("owner0000"), - mirror_token: HumanAddr::from("mirror0000"), - mint_contract: HumanAddr::from("mint0000"), - staking_contract: HumanAddr::from("staking0000"), - commission_collector: HumanAddr::from("collector0000"), - oracle_contract: HumanAddr::from("oracle0000"), - terraswap_factory: HumanAddr::from("terraswapfactory"), - }; - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - - store_total_weight(&mut deps.storage, 100).unwrap(); - store_weight( - &mut deps.storage, - &deps - .api - .canonical_address(&HumanAddr::from("asset0000")) - .unwrap(), - 10, - ) - .unwrap(); - - // incrase weight - let msg = HandleMsg::UpdateWeight { - asset_token: HumanAddr::from("asset0000"), - weight: 20, - }; - let env = mock_env("owner0001", &[]); - let res = handle(&mut deps, env, msg.clone()).unwrap_err(); - match res { - StdError::Unauthorized { .. } => {} - _ => panic!("DO NOT ENTER HERE"), - } - - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - let res = query(&deps, QueryMsg::DistributionInfo {}).unwrap(); - let distribution_info: DistributionInfoResponse = from_binary(&res).unwrap(); - assert_eq!( - distribution_info, - DistributionInfoResponse { - weights: vec![(HumanAddr::from("asset0000"), 20)], - last_distributed: 1_571_797_419, - } - ); - - assert_eq!( - read_weight( - &deps.storage, - &deps - .api - .canonical_address(&HumanAddr::from("asset0000")) - .unwrap() - ) - .unwrap(), - 20u32 - ); - assert_eq!(read_total_weight(&deps.storage).unwrap(), 110u32); -} - -#[test] -fn test_whitelist() { - let mut deps = mock_dependencies(20, &[]); - - let msg = InitMsg { - base_denom: BASE_DENOM.to_string(), - token_code_id: TOKEN_CODE_ID, - distribution_schedule: vec![], - }; - - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env.clone(), msg).unwrap(); - - let msg = HandleMsg::PostInitialize { - owner: HumanAddr::from("owner0000"), - mirror_token: HumanAddr::from("mirror0000"), - mint_contract: HumanAddr::from("mint0000"), - staking_contract: HumanAddr::from("staking0000"), - commission_collector: HumanAddr::from("collector0000"), - oracle_contract: HumanAddr::from("oracle0000"), - terraswap_factory: HumanAddr::from("terraswapfactory"), - }; - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - - let msg = HandleMsg::Whitelist { - name: "apple derivative".to_string(), - symbol: "mAPPL".to_string(), - oracle_feeder: HumanAddr::from("feeder0000"), - params: Params { - auction_discount: Decimal::percent(5), - min_collateral_ratio: Decimal::percent(150), - weight: Some(100u32), - mint_period: None, - min_collateral_ratio_after_ipo: None, - pre_ipo_price: None, - }, - }; - let env = mock_env("owner0000", &[]); - let res = handle(&mut deps, env.clone(), msg.clone()).unwrap(); - - assert_eq!( - res.log, - vec![ - log("action", "whitelist"), - log("symbol", "mAPPL"), - log("name", "apple derivative") - ] - ); - - // token creation msg should be returned - assert_eq!( - res.messages, - vec![CosmosMsg::Wasm(WasmMsg::Instantiate { - code_id: TOKEN_CODE_ID, - send: vec![], - label: None, - msg: to_binary(&TokenInitMsg { - name: "apple derivative".to_string(), - symbol: "mAPPL".to_string(), - decimals: 6u8, - initial_balances: vec![], - mint: Some(MinterResponse { - minter: HumanAddr::from("mint0000"), - cap: None, - }), - init_hook: Some(InitHook { - contract_addr: HumanAddr::from(MOCK_CONTRACT_ADDR), - msg: to_binary(&HandleMsg::TokenCreationHook { - oracle_feeder: HumanAddr::from("feeder0000") - }) - .unwrap(), - }), - }) - .unwrap(), - })] - ); - - let params: Params = read_params(&deps.storage).unwrap(); - assert_eq!( - params, - Params { - auction_discount: Decimal::percent(5), - min_collateral_ratio: Decimal::percent(150), - weight: Some(100u32), - mint_period: None, - min_collateral_ratio_after_ipo: None, - pre_ipo_price: None, - } - ); - - let res = handle(&mut deps, env.clone(), msg.clone()).unwrap_err(); - match res { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "A whitelist process is in progress"), - _ => panic!("DO NOT ENTER HERE"), - } - - let env = mock_env("addr0001", &[]); - let res = handle(&mut deps, env, msg).unwrap_err(); - match res { - StdError::Unauthorized { .. } => {} - _ => panic!("DO NOT ENTER HERE"), - } -} - -#[test] -fn test_token_creation_hook() { - let mut deps = mock_dependencies(20, &[]); - - let msg = InitMsg { - base_denom: BASE_DENOM.to_string(), - token_code_id: TOKEN_CODE_ID, - distribution_schedule: vec![], - }; - - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env.clone(), msg).unwrap(); - - let msg = HandleMsg::PostInitialize { - owner: HumanAddr::from("owner0000"), - mirror_token: HumanAddr::from("mirror0000"), - mint_contract: HumanAddr::from("mint0000"), - staking_contract: HumanAddr::from("staking0000"), - commission_collector: HumanAddr::from("collector0000"), - oracle_contract: HumanAddr::from("oracle0000"), - terraswap_factory: HumanAddr::from("terraswapfactory"), - }; - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - - // There is no whitelist process; failed - let msg = HandleMsg::TokenCreationHook { - oracle_feeder: HumanAddr::from("feeder0000"), - }; - let env = mock_env("asset0000", &[]); - let res = handle(&mut deps, env.clone(), msg.clone()); - match res { - Err(StdError::GenericErr { msg, .. }) => { - assert_eq!(msg, "No whitelist process in progress") - } - _ => panic!("DO NOT ENTER HERE"), - } - - let msg = HandleMsg::Whitelist { - name: "apple derivative".to_string(), - symbol: "mAPPL".to_string(), - oracle_feeder: HumanAddr::from("feeder0000"), - params: Params { - auction_discount: Decimal::percent(5), - min_collateral_ratio: Decimal::percent(150), - weight: Some(100u32), - mint_period: None, - min_collateral_ratio_after_ipo: None, - pre_ipo_price: None, - }, - }; - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env.clone(), msg.clone()).unwrap(); - - let msg = HandleMsg::TokenCreationHook { - oracle_feeder: HumanAddr::from("feeder0000"), - }; - let env = mock_env("asset0000", &[]); - let res = handle(&mut deps, env.clone(), msg.clone()).unwrap(); - assert_eq!( - res.messages, - vec![ - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("mint0000"), - send: vec![], - msg: to_binary(&MintHandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset0000"), - auction_discount: Decimal::percent(5), - min_collateral_ratio: Decimal::percent(150), - ipo_params: None, - }) - .unwrap(), - }), - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("oracle0000"), - send: vec![], - msg: to_binary(&OracleHandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset0000"), - feeder: HumanAddr::from("feeder0000"), - }) - .unwrap(), - }), - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("terraswapfactory"), - send: vec![], - msg: to_binary(&TerraswapFactoryHandleMsg::CreatePair { - asset_infos: [ - AssetInfo::NativeToken { - denom: BASE_DENOM.to_string(), - }, - AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - ], - init_hook: Some(InitHook { - msg: to_binary(&HandleMsg::TerraswapCreationHook { - asset_token: HumanAddr::from("asset0000"), - }) - .unwrap(), - contract_addr: HumanAddr::from(MOCK_CONTRACT_ADDR), - }), - }) - .unwrap(), - }) - ] - ); - assert_eq!( - res.log, - vec![ - log("asset_token_addr", "asset0000"), - log("is_pre_ipo", false), - ] - ); - - let res = query(&deps, QueryMsg::DistributionInfo {}).unwrap(); - let distribution_info: DistributionInfoResponse = from_binary(&res).unwrap(); - assert_eq!( - distribution_info, - DistributionInfoResponse { - weights: vec![(HumanAddr::from("asset0000"), 100)], - last_distributed: 1_571_797_419, - } - ); - - // There is no whitelist process; failed - let msg = HandleMsg::TokenCreationHook { - oracle_feeder: HumanAddr::from("feeder0000"), - }; - let env = mock_env("asset0000", &[]); - let res = handle(&mut deps, env.clone(), msg.clone()); - match res { - Err(StdError::GenericErr { msg, .. }) => { - assert_eq!(msg, "No whitelist process in progress") - } - _ => panic!("DO NOT ENTER HERE"), - } -} - -#[test] -fn test_token_creation_hook_without_weight() { - let mut deps = mock_dependencies(20, &[]); - - let msg = InitMsg { - base_denom: BASE_DENOM.to_string(), - token_code_id: TOKEN_CODE_ID, - distribution_schedule: vec![], - }; - - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env.clone(), msg).unwrap(); - - let msg = HandleMsg::PostInitialize { - owner: HumanAddr::from("owner0000"), - mirror_token: HumanAddr::from("mirror0000"), - mint_contract: HumanAddr::from("mint0000"), - staking_contract: HumanAddr::from("staking0000"), - commission_collector: HumanAddr::from("collector0000"), - oracle_contract: HumanAddr::from("oracle0000"), - terraswap_factory: HumanAddr::from("terraswapfactory"), - }; - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - - // There is no whitelist process; failed - let msg = HandleMsg::TokenCreationHook { - oracle_feeder: HumanAddr::from("feeder0000"), - }; - let env = mock_env("asset0000", &[]); - let res = handle(&mut deps, env.clone(), msg.clone()); - match res { - Err(StdError::GenericErr { msg, .. }) => { - assert_eq!(msg, "No whitelist process in progress") - } - _ => panic!("DO NOT ENTER HERE"), - } - - let msg = HandleMsg::Whitelist { - name: "apple derivative".to_string(), - symbol: "mAPPL".to_string(), - oracle_feeder: HumanAddr::from("feeder0000"), - params: Params { - auction_discount: Decimal::percent(5), - min_collateral_ratio: Decimal::percent(150), - weight: None, - mint_period: None, - min_collateral_ratio_after_ipo: None, - pre_ipo_price: None, - }, - }; - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env.clone(), msg.clone()).unwrap(); - - let msg = HandleMsg::TokenCreationHook { - oracle_feeder: HumanAddr::from("feeder0000"), - }; - let env = mock_env("asset0000", &[]); - let res = handle(&mut deps, env.clone(), msg.clone()).unwrap(); - assert_eq!( - res.messages, - vec![ - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("mint0000"), - send: vec![], - msg: to_binary(&MintHandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset0000"), - auction_discount: Decimal::percent(5), - min_collateral_ratio: Decimal::percent(150), - ipo_params: None, - }) - .unwrap(), - }), - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("oracle0000"), - send: vec![], - msg: to_binary(&OracleHandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset0000"), - feeder: HumanAddr::from("feeder0000"), - }) - .unwrap(), - }), - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("terraswapfactory"), - send: vec![], - msg: to_binary(&TerraswapFactoryHandleMsg::CreatePair { - asset_infos: [ - AssetInfo::NativeToken { - denom: BASE_DENOM.to_string(), - }, - AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - ], - init_hook: Some(InitHook { - msg: to_binary(&HandleMsg::TerraswapCreationHook { - asset_token: HumanAddr::from("asset0000"), - }) - .unwrap(), - contract_addr: HumanAddr::from(MOCK_CONTRACT_ADDR), - }), - }) - .unwrap(), - }) - ] - ); - - let res = query(&deps, QueryMsg::DistributionInfo {}).unwrap(); - let distribution_info: DistributionInfoResponse = from_binary(&res).unwrap(); - assert_eq!( - distribution_info, - DistributionInfoResponse { - weights: vec![(HumanAddr::from("asset0000"), 30)], - last_distributed: 1_571_797_419, - } - ); - - // There is no whitelist process; failed - let msg = HandleMsg::TokenCreationHook { - oracle_feeder: HumanAddr::from("feeder0000"), - }; - let env = mock_env("asset0000", &[]); - let res = handle(&mut deps, env.clone(), msg.clone()); - match res { - Err(StdError::GenericErr { msg, .. }) => { - assert_eq!(msg, "No whitelist process in progress") - } - _ => panic!("DO NOT ENTER HERE"), - } -} - -#[test] -fn test_terraswap_creation_hook() { - let mut deps = mock_dependencies(20, &[]); - deps.querier - .with_terraswap_pairs(&[(&"uusdasset0000".to_string(), &HumanAddr::from("LP0000"))]); - - let msg = InitMsg { - base_denom: BASE_DENOM.to_string(), - token_code_id: TOKEN_CODE_ID, - distribution_schedule: vec![], - }; - - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env.clone(), msg).unwrap(); - - let msg = HandleMsg::PostInitialize { - owner: HumanAddr::from("owner0000"), - mirror_token: HumanAddr::from("mirror0000"), - mint_contract: HumanAddr::from("mint0000"), - staking_contract: HumanAddr::from("staking0000"), - commission_collector: HumanAddr::from("collector0000"), - oracle_contract: HumanAddr::from("oracle0000"), - terraswap_factory: HumanAddr::from("terraswapfactory"), - }; - let _res = handle(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::Whitelist { - name: "apple derivative".to_string(), - symbol: "mAPPL".to_string(), - oracle_feeder: HumanAddr::from("feeder0000"), - params: Params { - auction_discount: Decimal::percent(5), - min_collateral_ratio: Decimal::percent(150), - weight: Some(100u32), - mint_period: None, - min_collateral_ratio_after_ipo: None, - pre_ipo_price: None, - }, - }; - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::TokenCreationHook { - oracle_feeder: HumanAddr::from("feeder0000"), - }; - let env = mock_env("asset0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::TerraswapCreationHook { - asset_token: HumanAddr::from("asset0000"), - }; - let env = mock_env("terraswapfactory", &[]); - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!( - res.messages, - vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("staking0000"), - send: vec![], - msg: to_binary(&StakingHandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset0000"), - staking_token: HumanAddr::from("LP0000"), - }) - .unwrap(), - })] - ); -} - -#[test] -fn test_distribute() { - let mut deps = mock_dependencies(20, &[]); - deps.querier.with_terraswap_pairs(&[ - (&"uusdasset0000".to_string(), &HumanAddr::from("LP0000")), - (&"uusdasset0001".to_string(), &HumanAddr::from("LP0001")), - ]); - - let msg = InitMsg { - base_denom: BASE_DENOM.to_string(), - token_code_id: TOKEN_CODE_ID, - distribution_schedule: vec![ - (1800, 3600, Uint128::from(3600u128)), - (3600, 3600 + 3600, Uint128::from(7200u128)), - ], - }; - - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env.clone(), msg).unwrap(); - - let msg = HandleMsg::PostInitialize { - owner: HumanAddr::from("owner0000"), - mirror_token: HumanAddr::from("mirror0000"), - mint_contract: HumanAddr::from("mint0000"), - staking_contract: HumanAddr::from("staking0000"), - commission_collector: HumanAddr::from("collector0000"), - oracle_contract: HumanAddr::from("oracle0000"), - terraswap_factory: HumanAddr::from("terraswapfactory"), - }; - let _res = handle(&mut deps, env, msg).unwrap(); - - // whitelist first item with weight 1.5 - let msg = HandleMsg::Whitelist { - name: "apple derivative".to_string(), - symbol: "mAPPL".to_string(), - oracle_feeder: HumanAddr::from("feeder0000"), - params: Params { - auction_discount: Decimal::percent(5), - min_collateral_ratio: Decimal::percent(150), - weight: Some(100u32), - mint_period: None, - min_collateral_ratio_after_ipo: None, - pre_ipo_price: None, - }, - }; - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::TokenCreationHook { - oracle_feeder: HumanAddr::from("feeder0000"), - }; - let env = mock_env("asset0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::TerraswapCreationHook { - asset_token: HumanAddr::from("asset0000"), - }; - let env = mock_env("terraswapfactory", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // Whitelist second item with weight 1 - let msg = HandleMsg::Whitelist { - name: "google derivative".to_string(), - symbol: "mGOGL".to_string(), - oracle_feeder: HumanAddr::from("feeder0000"), - params: Params { - auction_discount: Decimal::percent(5), - min_collateral_ratio: Decimal::percent(150), - weight: Some(100u32), - mint_period: None, - min_collateral_ratio_after_ipo: None, - pre_ipo_price: None, - }, - }; - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::TokenCreationHook { - oracle_feeder: HumanAddr::from("feeder0000"), - }; - let env = mock_env("asset0001", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::TerraswapCreationHook { - asset_token: HumanAddr::from("asset0001"), - }; - let env = mock_env("terraswapfactory", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // height is not increased so zero amount will be minted - let msg = HandleMsg::Distribute {}; - let env = mock_env("anyone", &[]); - let res = handle(&mut deps, env, msg); - match res { - Err(StdError::GenericErr { msg, .. }) => { - assert_eq!(msg, "Cannot distribute mirror token before interval") - } - _ => panic!("DO NOT ENTER HERE"), - } - - // one height increase - let msg = HandleMsg::Distribute {}; - let env = mock_env_time(&HumanAddr::from("addr0000"), 1_571_797_419u64 + 5400u64); - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!( - res.log, - vec![ - log("action", "distribute"), - log("distribution_amount", "7200"), - ] - ); - - assert_eq!( - res.messages, - vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("mirror0000"), - msg: to_binary(&Cw20HandleMsg::Send { - contract: HumanAddr::from("staking0000"), - amount: Uint128(7200u128), - msg: Some( - to_binary(&StakingCw20HookMsg::DepositReward { - rewards: vec![ - (HumanAddr::from("asset0000"), Uint128(3600u128)), - (HumanAddr::from("asset0001"), Uint128(3600u128)), - ], - }) - .unwrap() - ), - }) - .unwrap(), - send: vec![], - }),], - ); - - let res = query(&deps, QueryMsg::DistributionInfo {}).unwrap(); - let distribution_info: DistributionInfoResponse = from_binary(&res).unwrap(); - assert_eq!( - distribution_info, - DistributionInfoResponse { - weights: vec![ - (HumanAddr::from("asset0000"), 100), - (HumanAddr::from("asset0001"), 100) - ], - last_distributed: 1_571_802_819, - } - ); -} - -fn whitelist_token( - deps: &mut Extern, - name: &str, - symbol: &str, - asset_token: &str, - weight: u32, -) { - // whitelist first item with weight 1.5 - let msg = HandleMsg::Whitelist { - name: name.to_string(), - symbol: symbol.to_string(), - oracle_feeder: HumanAddr::from("feeder0000"), - params: Params { - auction_discount: Decimal::percent(5), - min_collateral_ratio: Decimal::percent(150), - weight: Some(weight), - mint_period: None, - min_collateral_ratio_after_ipo: None, - pre_ipo_price: None, - }, - }; - let env = mock_env("owner0000", &[]); - let _res = handle(deps, env, msg).unwrap(); - - let msg = HandleMsg::TokenCreationHook { - oracle_feeder: HumanAddr::from("feeder0000"), - }; - let env = mock_env(asset_token, &[]); - let _res = handle(deps, env, msg).unwrap(); - - let msg = HandleMsg::TerraswapCreationHook { - asset_token: HumanAddr::from(asset_token), - }; - let env = mock_env("terraswapfactory", &[]); - let _res = handle(deps, env, msg).unwrap(); -} -#[test] -fn test_distribute_split() { - let mut deps = mock_dependencies(20, &[]); - deps.querier.with_terraswap_pairs(&[ - (&"uusdasset0000".to_string(), &HumanAddr::from("LP0000")), - (&"uusdasset0001".to_string(), &HumanAddr::from("LP0001")), - (&"uusdasset0002".to_string(), &HumanAddr::from("LP0002")), - (&"uusdasset0003".to_string(), &HumanAddr::from("LP0003")), - (&"uusdasset0004".to_string(), &HumanAddr::from("LP0004")), - (&"uusdasset0005".to_string(), &HumanAddr::from("LP0005")), - (&"uusdasset0006".to_string(), &HumanAddr::from("LP0006")), - (&"uusdasset0007".to_string(), &HumanAddr::from("LP0007")), - (&"uusdasset0008".to_string(), &HumanAddr::from("LP0008")), - (&"uusdasset0009".to_string(), &HumanAddr::from("LP0009")), - (&"uusdasset0010".to_string(), &HumanAddr::from("LP0010")), - (&"uusdasset0011".to_string(), &HumanAddr::from("LP0011")), - ]); - - let msg = InitMsg { - base_denom: BASE_DENOM.to_string(), - token_code_id: TOKEN_CODE_ID, - distribution_schedule: vec![ - (1800, 3600, Uint128::from(3600u128)), - (3600, 3600 + 3600, Uint128::from(7200u128)), - ], - }; - - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env.clone(), msg).unwrap(); - - let msg = HandleMsg::PostInitialize { - owner: HumanAddr::from("owner0000"), - mirror_token: HumanAddr::from("mirror0000"), - mint_contract: HumanAddr::from("mint0000"), - staking_contract: HumanAddr::from("staking0000"), - commission_collector: HumanAddr::from("collector0000"), - oracle_contract: HumanAddr::from("oracle0000"), - terraswap_factory: HumanAddr::from("terraswapfactory"), - }; - let _res = handle(&mut deps, env, msg).unwrap(); - - // whitelist first item with weight 1.5 - whitelist_token(&mut deps, "asset 0", "a0", "asset0000", 100u32); - whitelist_token(&mut deps, "asset 1", "a1", "asset0001", 100u32); - whitelist_token(&mut deps, "asset 2", "a2", "asset0002", 100u32); - whitelist_token(&mut deps, "asset 3", "a3", "asset0003", 100u32); - whitelist_token(&mut deps, "asset 4", "a4", "asset0004", 100u32); - whitelist_token(&mut deps, "asset 5", "a5", "asset0005", 100u32); - whitelist_token(&mut deps, "asset 6", "a6", "asset0006", 100u32); - whitelist_token(&mut deps, "asset 7", "a7", "asset0007", 100u32); - whitelist_token(&mut deps, "asset 8", "a8", "asset0008", 100u32); - whitelist_token(&mut deps, "asset 9", "a9", "asset0009", 100u32); - whitelist_token(&mut deps, "asset 10", "a10", "asset0010", 100u32); - whitelist_token(&mut deps, "asset 11", "a11", "asset0011", 100u32); - - // one height increase - let msg = HandleMsg::Distribute {}; - let env = mock_env_time(&HumanAddr::from("addr0000"), 1_571_797_419u64 + 5400u64); - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!( - res.log, - vec![ - log("action", "distribute"), - log("distribution_amount", "7200"), - ] - ); - - assert_eq!( - res.messages[0], - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("mirror0000"), - msg: to_binary(&Cw20HandleMsg::Send { - contract: HumanAddr::from("staking0000"), - amount: Uint128(6000u128), - msg: Some( - to_binary(&StakingCw20HookMsg::DepositReward { - rewards: vec![ - (HumanAddr::from("asset0000"), Uint128(600u128)), - (HumanAddr::from("asset0001"), Uint128(600u128)), - (HumanAddr::from("asset0002"), Uint128(600u128)), - (HumanAddr::from("asset0003"), Uint128(600u128)), - (HumanAddr::from("asset0004"), Uint128(600u128)), - (HumanAddr::from("asset0005"), Uint128(600u128)), - (HumanAddr::from("asset0006"), Uint128(600u128)), - (HumanAddr::from("asset0007"), Uint128(600u128)), - (HumanAddr::from("asset0008"), Uint128(600u128)), - (HumanAddr::from("asset0009"), Uint128(600u128)), - ], - }) - .unwrap() - ), - }) - .unwrap(), - send: vec![], - }), - ); - - assert_eq!( - res.messages[1], - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("mirror0000"), - msg: to_binary(&Cw20HandleMsg::Send { - contract: HumanAddr::from("staking0000"), - amount: Uint128(1200u128), - msg: Some( - to_binary(&StakingCw20HookMsg::DepositReward { - rewards: vec![ - (HumanAddr::from("asset0010"), Uint128(600u128)), - (HumanAddr::from("asset0011"), Uint128(600u128)), - ], - }) - .unwrap() - ), - }) - .unwrap(), - send: vec![], - }), - ); -} - -#[test] -fn test_revocation() { - let mut deps = mock_dependencies(20, &[]); - deps.querier.with_terraswap_pairs(&[ - (&"uusdasset0000".to_string(), &HumanAddr::from("LP0000")), - (&"uusdasset0001".to_string(), &HumanAddr::from("LP0001")), - ]); - - let msg = InitMsg { - base_denom: BASE_DENOM.to_string(), - token_code_id: TOKEN_CODE_ID, - distribution_schedule: vec![], - }; - - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env.clone(), msg).unwrap(); - - let msg = HandleMsg::PostInitialize { - owner: HumanAddr::from("owner0000"), - mirror_token: HumanAddr::from("mirror0000"), - mint_contract: HumanAddr::from("mint0000"), - staking_contract: HumanAddr::from("staking0000"), - commission_collector: HumanAddr::from("collector0000"), - oracle_contract: HumanAddr::from("oracle0000"), - terraswap_factory: HumanAddr::from("terraswapfactory"), - }; - let _res = handle(&mut deps, env, msg).unwrap(); - - // whitelist item 1 - let msg = HandleMsg::Whitelist { - name: "tesla derivative".to_string(), - symbol: "mTSLA".to_string(), - oracle_feeder: HumanAddr::from("feeder0000"), - params: Params { - auction_discount: Decimal::percent(5), - min_collateral_ratio: Decimal::percent(150), - weight: Some(100u32), - mint_period: None, - min_collateral_ratio_after_ipo: None, - pre_ipo_price: None, - }, - }; - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::TokenCreationHook { - oracle_feeder: HumanAddr::from("feeder0000"), - }; - let env = mock_env("asset0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::TerraswapCreationHook { - asset_token: HumanAddr::from("asset0000"), - }; - let env = mock_env("terraswapfactory", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // whitelist item 2 - let msg = HandleMsg::Whitelist { - name: "apple derivative".to_string(), - symbol: "mAPPL".to_string(), - oracle_feeder: HumanAddr::from("feeder0000"), - params: Params { - auction_discount: Decimal::percent(5), - min_collateral_ratio: Decimal::percent(150), - weight: Some(100u32), - mint_period: None, - min_collateral_ratio_after_ipo: None, - pre_ipo_price: None, - }, - }; - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::TokenCreationHook { - oracle_feeder: HumanAddr::from("feeder0000"), - }; - let env = mock_env("asset0001", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::TerraswapCreationHook { - asset_token: HumanAddr::from("asset0001"), - }; - let env = mock_env("terraswapfactory", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - // register queriers - deps.querier.with_oracle_feeders(&[ - ( - &HumanAddr::from("asset0000"), - &HumanAddr::from("feeder0000"), - ), - ( - &HumanAddr::from("asset0001"), - &HumanAddr::from("feeder0000"), - ), - ]); - - // unauthorized revoke attempt - let msg = HandleMsg::RevokeAsset { - asset_token: HumanAddr::from("asset0000"), - end_price: Decimal::from_ratio(2u128, 1u128), - }; - let env = mock_env("address0000", &[]); - let res = handle(&mut deps, env, msg.clone()).unwrap_err(); - - match res { - StdError::Unauthorized { .. } => {} - _ => panic!("DO NOT ENTER HERE"), - } - - // SUCCESS - the feeder revokes item 1 - let env = mock_env("feeder0000", &[]); - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!( - res.messages, - vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("mint0000"), - send: vec![], - msg: to_binary(&MintHandleMsg::RegisterMigration { - asset_token: HumanAddr::from("asset0000"), - end_price: Decimal::from_ratio(2u128, 1u128), - }) - .unwrap(), - }),] - ); - - let msg = HandleMsg::RevokeAsset { - asset_token: HumanAddr::from("asset0001"), - end_price: Decimal::from_ratio(2u128, 1u128), - }; - // SUCCESS - the owner revokes item 2 - let env = mock_env("owner0000", &[]); - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!( - res.messages, - vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("mint0000"), - send: vec![], - msg: to_binary(&MintHandleMsg::RegisterMigration { - asset_token: HumanAddr::from("asset0001"), - end_price: Decimal::from_ratio(2u128, 1u128), - }) - .unwrap(), - }),] - ); -} - -#[test] -fn test_migration() { - let mut deps = mock_dependencies(20, &[]); - deps.querier - .with_terraswap_pairs(&[(&"uusdasset0000".to_string(), &HumanAddr::from("LP0000"))]); - - let msg = InitMsg { - base_denom: BASE_DENOM.to_string(), - token_code_id: TOKEN_CODE_ID, - distribution_schedule: vec![], - }; - - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env.clone(), msg).unwrap(); - - let msg = HandleMsg::PostInitialize { - owner: HumanAddr::from("owner0000"), - mirror_token: HumanAddr::from("mirror0000"), - mint_contract: HumanAddr::from("mint0000"), - staking_contract: HumanAddr::from("staking0000"), - commission_collector: HumanAddr::from("collector0000"), - oracle_contract: HumanAddr::from("oracle0000"), - terraswap_factory: HumanAddr::from("terraswapfactory"), - }; - let _res = handle(&mut deps, env, msg).unwrap(); - - // whitelist first item with weight 1.5 - let msg = HandleMsg::Whitelist { - name: "apple derivative".to_string(), - symbol: "mAPPL".to_string(), - oracle_feeder: HumanAddr::from("feeder0000"), - params: Params { - auction_discount: Decimal::percent(5), - min_collateral_ratio: Decimal::percent(150), - weight: Some(100u32), - mint_period: None, - min_collateral_ratio_after_ipo: None, - pre_ipo_price: None, - }, - }; - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::TokenCreationHook { - oracle_feeder: HumanAddr::from("feeder0000"), - }; - let env = mock_env("asset0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::TerraswapCreationHook { - asset_token: HumanAddr::from("asset0000"), - }; - let env = mock_env("terraswapfactory", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // register queriers - deps.querier.with_mint_configs(&[( - &HumanAddr::from("asset0000"), - &(Decimal::percent(1), Decimal::percent(1)), - )]); - deps.querier.with_oracle_feeders(&[( - &HumanAddr::from("asset0000"), - &HumanAddr::from("feeder0000"), - )]); - - // unauthorized migrate attempt - let msg = HandleMsg::MigrateAsset { - name: "apple migration".to_string(), - symbol: "mAPPL2".to_string(), - from_token: HumanAddr::from("asset0000"), - end_price: Decimal::from_ratio(2u128, 1u128), - }; - let env = mock_env("owner0000", &[]); - let res = handle(&mut deps, env, msg.clone()).unwrap_err(); - - match res { - StdError::Unauthorized { .. } => {} - _ => panic!("DO NOT ENTER HERE"), - } - - let env = mock_env("feeder0000", &[]); - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!( - res.messages, - vec![ - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("mint0000"), - send: vec![], - msg: to_binary(&MintHandleMsg::RegisterMigration { - asset_token: HumanAddr::from("asset0000"), - end_price: Decimal::from_ratio(2u128, 1u128), - }) - .unwrap(), - }), - CosmosMsg::Wasm(WasmMsg::Instantiate { - code_id: TOKEN_CODE_ID, - send: vec![], - label: None, - msg: to_binary(&TokenInitMsg { - name: "apple migration".to_string(), - symbol: "mAPPL2".to_string(), - decimals: 6u8, - initial_balances: vec![], - mint: Some(MinterResponse { - minter: HumanAddr::from("mint0000"), - cap: None, - }), - init_hook: Some(InitHook { - contract_addr: HumanAddr::from(MOCK_CONTRACT_ADDR), - msg: to_binary(&HandleMsg::TokenCreationHook { - oracle_feeder: HumanAddr::from("feeder0000") - }) - .unwrap(), - }), - }) - .unwrap(), - }) - ] - ); -} - -#[test] -fn test_whitelist_pre_ipo_asset() { - let mut deps = mock_dependencies(20, &[]); - - let msg = InitMsg { - base_denom: BASE_DENOM.to_string(), - token_code_id: TOKEN_CODE_ID, - distribution_schedule: vec![], - }; - - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env.clone(), msg).unwrap(); - - let msg = HandleMsg::PostInitialize { - owner: HumanAddr::from("owner0000"), - mirror_token: HumanAddr::from("mirror0000"), - mint_contract: HumanAddr::from("mint0000"), - staking_contract: HumanAddr::from("staking0000"), - commission_collector: HumanAddr::from("collector0000"), - oracle_contract: HumanAddr::from("oracle0000"), - terraswap_factory: HumanAddr::from("terraswapfactory"), - }; - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - - let msg = HandleMsg::Whitelist { - name: "pre-IPO asset".to_string(), - symbol: "mPreIPO".to_string(), - oracle_feeder: HumanAddr::from("feeder0000"), - params: Params { - auction_discount: Decimal::percent(5), - min_collateral_ratio: Decimal::percent(1000), - weight: Some(100u32), - mint_period: Some(10000u64), - min_collateral_ratio_after_ipo: Some(Decimal::percent(150)), - pre_ipo_price: Some(Decimal::percent(1)), - }, - }; - let env = mock_env("owner0000", &[]); - let res = handle(&mut deps, env.clone(), msg.clone()).unwrap(); - - // token creation msg should be returned - assert_eq!( - res.messages, - vec![CosmosMsg::Wasm(WasmMsg::Instantiate { - code_id: TOKEN_CODE_ID, - send: vec![], - label: None, - msg: to_binary(&TokenInitMsg { - name: "pre-IPO asset".to_string(), - symbol: "mPreIPO".to_string(), - decimals: 6u8, - initial_balances: vec![], - mint: Some(MinterResponse { - minter: HumanAddr::from("mint0000"), - cap: None, - }), - init_hook: Some(InitHook { - contract_addr: HumanAddr::from(MOCK_CONTRACT_ADDR), - msg: to_binary(&HandleMsg::TokenCreationHook { - oracle_feeder: HumanAddr::from("feeder0000") - }) - .unwrap(), - }), - }) - .unwrap(), - })] - ); - - let params: Params = read_params(&deps.storage).unwrap(); - assert_eq!( - params, - Params { - auction_discount: Decimal::percent(5), - min_collateral_ratio: Decimal::percent(1000), - weight: Some(100u32), - mint_period: Some(10000u64), - min_collateral_ratio_after_ipo: Some(Decimal::percent(150)), - pre_ipo_price: Some(Decimal::percent(1)), - } - ); - - // execute token creation hook - let msg = HandleMsg::TokenCreationHook { - oracle_feeder: HumanAddr::from("feeder0000"), - }; - - let env = mock_env("asset0000", &[]); - let res = handle(&mut deps, env.clone(), msg.clone()).unwrap(); - assert_eq!( - res.messages, - vec![ - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("mint0000"), - send: vec![], - msg: to_binary(&MintHandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset0000"), - auction_discount: Decimal::percent(5), - min_collateral_ratio: Decimal::percent(1000), - ipo_params: Some(IPOParams { - mint_end: env.block.time + 10000u64, - min_collateral_ratio_after_ipo: Decimal::percent(150), - pre_ipo_price: Decimal::percent(1), - }), - }) - .unwrap(), - }), - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("oracle0000"), - send: vec![], - msg: to_binary(&OracleHandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset0000"), - feeder: HumanAddr::from("feeder0000"), - }) - .unwrap(), - }), - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("terraswapfactory"), - send: vec![], - msg: to_binary(&TerraswapFactoryHandleMsg::CreatePair { - asset_infos: [ - AssetInfo::NativeToken { - denom: BASE_DENOM.to_string(), - }, - AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - ], - init_hook: Some(InitHook { - msg: to_binary(&HandleMsg::TerraswapCreationHook { - asset_token: HumanAddr::from("asset0000"), - }) - .unwrap(), - contract_addr: HumanAddr::from(MOCK_CONTRACT_ADDR), - }), - }) - .unwrap(), - }) - ] - ); - - assert_eq!( - res.log, - vec![ - log("asset_token_addr", "asset0000"), - log("is_pre_ipo", true), - log("mint_end", env.block.time + 10000u64), - log("min_collateral_ratio_after_ipo", "1.5"), - log("pre_ipo_price", "0.01"), - ] - ); -} diff --git a/contracts/mirror_factory/tests/integration.rs b/contracts/mirror_factory/tests/integration.rs deleted file mode 100644 index 23b0d6a8b..000000000 --- a/contracts/mirror_factory/tests/integration.rs +++ /dev/null @@ -1,203 +0,0 @@ -//! This integration test tries to run and call the generated wasm. -//! It depends on a Wasm build being available, which you can create with `cargo wasm`. -//! Then running `cargo integration-test` will validate we can properly call into that generated Wasm. -//! -//! You can easily convert unit tests to integration tests as follows: -//! 1. Copy them over verbatim -//! 2. Then change -//! let mut deps = mock_dependencies(20, &[]); -//! to -//! let mut deps = mock_instance(WASM, &[]); -//! 3. If you access raw storage, where ever you see something like: -//! deps.storage.get(CONFIG_KEY).expect("no data stored"); -//! replace it with: -//! deps.with_storage(|store| { -//! let data = store.get(CONFIG_KEY).expect("no data stored"); -//! //... -//! }); -//! 4. Anywhere you see query(&deps, ...) you must replace it with query(&mut deps, ...) -use cosmwasm_std::{ - from_binary, Coin, HandleResponse, HandleResult, HumanAddr, InitResponse, StdError, Uint128, -}; -use cosmwasm_vm::testing::{ - handle, init, mock_dependencies, mock_env, query, MockApi, MockQuerier, MockStorage, -}; -use cosmwasm_vm::Instance; -use mirror_protocol::factory::{ConfigResponse, HandleMsg, InitMsg, QueryMsg}; - -// This line will test the output of cargo wasm -static WASM: &[u8] = - include_bytes!("../../../target/wasm32-unknown-unknown/release/mirror_factory.wasm"); -// You can uncomment this line instead to test productionified build from rust-optimizer -// static WASM: &[u8] = include_bytes!("../contract.wasm"); - -const DEFAULT_GAS_LIMIT: u64 = 500_000; - -pub fn mock_instance( - wasm: &[u8], - contract_balance: &[Coin], -) -> Instance { - // TODO: check_wasm is not exported from cosmwasm_vm - // let terra_features = features_from_csv("staking,terra"); - // check_wasm(wasm, &terra_features).unwrap(); - let deps = mock_dependencies(20, contract_balance); - Instance::from_code(wasm, deps, DEFAULT_GAS_LIMIT).unwrap() -} - -static TOKEN_CODE_ID: u64 = 10u64; -static BASE_DENOM: &str = "uusd"; -#[test] -fn proper_initialization() { - let mut deps = mock_instance(WASM, &[]); - - let msg = InitMsg { - base_denom: BASE_DENOM.to_string(), - token_code_id: TOKEN_CODE_ID, - distribution_schedule: vec![], - }; - - let env = mock_env("addr0000", &[]); - let _res: InitResponse = init(&mut deps, env.clone(), msg).unwrap(); - - let msg = HandleMsg::PostInitialize { - owner: HumanAddr::from("owner0000"), - mirror_token: HumanAddr::from("mirror0000"), - mint_contract: HumanAddr::from("mint0000"), - staking_contract: HumanAddr::from("staking0000"), - commission_collector: HumanAddr::from("collector0000"), - oracle_contract: HumanAddr::from("oracle0000"), - terraswap_factory: HumanAddr::from("terraswapfactory"), - }; - let _res: HandleResponse = handle(&mut deps, env.clone(), msg).unwrap(); - - // cannot update mirror token after initialization - let msg = HandleMsg::PostInitialize { - owner: HumanAddr::from("owner0000"), - mirror_token: HumanAddr::from("mirror0000"), - mint_contract: HumanAddr::from("mint0000"), - staking_contract: HumanAddr::from("staking0000"), - commission_collector: HumanAddr::from("collector0000"), - oracle_contract: HumanAddr::from("oracle0000"), - terraswap_factory: HumanAddr::from("terraswapfactory"), - }; - let res: HandleResult = handle(&mut deps, env, msg); - match res { - Err(StdError::Unauthorized { .. }) => {} - _ => panic!("DO NOT ENTER HERE"), - } - - // it worked, let's query the state - let res = query(&mut deps, QueryMsg::Config {}).unwrap(); - let config: ConfigResponse = from_binary(&res).unwrap(); - assert_eq!( - config, - ConfigResponse { - owner: HumanAddr::from("owner0000"), - mirror_token: HumanAddr::from("mirror0000"), - mint_contract: HumanAddr::from("mint0000"), - staking_contract: HumanAddr::from("staking0000"), - commission_collector: HumanAddr::from("collector0000"), - oracle_contract: HumanAddr::from("oracle0000"), - terraswap_factory: HumanAddr::from("terraswapfactory"), - base_denom: BASE_DENOM.to_string(), - token_code_id: TOKEN_CODE_ID, - genesis_time: 1_571_797_419, - distribution_schedule: vec![], - } - ); -} - -#[test] -fn test_update_config() { - let mut deps = mock_instance(WASM, &[]); - - let msg = InitMsg { - base_denom: BASE_DENOM.to_string(), - token_code_id: TOKEN_CODE_ID, - distribution_schedule: vec![], - }; - - let env = mock_env("addr0000", &[]); - let _res: InitResponse = init(&mut deps, env.clone(), msg).unwrap(); - - let msg = HandleMsg::PostInitialize { - owner: HumanAddr::from("owner0000"), - mirror_token: HumanAddr::from("mirror0000"), - mint_contract: HumanAddr::from("mint0000"), - staking_contract: HumanAddr::from("staking0000"), - commission_collector: HumanAddr::from("collector0000"), - oracle_contract: HumanAddr::from("oracle0000"), - terraswap_factory: HumanAddr::from("terraswapfactory"), - }; - let _res: HandleResponse = handle(&mut deps, env.clone(), msg).unwrap(); - - // upate owner - let msg = HandleMsg::UpdateConfig { - owner: Some(HumanAddr::from("owner0001")), - token_code_id: None, - distribution_schedule: None, - }; - - let env = mock_env("owner0000", &[]); - let _res: HandleResponse = handle(&mut deps, env, msg).unwrap(); - let res = query(&mut deps, QueryMsg::Config {}).unwrap(); - let config: ConfigResponse = from_binary(&res).unwrap(); - assert_eq!( - config, - ConfigResponse { - owner: HumanAddr::from("owner0001"), - mirror_token: HumanAddr::from("mirror0000"), - mint_contract: HumanAddr::from("mint0000"), - staking_contract: HumanAddr::from("staking0000"), - commission_collector: HumanAddr::from("collector0000"), - oracle_contract: HumanAddr::from("oracle0000"), - terraswap_factory: HumanAddr::from("terraswapfactory"), - base_denom: BASE_DENOM.to_string(), - token_code_id: TOKEN_CODE_ID, - genesis_time: 1_571_797_419, - distribution_schedule: vec![], - } - ); - - // update rest part - let msg = HandleMsg::UpdateConfig { - owner: None, - token_code_id: Some(TOKEN_CODE_ID + 1), - distribution_schedule: Some(vec![(1, 2, Uint128::from(123u128))]), - }; - - let env = mock_env("owner0001", &[]); - let _res: HandleResponse = handle(&mut deps, env, msg).unwrap(); - let res = query(&mut deps, QueryMsg::Config {}).unwrap(); - let config: ConfigResponse = from_binary(&res).unwrap(); - assert_eq!( - config, - ConfigResponse { - owner: HumanAddr::from("owner0001"), - mirror_token: HumanAddr::from("mirror0000"), - mint_contract: HumanAddr::from("mint0000"), - staking_contract: HumanAddr::from("staking0000"), - commission_collector: HumanAddr::from("collector0000"), - oracle_contract: HumanAddr::from("oracle0000"), - terraswap_factory: HumanAddr::from("terraswapfactory"), - base_denom: BASE_DENOM.to_string(), - token_code_id: TOKEN_CODE_ID + 1, - genesis_time: 1_571_797_419, - distribution_schedule: vec![(1, 2, Uint128::from(123u128))], - } - ); - - // failed unauthoirzed - let msg = HandleMsg::UpdateConfig { - owner: None, - distribution_schedule: None, - token_code_id: Some(TOKEN_CODE_ID + 1), - }; - - let env = mock_env("owner0000", &[]); - let res: HandleResult = handle(&mut deps, env, msg); - match res { - Err(StdError::Unauthorized { .. }) => {} - _ => panic!("DO NOT ENTER HERE"), - } -} diff --git a/contracts/mirror_gov/.editorconfig b/contracts/mirror_gov/.editorconfig deleted file mode 100644 index 3d36f20b1..000000000 --- a/contracts/mirror_gov/.editorconfig +++ /dev/null @@ -1,11 +0,0 @@ -root = true - -[*] -indent_style = space -indent_size = 2 -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true - -[*.rs] -indent_size = 4 diff --git a/contracts/mirror_gov/.gitignore b/contracts/mirror_gov/.gitignore deleted file mode 100644 index 6815fd944..000000000 --- a/contracts/mirror_gov/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -/target -**/*.rs.bk -*.iml -.idea diff --git a/contracts/mirror_gov/Cargo.toml b/contracts/mirror_gov/Cargo.toml deleted file mode 100644 index 71034653b..000000000 --- a/contracts/mirror_gov/Cargo.toml +++ /dev/null @@ -1,51 +0,0 @@ -[package] -name = "mirror-gov" -version = "1.1.0" -authors = ["Terraform Labs, PTE."] -edition = "2018" -license = "Apache-2.0" -description = "A Goverance contract for Mirror Protocol - allows a user to create poll and do vote" -repository = "https://github.com/CosmWasm/cosmwasm-examples" - -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 = ["cranelift"] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces", "cosmwasm-vm/backtraces"] -cranelift = ["cosmwasm-vm/default-cranelift"] -singlepass = ["cosmwasm-vm/default-singlepass"] - -[dependencies] -cw20 = "0.2" -cosmwasm-std = { version = "0.10.1", features = ["iterator"] } -cosmwasm-storage = { version = "0.10.1", features = ["iterator"] } -mirror-protocol = { version = "1.1.0", path = "../../packages/mirror_protocol" } -schemars = "0.7" -serde = { version = "1.0.103", default-features = false, features = ["derive"] } -hex = "0.4" - -[dev-dependencies] -cosmwasm-vm = { version = "0.10.1", default-features = false, features = ["iterator"] } -cosmwasm-schema = "0.10.0" diff --git a/contracts/mirror_gov/README.md b/contracts/mirror_gov/README.md deleted file mode 100644 index 8b7a75d2d..000000000 --- a/contracts/mirror_gov/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Mirror Governance - -**NOTE**: Reference documentation for this contract is available [here](https://docs.mirror.finance/contracts/gov). - -The Gov Contract contains logic for holding polls and Mirror Token (MIR) staking, and allows the Mirror Protocol to be governed by its users in a decentralized manner. After the initial bootstrapping of Mirror Protocol contracts, the Gov Contract is assigned to be the owner of itself and Mirror Factory. - -New proposals for change are submitted as polls, and are voted on by MIR stakers through the voting procedure. Polls can contain messages that can be executed directly without changing the Mirror Protocol code. - -The Gov Contract keeps a balance of MIR tokens, which it uses to reward stakers with funds it receives from trading fees sent by the Mirror Collector and user deposits from creating new governance polls. This balance is separate from the Community Pool, which is held by the Community contract (owned by the Gov contract). diff --git a/contracts/mirror_gov/examples/schema.rs b/contracts/mirror_gov/examples/schema.rs deleted file mode 100644 index e9e2a7acf..000000000 --- a/contracts/mirror_gov/examples/schema.rs +++ /dev/null @@ -1,27 +0,0 @@ -use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; -use std::env::current_dir; -use std::fs::create_dir_all; - -use mirror_protocol::gov::{ - ConfigResponse, Cw20HookMsg, ExecuteMsg, HandleMsg, InitMsg, MigrateMsg, PollCountResponse, - PollResponse, PollsResponse, QueryMsg, StakerResponse, -}; - -fn main() { - let mut out_dir = current_dir().unwrap(); - out_dir.push("schema"); - create_dir_all(&out_dir).unwrap(); - remove_schemas(&out_dir).unwrap(); - - export_schema(&schema_for!(InitMsg), &out_dir); - export_schema(&schema_for!(HandleMsg), &out_dir); - export_schema(&schema_for!(Cw20HookMsg), &out_dir); - export_schema(&schema_for!(QueryMsg), &out_dir); - export_schema(&schema_for!(StakerResponse), &out_dir); - export_schema(&schema_for!(ConfigResponse), &out_dir); - export_schema(&schema_for!(PollResponse), &out_dir); - export_schema(&schema_for!(ExecuteMsg), &out_dir); - export_schema(&schema_for!(PollsResponse), &out_dir); - export_schema(&schema_for!(PollCountResponse), &out_dir); - export_schema(&schema_for!(MigrateMsg), &out_dir); -} diff --git a/contracts/mirror_gov/rustfmt.toml b/contracts/mirror_gov/rustfmt.toml deleted file mode 100644 index 11a85e6a9..000000000 --- a/contracts/mirror_gov/rustfmt.toml +++ /dev/null @@ -1,15 +0,0 @@ -# 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/mirror_gov/schema/config_response.json b/contracts/mirror_gov/schema/config_response.json deleted file mode 100644 index 281faeeb7..000000000 --- a/contracts/mirror_gov/schema/config_response.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ConfigResponse", - "type": "object", - "required": [ - "effective_delay", - "expiration_period", - "mirror_token", - "owner", - "proposal_deposit", - "quorum", - "snapshot_period", - "threshold", - "voter_weight", - "voting_period" - ], - "properties": { - "effective_delay": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "expiration_period": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "mirror_token": { - "$ref": "#/definitions/HumanAddr" - }, - "owner": { - "$ref": "#/definitions/HumanAddr" - }, - "proposal_deposit": { - "$ref": "#/definitions/Uint128" - }, - "quorum": { - "$ref": "#/definitions/Decimal" - }, - "snapshot_period": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "threshold": { - "$ref": "#/definitions/Decimal" - }, - "voter_weight": { - "$ref": "#/definitions/Decimal" - }, - "voting_period": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "definitions": { - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0 The greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "HumanAddr": { - "type": "string" - }, - "Uint128": { - "type": "string" - } - } -} diff --git a/contracts/mirror_gov/schema/cw20_hook_msg.json b/contracts/mirror_gov/schema/cw20_hook_msg.json deleted file mode 100644 index ba7acc87c..000000000 --- a/contracts/mirror_gov/schema/cw20_hook_msg.json +++ /dev/null @@ -1,94 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Cw20HookMsg", - "anyOf": [ - { - "description": "StakeVotingTokens a user can stake their mirror token to receive rewards or do vote on polls", - "type": "object", - "required": [ - "stake_voting_tokens" - ], - "properties": { - "stake_voting_tokens": { - "type": "object" - } - } - }, - { - "description": "CreatePoll need to receive deposit from a proposer", - "type": "object", - "required": [ - "create_poll" - ], - "properties": { - "create_poll": { - "type": "object", - "required": [ - "description", - "title" - ], - "properties": { - "description": { - "type": "string" - }, - "execute_msg": { - "anyOf": [ - { - "$ref": "#/definitions/ExecuteMsg" - }, - { - "type": "null" - } - ] - }, - "link": { - "type": [ - "string", - "null" - ] - }, - "title": { - "type": "string" - } - } - } - } - }, - { - "description": "Deposit rewards to be distributed among stakers and voters", - "type": "object", - "required": [ - "deposit_reward" - ], - "properties": { - "deposit_reward": { - "type": "object" - } - } - } - ], - "definitions": { - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", - "type": "string" - }, - "ExecuteMsg": { - "type": "object", - "required": [ - "contract", - "msg" - ], - "properties": { - "contract": { - "$ref": "#/definitions/HumanAddr" - }, - "msg": { - "$ref": "#/definitions/Binary" - } - } - }, - "HumanAddr": { - "type": "string" - } - } -} diff --git a/contracts/mirror_gov/schema/handle_msg.json b/contracts/mirror_gov/schema/handle_msg.json deleted file mode 100644 index 8243701ca..000000000 --- a/contracts/mirror_gov/schema/handle_msg.json +++ /dev/null @@ -1,320 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "HandleMsg", - "anyOf": [ - { - "type": "object", - "required": [ - "receive" - ], - "properties": { - "receive": { - "$ref": "#/definitions/Cw20ReceiveMsg" - } - } - }, - { - "type": "object", - "required": [ - "update_config" - ], - "properties": { - "update_config": { - "type": "object", - "properties": { - "effective_delay": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - }, - "expiration_period": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - }, - "owner": { - "anyOf": [ - { - "$ref": "#/definitions/HumanAddr" - }, - { - "type": "null" - } - ] - }, - "proposal_deposit": { - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - }, - "quorum": { - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - }, - "snapshot_period": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - }, - "threshold": { - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - }, - "voter_weight": { - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - }, - "voting_period": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - } - } - } - } - }, - { - "type": "object", - "required": [ - "cast_vote" - ], - "properties": { - "cast_vote": { - "type": "object", - "required": [ - "amount", - "poll_id", - "vote" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "poll_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "vote": { - "$ref": "#/definitions/VoteOption" - } - } - } - } - }, - { - "type": "object", - "required": [ - "withdraw_voting_tokens" - ], - "properties": { - "withdraw_voting_tokens": { - "type": "object", - "properties": { - "amount": { - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - } - } - } - } - }, - { - "type": "object", - "required": [ - "withdraw_voting_rewards" - ], - "properties": { - "withdraw_voting_rewards": { - "type": "object" - } - } - }, - { - "type": "object", - "required": [ - "stake_voting_rewards" - ], - "properties": { - "stake_voting_rewards": { - "type": "object" - } - } - }, - { - "type": "object", - "required": [ - "end_poll" - ], - "properties": { - "end_poll": { - "type": "object", - "required": [ - "poll_id" - ], - "properties": { - "poll_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - } - } - }, - { - "type": "object", - "required": [ - "execute_poll" - ], - "properties": { - "execute_poll": { - "type": "object", - "required": [ - "poll_id" - ], - "properties": { - "poll_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - } - } - }, - { - "type": "object", - "required": [ - "expire_poll" - ], - "properties": { - "expire_poll": { - "type": "object", - "required": [ - "poll_id" - ], - "properties": { - "poll_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - } - } - }, - { - "type": "object", - "required": [ - "snapshot_poll" - ], - "properties": { - "snapshot_poll": { - "type": "object", - "required": [ - "poll_id" - ], - "properties": { - "poll_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - } - } - } - ], - "definitions": { - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", - "type": "string" - }, - "Cw20ReceiveMsg": { - "description": "Cw20ReceiveMsg should be de/serialized under `Receive()` variant in a HandleMsg", - "type": "object", - "required": [ - "amount", - "sender" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "msg": { - "anyOf": [ - { - "$ref": "#/definitions/Binary" - }, - { - "type": "null" - } - ] - }, - "sender": { - "$ref": "#/definitions/HumanAddr" - } - } - }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0 The greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "HumanAddr": { - "type": "string" - }, - "Uint128": { - "type": "string" - }, - "VoteOption": { - "type": "string", - "enum": [ - "yes", - "no", - "abstain" - ] - } - } -} diff --git a/contracts/mirror_gov/schema/init_msg.json b/contracts/mirror_gov/schema/init_msg.json deleted file mode 100644 index 9706375f3..000000000 --- a/contracts/mirror_gov/schema/init_msg.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InitMsg", - "type": "object", - "required": [ - "effective_delay", - "expiration_period", - "mirror_token", - "proposal_deposit", - "quorum", - "snapshot_period", - "threshold", - "voter_weight", - "voting_period" - ], - "properties": { - "effective_delay": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "expiration_period": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "mirror_token": { - "$ref": "#/definitions/HumanAddr" - }, - "proposal_deposit": { - "$ref": "#/definitions/Uint128" - }, - "quorum": { - "$ref": "#/definitions/Decimal" - }, - "snapshot_period": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "threshold": { - "$ref": "#/definitions/Decimal" - }, - "voter_weight": { - "$ref": "#/definitions/Decimal" - }, - "voting_period": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "definitions": { - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0 The greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "HumanAddr": { - "type": "string" - }, - "Uint128": { - "type": "string" - } - } -} diff --git a/contracts/mirror_gov/schema/migrate_msg.json b/contracts/mirror_gov/schema/migrate_msg.json deleted file mode 100644 index 3b84a7914..000000000 --- a/contracts/mirror_gov/schema/migrate_msg.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "MigrateMsg", - "description": "Migrates the contract state, currently taking a state version argument", - "type": "object", - "required": [ - "snapshot_period", - "version", - "voter_weight" - ], - "properties": { - "snapshot_period": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "version": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "voter_weight": { - "$ref": "#/definitions/Decimal" - } - }, - "definitions": { - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0 The greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - } - } -} diff --git a/contracts/mirror_gov/schema/poll_response.json b/contracts/mirror_gov/schema/poll_response.json deleted file mode 100644 index 4a773eee6..000000000 --- a/contracts/mirror_gov/schema/poll_response.json +++ /dev/null @@ -1,130 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "PollResponse", - "type": "object", - "required": [ - "abstain_votes", - "creator", - "deposit_amount", - "description", - "end_height", - "id", - "no_votes", - "status", - "title", - "voters_reward", - "yes_votes" - ], - "properties": { - "abstain_votes": { - "$ref": "#/definitions/Uint128" - }, - "creator": { - "$ref": "#/definitions/HumanAddr" - }, - "deposit_amount": { - "$ref": "#/definitions/Uint128" - }, - "description": { - "type": "string" - }, - "end_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "execute_data": { - "anyOf": [ - { - "$ref": "#/definitions/ExecuteMsg" - }, - { - "type": "null" - } - ] - }, - "id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "link": { - "type": [ - "string", - "null" - ] - }, - "no_votes": { - "$ref": "#/definitions/Uint128" - }, - "staked_amount": { - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - }, - "status": { - "$ref": "#/definitions/PollStatus" - }, - "title": { - "type": "string" - }, - "total_balance_at_end_poll": { - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - }, - "voters_reward": { - "$ref": "#/definitions/Uint128" - }, - "yes_votes": { - "$ref": "#/definitions/Uint128" - } - }, - "definitions": { - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", - "type": "string" - }, - "ExecuteMsg": { - "type": "object", - "required": [ - "contract", - "msg" - ], - "properties": { - "contract": { - "$ref": "#/definitions/HumanAddr" - }, - "msg": { - "$ref": "#/definitions/Binary" - } - } - }, - "HumanAddr": { - "type": "string" - }, - "PollStatus": { - "type": "string", - "enum": [ - "in_progress", - "passed", - "rejected", - "executed", - "expired" - ] - }, - "Uint128": { - "type": "string" - } - } -} diff --git a/contracts/mirror_gov/schema/polls_response.json b/contracts/mirror_gov/schema/polls_response.json deleted file mode 100644 index 1c74330bb..000000000 --- a/contracts/mirror_gov/schema/polls_response.json +++ /dev/null @@ -1,144 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "PollsResponse", - "type": "object", - "required": [ - "polls" - ], - "properties": { - "polls": { - "type": "array", - "items": { - "$ref": "#/definitions/PollResponse" - } - } - }, - "definitions": { - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", - "type": "string" - }, - "ExecuteMsg": { - "type": "object", - "required": [ - "contract", - "msg" - ], - "properties": { - "contract": { - "$ref": "#/definitions/HumanAddr" - }, - "msg": { - "$ref": "#/definitions/Binary" - } - } - }, - "HumanAddr": { - "type": "string" - }, - "PollResponse": { - "type": "object", - "required": [ - "abstain_votes", - "creator", - "deposit_amount", - "description", - "end_height", - "id", - "no_votes", - "status", - "title", - "voters_reward", - "yes_votes" - ], - "properties": { - "abstain_votes": { - "$ref": "#/definitions/Uint128" - }, - "creator": { - "$ref": "#/definitions/HumanAddr" - }, - "deposit_amount": { - "$ref": "#/definitions/Uint128" - }, - "description": { - "type": "string" - }, - "end_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "execute_data": { - "anyOf": [ - { - "$ref": "#/definitions/ExecuteMsg" - }, - { - "type": "null" - } - ] - }, - "id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "link": { - "type": [ - "string", - "null" - ] - }, - "no_votes": { - "$ref": "#/definitions/Uint128" - }, - "staked_amount": { - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - }, - "status": { - "$ref": "#/definitions/PollStatus" - }, - "title": { - "type": "string" - }, - "total_balance_at_end_poll": { - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - }, - "voters_reward": { - "$ref": "#/definitions/Uint128" - }, - "yes_votes": { - "$ref": "#/definitions/Uint128" - } - } - }, - "PollStatus": { - "type": "string", - "enum": [ - "in_progress", - "passed", - "rejected", - "executed", - "expired" - ] - }, - "Uint128": { - "type": "string" - } - } -} diff --git a/contracts/mirror_gov/schema/query_msg.json b/contracts/mirror_gov/schema/query_msg.json deleted file mode 100644 index c1e3c6f12..000000000 --- a/contracts/mirror_gov/schema/query_msg.json +++ /dev/null @@ -1,188 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryMsg", - "anyOf": [ - { - "type": "object", - "required": [ - "config" - ], - "properties": { - "config": { - "type": "object" - } - } - }, - { - "type": "object", - "required": [ - "state" - ], - "properties": { - "state": { - "type": "object" - } - } - }, - { - "type": "object", - "required": [ - "staker" - ], - "properties": { - "staker": { - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "$ref": "#/definitions/HumanAddr" - } - } - } - } - }, - { - "type": "object", - "required": [ - "poll" - ], - "properties": { - "poll": { - "type": "object", - "required": [ - "poll_id" - ], - "properties": { - "poll_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - } - } - }, - { - "type": "object", - "required": [ - "polls" - ], - "properties": { - "polls": { - "type": "object", - "properties": { - "filter": { - "anyOf": [ - { - "$ref": "#/definitions/PollStatus" - }, - { - "type": "null" - } - ] - }, - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "order_by": { - "anyOf": [ - { - "$ref": "#/definitions/OrderBy" - }, - { - "type": "null" - } - ] - }, - "start_after": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - } - } - } - } - }, - { - "type": "object", - "required": [ - "voters" - ], - "properties": { - "voters": { - "type": "object", - "required": [ - "poll_id" - ], - "properties": { - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "order_by": { - "anyOf": [ - { - "$ref": "#/definitions/OrderBy" - }, - { - "type": "null" - } - ] - }, - "poll_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "start_after": { - "anyOf": [ - { - "$ref": "#/definitions/HumanAddr" - }, - { - "type": "null" - } - ] - } - } - } - } - } - ], - "definitions": { - "HumanAddr": { - "type": "string" - }, - "OrderBy": { - "type": "string", - "enum": [ - "asc", - "desc" - ] - }, - "PollStatus": { - "type": "string", - "enum": [ - "in_progress", - "passed", - "rejected", - "executed", - "expired" - ] - } - } -} diff --git a/contracts/mirror_gov/schema/staker_response.json b/contracts/mirror_gov/schema/staker_response.json deleted file mode 100644 index e2a84ba65..000000000 --- a/contracts/mirror_gov/schema/staker_response.json +++ /dev/null @@ -1,87 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "StakerResponse", - "type": "object", - "required": [ - "balance", - "locked_balance", - "pending_voting_rewards", - "share", - "withdrawable_polls" - ], - "properties": { - "balance": { - "$ref": "#/definitions/Uint128" - }, - "locked_balance": { - "type": "array", - "items": { - "type": "array", - "items": [ - { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - { - "$ref": "#/definitions/VoterInfo" - } - ], - "maxItems": 2, - "minItems": 2 - } - }, - "pending_voting_rewards": { - "$ref": "#/definitions/Uint128" - }, - "share": { - "$ref": "#/definitions/Uint128" - }, - "withdrawable_polls": { - "type": "array", - "items": { - "type": "array", - "items": [ - { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - { - "$ref": "#/definitions/Uint128" - } - ], - "maxItems": 2, - "minItems": 2 - } - } - }, - "definitions": { - "Uint128": { - "type": "string" - }, - "VoteOption": { - "type": "string", - "enum": [ - "yes", - "no", - "abstain" - ] - }, - "VoterInfo": { - "type": "object", - "required": [ - "balance", - "vote" - ], - "properties": { - "balance": { - "$ref": "#/definitions/Uint128" - }, - "vote": { - "$ref": "#/definitions/VoteOption" - } - } - } - } -} diff --git a/contracts/mirror_gov/src/contract.rs b/contracts/mirror_gov/src/contract.rs deleted file mode 100644 index d3200b720..000000000 --- a/contracts/mirror_gov/src/contract.rs +++ /dev/null @@ -1,900 +0,0 @@ -use crate::querier::load_token_balance; -use crate::staking::{ - deposit_reward, query_staker, stake_voting_rewards, stake_voting_tokens, - withdraw_voting_rewards, withdraw_voting_tokens, -}; -use crate::state::{ - bank_read, bank_store, config_read, config_store, poll_indexer_store, poll_read, poll_store, - poll_voter_read, poll_voter_store, read_poll_voters, read_polls, state_read, state_store, - Config, ExecuteData, Poll, State, -}; - -use cosmwasm_std::{ - from_binary, log, to_binary, Api, Binary, CosmosMsg, Decimal, Env, Extern, HandleResponse, - HandleResult, HumanAddr, InitResponse, InitResult, MigrateResponse, MigrateResult, Querier, - StdError, StdResult, Storage, Uint128, WasmMsg, -}; -use cw20::{Cw20HandleMsg, Cw20ReceiveMsg}; - -use mirror_protocol::common::OrderBy; -use mirror_protocol::gov::{ - ConfigResponse, Cw20HookMsg, ExecuteMsg, HandleMsg, InitMsg, MigrateMsg, PollResponse, - PollStatus, PollsResponse, QueryMsg, StateResponse, VoteOption, VoterInfo, VotersResponse, - VotersResponseItem, -}; - -const MIN_TITLE_LENGTH: usize = 4; -const MAX_TITLE_LENGTH: usize = 64; -const MIN_DESC_LENGTH: usize = 4; -const MAX_DESC_LENGTH: usize = 256; -const MIN_LINK_LENGTH: usize = 12; -const MAX_LINK_LENGTH: usize = 128; - -pub fn init( - deps: &mut Extern, - env: Env, - msg: InitMsg, -) -> InitResult { - validate_quorum(msg.quorum)?; - validate_threshold(msg.threshold)?; - - let config = Config { - mirror_token: deps.api.canonical_address(&msg.mirror_token)?, - owner: deps.api.canonical_address(&env.message.sender)?, - quorum: msg.quorum, - threshold: msg.threshold, - voting_period: msg.voting_period, - effective_delay: msg.effective_delay, - expiration_period: msg.expiration_period, - proposal_deposit: msg.proposal_deposit, - voter_weight: msg.voter_weight, - snapshot_period: msg.snapshot_period, - }; - - let state = State { - contract_addr: deps.api.canonical_address(&env.contract.address)?, - poll_count: 0, - total_share: Uint128::zero(), - total_deposit: Uint128::zero(), - pending_voting_rewards: Uint128::zero(), - }; - - config_store(&mut deps.storage).save(&config)?; - state_store(&mut deps.storage).save(&state)?; - - Ok(InitResponse::default()) -} - -pub fn handle( - deps: &mut Extern, - env: Env, - msg: HandleMsg, -) -> StdResult { - match msg { - HandleMsg::Receive(msg) => receive_cw20(deps, env, msg), - HandleMsg::UpdateConfig { - owner, - quorum, - threshold, - voting_period, - effective_delay, - expiration_period, - proposal_deposit, - voter_weight, - snapshot_period, - } => update_config( - deps, - env, - owner, - quorum, - threshold, - voting_period, - effective_delay, - expiration_period, - proposal_deposit, - voter_weight, - snapshot_period, - ), - HandleMsg::WithdrawVotingTokens { amount } => withdraw_voting_tokens(deps, env, amount), - HandleMsg::WithdrawVotingRewards {} => withdraw_voting_rewards(deps, env), - HandleMsg::StakeVotingRewards {} => stake_voting_rewards(deps, env), - HandleMsg::CastVote { - poll_id, - vote, - amount, - } => cast_vote(deps, env, poll_id, vote, amount), - HandleMsg::EndPoll { poll_id } => end_poll(deps, env, poll_id), - HandleMsg::ExecutePoll { poll_id } => execute_poll(deps, env, poll_id), - HandleMsg::ExpirePoll { poll_id } => expire_poll(deps, env, poll_id), - HandleMsg::SnapshotPoll { poll_id } => snapshot_poll(deps, env, poll_id), - } -} - -pub fn receive_cw20( - deps: &mut Extern, - env: Env, - cw20_msg: Cw20ReceiveMsg, -) -> HandleResult { - // only asset contract can execute this message - let config: Config = config_read(&deps.storage).load()?; - if config.mirror_token != deps.api.canonical_address(&env.message.sender)? { - return Err(StdError::unauthorized()); - } - - if let Some(msg) = cw20_msg.msg { - match from_binary(&msg)? { - Cw20HookMsg::StakeVotingTokens {} => { - stake_voting_tokens(deps, env, cw20_msg.sender, cw20_msg.amount) - } - Cw20HookMsg::CreatePoll { - title, - description, - link, - execute_msg, - } => create_poll( - deps, - env, - cw20_msg.sender, - cw20_msg.amount, - title, - description, - link, - execute_msg, - ), - Cw20HookMsg::DepositReward {} => { - // only reward token contract can execute this message - if config.mirror_token != deps.api.canonical_address(&env.message.sender)? { - return Err(StdError::unauthorized()); - } - deposit_reward(deps, env, cw20_msg.sender, cw20_msg.amount) - } - } - } else { - Err(StdError::generic_err("data should be given")) - } -} - -#[allow(clippy::too_many_arguments)] -pub fn update_config( - deps: &mut Extern, - env: Env, - owner: Option, - quorum: Option, - threshold: Option, - voting_period: Option, - effective_delay: Option, - expiration_period: Option, - proposal_deposit: Option, - voter_weight: Option, - snapshot_period: Option, -) -> HandleResult { - let api = deps.api; - config_store(&mut deps.storage).update(|mut config| { - if config.owner != api.canonical_address(&env.message.sender)? { - return Err(StdError::unauthorized()); - } - - if let Some(owner) = owner { - config.owner = api.canonical_address(&owner)?; - } - - if let Some(quorum) = quorum { - validate_quorum(quorum)?; - config.quorum = quorum; - } - - if let Some(threshold) = threshold { - validate_threshold(threshold)?; - config.threshold = threshold; - } - - if let Some(voting_period) = voting_period { - config.voting_period = voting_period; - } - - if let Some(effective_delay) = effective_delay { - config.effective_delay = effective_delay; - } - - if let Some(expiration_period) = expiration_period { - config.expiration_period = expiration_period; - } - - if let Some(proposal_deposit) = proposal_deposit { - config.proposal_deposit = proposal_deposit; - } - - if let Some(voter_weight) = voter_weight { - validate_voter_weight(voter_weight)?; - config.voter_weight = voter_weight; - } - - if let Some(snapshot_period) = snapshot_period { - config.snapshot_period = snapshot_period; - } - - Ok(config) - })?; - Ok(HandleResponse::default()) -} - -/// validate_title returns an error if the title is invalid -fn validate_title(title: &str) -> StdResult<()> { - if title.len() < MIN_TITLE_LENGTH { - Err(StdError::generic_err("Title too short")) - } else if title.len() > MAX_TITLE_LENGTH { - Err(StdError::generic_err("Title too long")) - } else { - Ok(()) - } -} - -/// validate_description returns an error if the description is invalid -fn validate_description(description: &str) -> StdResult<()> { - if description.len() < MIN_DESC_LENGTH { - Err(StdError::generic_err("Description too short")) - } else if description.len() > MAX_DESC_LENGTH { - Err(StdError::generic_err("Description too long")) - } else { - Ok(()) - } -} - -/// validate_link returns an error if the link is invalid -fn validate_link(link: &Option) -> StdResult<()> { - if let Some(link) = link { - if link.len() < MIN_LINK_LENGTH { - Err(StdError::generic_err("Link too short")) - } else if link.len() > MAX_LINK_LENGTH { - Err(StdError::generic_err("Link too long")) - } else { - Ok(()) - } - } else { - Ok(()) - } -} - -/// validate_quorum returns an error if the quorum is invalid -/// (we require 0-1) -fn validate_quorum(quorum: Decimal) -> StdResult<()> { - if quorum > Decimal::one() { - Err(StdError::generic_err("quorum must be 0 to 1")) - } else { - Ok(()) - } -} - -/// validate_threshold returns an error if the threshold is invalid -/// (we require 0-1) -fn validate_threshold(threshold: Decimal) -> StdResult<()> { - if threshold > Decimal::one() { - Err(StdError::generic_err("threshold must be 0 to 1")) - } else { - Ok(()) - } -} - -pub fn validate_voter_weight(voter_weight: Decimal) -> StdResult<()> { - if voter_weight >= Decimal::one() { - Err(StdError::generic_err("voter_weight must be smaller than 1")) - } else { - Ok(()) - } -} - -#[allow(clippy::too_many_arguments)] -/// create a new poll -pub fn create_poll( - deps: &mut Extern, - env: Env, - proposer: HumanAddr, - deposit_amount: Uint128, - title: String, - description: String, - link: Option, - execute_msg: Option, -) -> StdResult { - validate_title(&title)?; - validate_description(&description)?; - validate_link(&link)?; - - let config: Config = config_store(&mut deps.storage).load()?; - if deposit_amount < config.proposal_deposit { - return Err(StdError::generic_err(format!( - "Must deposit more than {} token", - config.proposal_deposit - ))); - } - - let mut state: State = state_store(&mut deps.storage).load()?; - let poll_id = state.poll_count + 1; - - // Increase poll count & total deposit amount - state.poll_count += 1; - state.total_deposit += deposit_amount; - - let execute_data = if let Some(execute_msg) = execute_msg { - Some(ExecuteData { - contract: deps.api.canonical_address(&execute_msg.contract)?, - msg: execute_msg.msg, - }) - } else { - None - }; - - let sender_address_raw = deps.api.canonical_address(&proposer)?; - let new_poll = Poll { - id: poll_id, - creator: sender_address_raw, - status: PollStatus::InProgress, - yes_votes: Uint128::zero(), - no_votes: Uint128::zero(), - abstain_votes: Uint128::zero(), - end_height: env.block.height + config.voting_period, - title, - description, - link, - execute_data, - deposit_amount, - total_balance_at_end_poll: None, - voters_reward: Uint128::zero(), - staked_amount: None, - }; - - poll_store(&mut deps.storage).save(&poll_id.to_be_bytes(), &new_poll)?; - poll_indexer_store(&mut deps.storage, &PollStatus::InProgress) - .save(&poll_id.to_be_bytes(), &true)?; - - state_store(&mut deps.storage).save(&state)?; - - let r = HandleResponse { - messages: vec![], - log: vec![ - log("action", "create_poll"), - log( - "creator", - deps.api.human_address(&new_poll.creator)?.as_str(), - ), - log("poll_id", &poll_id.to_string()), - log("end_height", new_poll.end_height), - ], - data: None, - }; - Ok(r) -} - -/* - * Ends a poll. - */ -pub fn end_poll( - deps: &mut Extern, - env: Env, - poll_id: u64, -) -> HandleResult { - let mut a_poll: Poll = poll_store(&mut deps.storage).load(&poll_id.to_be_bytes())?; - - if a_poll.status != PollStatus::InProgress { - return Err(StdError::generic_err("Poll is not in progress")); - } - - if a_poll.end_height > env.block.height { - return Err(StdError::generic_err("Voting period has not expired")); - } - - let no = a_poll.no_votes.u128(); - let yes = a_poll.yes_votes.u128(); - let abstain = a_poll.abstain_votes.u128(); - - let tallied_weight = yes + no + abstain; - - let mut poll_status = PollStatus::Rejected; - let mut rejected_reason = ""; - let mut passed = false; - - let mut messages: Vec = vec![]; - let config: Config = config_read(&deps.storage).load()?; - let mut state: State = state_read(&deps.storage).load()?; - - let (quorum, staked_weight) = if state.total_share.u128() == 0 { - (Decimal::zero(), Uint128::zero()) - } else if let Some(staked_amount) = a_poll.staked_amount { - ( - Decimal::from_ratio(tallied_weight, staked_amount), - staked_amount, - ) - } else { - let total_locked_balance = state.total_deposit + state.pending_voting_rewards; - let staked_weight = (load_token_balance( - &deps, - &deps.api.human_address(&config.mirror_token)?, - &state.contract_addr, - )? - total_locked_balance)?; - ( - Decimal::from_ratio(tallied_weight, staked_weight), - staked_weight, - ) - }; - - if tallied_weight == 0 || quorum < config.quorum { - // Quorum: More than quorum of the total staked tokens at the end of the voting - // period need to have participated in the vote. - rejected_reason = "Quorum not reached"; - } else { - if yes != 0u128 && Decimal::from_ratio(yes, yes + no) > config.threshold { - //Threshold: More than 50% of the tokens that participated in the vote - // (after excluding “Abstain” votes) need to have voted in favor of the proposal (“Yes”). - poll_status = PollStatus::Passed; - passed = true; - } else { - rejected_reason = "Threshold not reached"; - } - - // Refunds deposit only when quorum is reached - if !a_poll.deposit_amount.is_zero() { - messages.push(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: deps.api.human_address(&config.mirror_token)?, - send: vec![], - msg: to_binary(&Cw20HandleMsg::Transfer { - recipient: deps.api.human_address(&a_poll.creator)?, - amount: a_poll.deposit_amount, - })?, - })) - } - } - - // Decrease total deposit amount - state.total_deposit = (state.total_deposit - a_poll.deposit_amount)?; - state_store(&mut deps.storage).save(&state)?; - - // Update poll indexer - poll_indexer_store(&mut deps.storage, &PollStatus::InProgress).remove(&a_poll.id.to_be_bytes()); - poll_indexer_store(&mut deps.storage, &poll_status).save(&a_poll.id.to_be_bytes(), &true)?; - - // Update poll status - a_poll.status = poll_status; - a_poll.total_balance_at_end_poll = Some(staked_weight); - poll_store(&mut deps.storage).save(&poll_id.to_be_bytes(), &a_poll)?; - - Ok(HandleResponse { - messages, - log: vec![ - log("action", "end_poll"), - log("poll_id", &poll_id.to_string()), - log("rejected_reason", rejected_reason), - log("passed", &passed.to_string()), - ], - data: None, - }) -} - -/* - * Execute a msg of passed poll. - */ -pub fn execute_poll( - deps: &mut Extern, - env: Env, - poll_id: u64, -) -> HandleResult { - let config: Config = config_read(&deps.storage).load()?; - let mut a_poll: Poll = poll_store(&mut deps.storage).load(&poll_id.to_be_bytes())?; - - if a_poll.status != PollStatus::Passed { - return Err(StdError::generic_err("Poll is not in passed status")); - } - - if a_poll.end_height + config.effective_delay > env.block.height { - return Err(StdError::generic_err("Effective delay has not expired")); - } - - poll_indexer_store(&mut deps.storage, &PollStatus::Passed).remove(&poll_id.to_be_bytes()); - poll_indexer_store(&mut deps.storage, &PollStatus::Executed) - .save(&poll_id.to_be_bytes(), &true)?; - - a_poll.status = PollStatus::Executed; - poll_store(&mut deps.storage).save(&poll_id.to_be_bytes(), &a_poll)?; - - let mut messages: Vec = vec![]; - if let Some(execute_data) = a_poll.execute_data { - messages.push(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: deps.api.human_address(&execute_data.contract)?, - msg: execute_data.msg, - send: vec![], - })) - } else { - return Err(StdError::generic_err("The poll does not have execute_data")); - } - - Ok(HandleResponse { - messages, - log: vec![ - log("action", "execute_poll"), - log("poll_id", poll_id.to_string()), - ], - data: None, - }) -} - -/// ExpirePoll is used to make the poll as expired state for querying purpose -pub fn expire_poll( - deps: &mut Extern, - env: Env, - poll_id: u64, -) -> HandleResult { - let config: Config = config_read(&deps.storage).load()?; - let mut a_poll: Poll = poll_store(&mut deps.storage).load(&poll_id.to_be_bytes())?; - - if a_poll.status != PollStatus::Passed { - return Err(StdError::generic_err("Poll is not in passed status")); - } - - if a_poll.execute_data.is_none() { - return Err(StdError::generic_err( - "Cannot make a text proposal to expired state", - )); - } - - if a_poll.end_height + config.expiration_period > env.block.height { - return Err(StdError::generic_err("Expire height has not been reached")); - } - - poll_indexer_store(&mut deps.storage, &PollStatus::Passed).remove(&poll_id.to_be_bytes()); - poll_indexer_store(&mut deps.storage, &PollStatus::Expired) - .save(&poll_id.to_be_bytes(), &true)?; - - a_poll.status = PollStatus::Expired; - poll_store(&mut deps.storage).save(&poll_id.to_be_bytes(), &a_poll)?; - - Ok(HandleResponse { - messages: vec![], - log: vec![ - log("action", "expire_poll"), - log("poll_id", poll_id.to_string()), - ], - data: None, - }) -} - -pub fn cast_vote( - deps: &mut Extern, - env: Env, - poll_id: u64, - vote: VoteOption, - amount: Uint128, -) -> HandleResult { - let sender_address_raw = deps.api.canonical_address(&env.message.sender)?; - let config = config_read(&deps.storage).load()?; - let state = state_read(&deps.storage).load()?; - if poll_id == 0 || state.poll_count < poll_id { - return Err(StdError::generic_err("Poll does not exist")); - } - - let mut a_poll: Poll = poll_store(&mut deps.storage).load(&poll_id.to_be_bytes())?; - if a_poll.status != PollStatus::InProgress || env.block.height > a_poll.end_height { - return Err(StdError::generic_err("Poll is not in progress")); - } - - // Check the voter already has a vote on the poll - if poll_voter_read(&deps.storage, poll_id) - .load(&sender_address_raw.as_slice()) - .is_ok() - { - return Err(StdError::generic_err("User has already voted.")); - } - - let key = &sender_address_raw.as_slice(); - let mut token_manager = bank_read(&deps.storage).may_load(key)?.unwrap_or_default(); - - // convert share to amount - let total_share = state.total_share; - let total_locked_balance = state.total_deposit + state.pending_voting_rewards; - let total_balance = (load_token_balance( - &deps, - &deps.api.human_address(&config.mirror_token)?, - &state.contract_addr, - )? - total_locked_balance)?; - - if token_manager - .share - .multiply_ratio(total_balance, total_share) - < amount - { - return Err(StdError::generic_err( - "User does not have enough staked tokens.", - )); - } - - // update tally info - match vote { - VoteOption::Yes => a_poll.yes_votes += amount, - VoteOption::No => a_poll.no_votes += amount, - VoteOption::Abstain => a_poll.abstain_votes += amount, - } - - let vote_info = VoterInfo { - vote, - balance: amount, - }; - token_manager - .locked_balance - .push((poll_id, vote_info.clone())); - token_manager.participated_polls = vec![]; - bank_store(&mut deps.storage).save(key, &token_manager)?; - - // store poll voter && and update poll data - poll_voter_store(&mut deps.storage, poll_id) - .save(&sender_address_raw.as_slice(), &vote_info)?; - - // processing snapshot - let time_to_end = a_poll.end_height - env.block.height; - - if time_to_end < config.snapshot_period && a_poll.staked_amount.is_none() { - a_poll.staked_amount = Some(total_balance); - } - - poll_store(&mut deps.storage).save(&poll_id.to_be_bytes(), &a_poll)?; - - let log = vec![ - log("action", "cast_vote"), - log("poll_id", &poll_id.to_string()), - log("amount", &amount.to_string()), - log("voter", &env.message.sender.as_str()), - log("vote_option", vote_info.vote), - ]; - - let r = HandleResponse { - messages: vec![], - log, - data: None, - }; - Ok(r) -} - -/// SnapshotPoll is used to take a snapshot of the staked amount for quorum calculation -pub fn snapshot_poll( - deps: &mut Extern, - env: Env, - poll_id: u64, -) -> HandleResult { - let config: Config = config_read(&deps.storage).load()?; - let mut a_poll: Poll = poll_store(&mut deps.storage).load(&poll_id.to_be_bytes())?; - - if a_poll.status != PollStatus::InProgress { - return Err(StdError::generic_err("Poll is not in progress")); - } - - let time_to_end = a_poll.end_height - env.block.height; - - if time_to_end > config.snapshot_period { - return Err(StdError::generic_err("Cannot snapshot at this height")); - } - - if a_poll.staked_amount.is_some() { - return Err(StdError::generic_err("Snapshot has already occurred")); - } - - // store the current staked amount for quorum calculation - let state: State = state_store(&mut deps.storage).load()?; - - let total_locked_balance = state.total_deposit + state.pending_voting_rewards; - let staked_amount = (load_token_balance( - &deps, - &deps.api.human_address(&config.mirror_token)?, - &state.contract_addr, - )? - total_locked_balance)?; - - a_poll.staked_amount = Some(staked_amount); - - poll_store(&mut deps.storage).save(&poll_id.to_be_bytes(), &a_poll)?; - - Ok(HandleResponse { - messages: vec![], - log: vec![ - log("action", "snapshot_poll"), - log("poll_id", poll_id.to_string()), - log("staked_amount", staked_amount), - ], - data: None, - }) -} - -pub fn query( - deps: &Extern, - msg: QueryMsg, -) -> StdResult { - match msg { - QueryMsg::Config {} => to_binary(&query_config(&deps)?), - QueryMsg::State {} => to_binary(&query_state(&deps)?), - QueryMsg::Staker { address } => to_binary(&query_staker(deps, address)?), - QueryMsg::Poll { poll_id } => to_binary(&query_poll(deps, poll_id)?), - QueryMsg::Polls { - filter, - start_after, - limit, - order_by, - } => to_binary(&query_polls(deps, filter, start_after, limit, order_by)?), - QueryMsg::Voters { - poll_id, - start_after, - limit, - order_by, - } => to_binary(&query_voters(deps, poll_id, start_after, limit, order_by)?), - } -} - -fn query_config( - deps: &Extern, -) -> StdResult { - let config: Config = config_read(&deps.storage).load()?; - Ok(ConfigResponse { - owner: deps.api.human_address(&config.owner)?, - mirror_token: deps.api.human_address(&config.mirror_token)?, - quorum: config.quorum, - threshold: config.threshold, - voting_period: config.voting_period, - effective_delay: config.effective_delay, - expiration_period: config.expiration_period, - proposal_deposit: config.proposal_deposit, - voter_weight: config.voter_weight, - snapshot_period: config.snapshot_period, - }) -} - -fn query_state(deps: &Extern) -> StdResult { - let state: State = state_read(&deps.storage).load()?; - Ok(StateResponse { - poll_count: state.poll_count, - total_share: state.total_share, - total_deposit: state.total_deposit, - pending_voting_rewards: state.pending_voting_rewards, - }) -} - -fn query_poll( - deps: &Extern, - poll_id: u64, -) -> StdResult { - let poll = match poll_read(&deps.storage).may_load(&poll_id.to_be_bytes())? { - Some(poll) => Some(poll), - None => return Err(StdError::generic_err("Poll does not exist")), - } - .unwrap(); - - Ok(PollResponse { - id: poll.id, - creator: deps.api.human_address(&poll.creator).unwrap(), - status: poll.status, - end_height: poll.end_height, - title: poll.title, - description: poll.description, - link: poll.link, - deposit_amount: poll.deposit_amount, - execute_data: if let Some(execute_data) = poll.execute_data { - Some(ExecuteMsg { - contract: deps.api.human_address(&execute_data.contract)?, - msg: execute_data.msg, - }) - } else { - None - }, - yes_votes: poll.yes_votes, - no_votes: poll.no_votes, - abstain_votes: poll.abstain_votes, - total_balance_at_end_poll: poll.total_balance_at_end_poll, - voters_reward: poll.voters_reward, - staked_amount: poll.staked_amount, - }) -} - -fn query_polls( - deps: &Extern, - filter: Option, - start_after: Option, - limit: Option, - order_by: Option, -) -> StdResult { - let polls = read_polls(&deps.storage, filter, start_after, limit, order_by, None)?; - let poll_responses: StdResult> = polls - .iter() - .map(|poll| { - Ok(PollResponse { - id: poll.id, - creator: deps.api.human_address(&poll.creator).unwrap(), - status: poll.status.clone(), - end_height: poll.end_height, - title: poll.title.to_string(), - description: poll.description.to_string(), - link: poll.link.clone(), - deposit_amount: poll.deposit_amount, - execute_data: if let Some(execute_data) = poll.execute_data.clone() { - Some(ExecuteMsg { - contract: deps.api.human_address(&execute_data.contract)?, - msg: execute_data.msg, - }) - } else { - None - }, - yes_votes: poll.yes_votes, - no_votes: poll.no_votes, - abstain_votes: poll.abstain_votes, - total_balance_at_end_poll: poll.total_balance_at_end_poll, - voters_reward: poll.voters_reward, - staked_amount: poll.staked_amount, - }) - }) - .collect(); - - Ok(PollsResponse { - polls: poll_responses?, - }) -} - -fn query_voters( - deps: &Extern, - poll_id: u64, - start_after: Option, - limit: Option, - order_by: Option, -) -> StdResult { - let poll: Poll = match poll_read(&deps.storage).may_load(&poll_id.to_be_bytes())? { - Some(poll) => Some(poll), - None => return Err(StdError::generic_err("Poll does not exist")), - } - .unwrap(); - - let voters = if poll.status != PollStatus::InProgress { - vec![] - } else if let Some(start_after) = start_after { - read_poll_voters( - &deps.storage, - poll_id, - Some(deps.api.canonical_address(&start_after)?), - limit, - order_by, - )? - } else { - read_poll_voters(&deps.storage, poll_id, None, limit, order_by)? - }; - - let voters_response: StdResult> = voters - .iter() - .map(|voter_info| { - Ok(VotersResponseItem { - voter: deps.api.human_address(&voter_info.0)?, - vote: voter_info.1.vote.clone(), - balance: voter_info.1.balance, - }) - }) - .collect(); - - Ok(VotersResponse { - voters: voters_response?, - }) -} - -use crate::migrate::{migrate_config, migrate_poll_indexer, migrate_polls, migrate_state}; -pub fn migrate( - deps: &mut Extern, - _env: Env, - msg: MigrateMsg, -) -> MigrateResult { - // Currently support 2 migration processes - // - version 1 migrates poll indexers, config, state, and polls - // - version 2 migrates config, state and polls - if msg.version.eq(&1u64) { - migrate_poll_indexer(&mut deps.storage, &PollStatus::InProgress)?; - migrate_poll_indexer(&mut deps.storage, &PollStatus::Passed)?; - migrate_poll_indexer(&mut deps.storage, &PollStatus::Rejected)?; - migrate_poll_indexer(&mut deps.storage, &PollStatus::Executed)?; - migrate_poll_indexer(&mut deps.storage, &PollStatus::Expired)?; - } else if !msg.version.eq(&2u64) { - return Err(StdError::generic_err("Invalid migrate version number")); - } - - // migrations for voting rewards and abstain votes - migrate_config(&mut deps.storage, msg.voter_weight, msg.snapshot_period)?; - migrate_state(&mut deps.storage)?; - migrate_polls(&mut deps.storage)?; - - Ok(MigrateResponse::default()) -} diff --git a/contracts/mirror_gov/src/lib.rs b/contracts/mirror_gov/src/lib.rs deleted file mode 100644 index f011f1fb9..000000000 --- a/contracts/mirror_gov/src/lib.rs +++ /dev/null @@ -1,15 +0,0 @@ -pub mod contract; -pub mod state; - -mod migrate; -mod querier; -mod staking; - -#[cfg(test)] -mod tests; - -#[cfg(test)] -mod mock_querier; - -#[cfg(target_arch = "wasm32")] -cosmwasm_std::create_entry_points_with_migration!(contract); diff --git a/contracts/mirror_gov/src/migrate.rs b/contracts/mirror_gov/src/migrate.rs deleted file mode 100644 index 7a94c5b86..000000000 --- a/contracts/mirror_gov/src/migrate.rs +++ /dev/null @@ -1,387 +0,0 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use cosmwasm_std::{CanonicalAddr, Decimal, Order, StdError, StdResult, Storage, Uint128}; -use cosmwasm_storage::{singleton, singleton_read, Bucket, ReadonlySingleton, Singleton}; - -use crate::state::{Config, ExecuteData, Poll, State}; -use mirror_protocol::gov::PollStatus; - -use std::convert::TryInto; - -#[cfg(test)] -use crate::state::{config_read, poll_read, state_read}; - -static PREFIX_POLL_INDEXER_OLD: &[u8] = b"poll_voter"; -static PREFIX_POLL_INDEXER: &[u8] = b"poll_indexer"; -static KEY_CONFIG: &[u8] = b"config"; -static KEY_STATE: &[u8] = b"state"; -static PREFIX_POLL: &[u8] = b"poll"; - -#[cfg(test)] -pub fn poll_indexer_old_store<'a, S: Storage>( - storage: &'a mut S, - status: &PollStatus, -) -> Bucket<'a, S, bool> { - Bucket::multilevel( - &[PREFIX_POLL_INDEXER_OLD, status.to_string().as_bytes()], - storage, - ) -} -#[cfg(test)] -pub fn polls_old_store<'a, S: Storage>(storage: &'a mut S) -> Bucket<'a, S, LegacyPoll> { - Bucket::new(PREFIX_POLL, storage) -} -#[cfg(test)] -pub fn state_old_store<'a, S: Storage>(storage: &'a mut S) -> Singleton<'a, S, LegacyState> { - Singleton::new(storage, KEY_STATE) -} -#[cfg(test)] -pub fn config_old_store<'a, S: Storage>(storage: &'a mut S) -> Singleton<'a, S, LegacyConfig> { - Singleton::new(storage, KEY_CONFIG) -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct LegacyConfig { - pub owner: CanonicalAddr, - pub mirror_token: CanonicalAddr, - pub quorum: Decimal, - pub threshold: Decimal, - pub voting_period: u64, - pub effective_delay: u64, - pub expiration_period: u64, - pub proposal_deposit: Uint128, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct LegacyState { - pub contract_addr: CanonicalAddr, - pub poll_count: u64, - pub total_share: Uint128, - pub total_deposit: Uint128, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct LegacyPoll { - pub id: u64, - pub creator: CanonicalAddr, - pub status: PollStatus, - pub yes_votes: Uint128, - pub no_votes: Uint128, - pub end_height: u64, - pub title: String, - pub description: String, - pub link: Option, - pub execute_data: Option, - pub deposit_amount: Uint128, - pub total_balance_at_end_poll: Option, -} - -pub fn migrate_poll_indexer(storage: &mut S, status: &PollStatus) -> StdResult<()> { - let mut old_indexer_bucket: Bucket = Bucket::multilevel( - &[PREFIX_POLL_INDEXER_OLD, status.to_string().as_bytes()], - storage, - ); - - let mut poll_ids: Vec = vec![]; - for item in old_indexer_bucket.range(None, None, Order::Ascending) { - let (k, _) = item?; - poll_ids.push(bytes_to_u64(&k)?); - } - - for id in poll_ids.clone().into_iter() { - old_indexer_bucket.remove(&id.to_be_bytes()); - } - - let mut new_indexer_bucket: Bucket = Bucket::multilevel( - &[PREFIX_POLL_INDEXER, status.to_string().as_bytes()], - storage, - ); - - for id in poll_ids.into_iter() { - new_indexer_bucket.save(&id.to_be_bytes(), &true)?; - } - - return Ok(()); -} - -fn bytes_to_u64(data: &[u8]) -> StdResult { - match data[0..8].try_into() { - Ok(bytes) => Ok(u64::from_be_bytes(bytes)), - Err(_) => Err(StdError::generic_err( - "Corrupted data found. 8 byte expected.", - )), - } -} - -pub fn migrate_config( - storage: &mut S, - voter_weight: Decimal, - snapshot_period: u64, -) -> StdResult<()> { - let legacty_store: ReadonlySingleton = singleton_read(storage, KEY_CONFIG); - let legacy_config: LegacyConfig = legacty_store.load()?; - let config = Config { - mirror_token: legacy_config.mirror_token, - owner: legacy_config.owner, - quorum: legacy_config.quorum, - threshold: legacy_config.threshold, - voting_period: legacy_config.voting_period, - effective_delay: legacy_config.effective_delay, - expiration_period: legacy_config.expiration_period, - proposal_deposit: legacy_config.proposal_deposit, - voter_weight: voter_weight, - snapshot_period: snapshot_period, - }; - let mut store: Singleton = singleton(storage, KEY_CONFIG); - store.save(&config)?; - Ok(()) -} - -pub fn migrate_state(storage: &mut S) -> StdResult<()> { - let legacy_store: ReadonlySingleton = singleton_read(storage, KEY_STATE); - let legacy_state: LegacyState = legacy_store.load()?; - let state = State { - contract_addr: legacy_state.contract_addr, - poll_count: legacy_state.poll_count, - total_share: legacy_state.total_share, - total_deposit: legacy_state.total_deposit, - pending_voting_rewards: Uint128::zero(), - }; - let mut store: Singleton = singleton(storage, KEY_STATE); - store.save(&state)?; - Ok(()) -} - -pub fn migrate_polls(storage: &mut S) -> StdResult<()> { - let mut legacy_polls_bucket: Bucket = Bucket::new(PREFIX_POLL, storage); - - let mut read_polls: Vec<(u64, LegacyPoll)> = vec![]; - for item in legacy_polls_bucket.range(None, None, Order::Ascending) { - let (k, p) = item?; - read_polls.push((bytes_to_u64(&k)?, p)); - } - - for (id, _) in read_polls.clone().into_iter() { - legacy_polls_bucket.remove(&id.to_be_bytes()); - } - - let mut new_polls_bucket: Bucket = Bucket::new(PREFIX_POLL, storage); - - for (id, poll) in read_polls.into_iter() { - let new_poll = &Poll { - id: poll.id, - creator: poll.creator, - status: poll.status, - yes_votes: poll.yes_votes, - no_votes: poll.no_votes, - abstain_votes: Uint128::zero(), - end_height: poll.end_height, - title: poll.title, - description: poll.description, - link: poll.link, - execute_data: poll.execute_data, - deposit_amount: poll.deposit_amount, - total_balance_at_end_poll: poll.total_balance_at_end_poll, - voters_reward: Uint128::zero(), - staked_amount: None, - }; - new_polls_bucket.save(&id.to_be_bytes(), new_poll)?; - } - - Ok(()) -} - -#[cfg(test)] -mod migrate_tests { - use super::*; - use crate::state::poll_indexer_store; - use cosmwasm_std::testing::mock_dependencies; - - #[test] - fn test_poll_indexer_migration() { - let mut deps = mock_dependencies(20, &[]); - poll_indexer_old_store(&mut deps.storage, &PollStatus::InProgress) - .save(&1u64.to_be_bytes(), &true) - .unwrap(); - - poll_indexer_old_store(&mut deps.storage, &PollStatus::Executed) - .save(&2u64.to_be_bytes(), &true) - .unwrap(); - - migrate_poll_indexer(&mut deps.storage, &PollStatus::InProgress).unwrap(); - migrate_poll_indexer(&mut deps.storage, &PollStatus::Executed).unwrap(); - migrate_poll_indexer(&mut deps.storage, &PollStatus::Passed).unwrap(); - - assert_eq!( - poll_indexer_store(&mut deps.storage, &PollStatus::InProgress) - .load(&1u64.to_be_bytes()) - .unwrap(), - true - ); - - assert_eq!( - poll_indexer_store(&mut deps.storage, &PollStatus::Executed) - .load(&2u64.to_be_bytes()) - .unwrap(), - true - ); - } - - #[test] - fn test_polls_migration() { - let mut deps = mock_dependencies(20, &[]); - polls_old_store(&mut deps.storage) - .save( - &1u64.to_be_bytes(), - &LegacyPoll { - id: 1u64, - creator: CanonicalAddr::default(), - status: PollStatus::InProgress, - yes_votes: Uint128::zero(), - no_votes: Uint128::zero(), - end_height: 1u64, - title: "test".to_string(), - description: "description".to_string(), - link: None, - execute_data: None, - deposit_amount: Uint128::zero(), - total_balance_at_end_poll: None, - }, - ) - .unwrap(); - polls_old_store(&mut deps.storage) - .save( - &2u64.to_be_bytes(), - &LegacyPoll { - id: 2u64, - creator: CanonicalAddr::default(), - status: PollStatus::InProgress, - yes_votes: Uint128::zero(), - no_votes: Uint128::zero(), - end_height: 1u64, - title: "test2".to_string(), - description: "description".to_string(), - link: None, - execute_data: None, - deposit_amount: Uint128::zero(), - total_balance_at_end_poll: None, - }, - ) - .unwrap(); - - migrate_polls(&mut deps.storage).unwrap(); - - let poll1: Poll = poll_read(&mut deps.storage) - .load(&1u64.to_be_bytes()) - .unwrap(); - assert_eq!( - poll1, - Poll { - id: 1u64, - creator: CanonicalAddr::default(), - status: PollStatus::InProgress, - yes_votes: Uint128::zero(), - no_votes: Uint128::zero(), - end_height: 1u64, - title: "test".to_string(), - description: "description".to_string(), - link: None, - execute_data: None, - deposit_amount: Uint128::zero(), - total_balance_at_end_poll: None, - abstain_votes: Uint128::zero(), - voters_reward: Uint128::zero(), - staked_amount: None, - } - ); - let poll2: Poll = poll_read(&mut deps.storage) - .load(&2u64.to_be_bytes()) - .unwrap(); - assert_eq!( - poll2, - Poll { - id: 2u64, - creator: CanonicalAddr::default(), - status: PollStatus::InProgress, - yes_votes: Uint128::zero(), - no_votes: Uint128::zero(), - end_height: 1u64, - title: "test2".to_string(), - description: "description".to_string(), - link: None, - execute_data: None, - deposit_amount: Uint128::zero(), - total_balance_at_end_poll: None, - abstain_votes: Uint128::zero(), - voters_reward: Uint128::zero(), - staked_amount: None, - } - ); - } - - #[test] - fn test_config_migration() { - let mut deps = mock_dependencies(20, &[]); - let mut legacy_config_store = config_old_store(&mut deps.storage); - legacy_config_store - .save(&LegacyConfig { - mirror_token: CanonicalAddr::default(), - owner: CanonicalAddr::default(), - quorum: Decimal::one(), - threshold: Decimal::one(), - voting_period: 100u64, - effective_delay: 100u64, - expiration_period: 100u64, - proposal_deposit: Uint128(100000u128), - }) - .unwrap(); - - migrate_config(&mut deps.storage, Decimal::percent(50u64), 50u64).unwrap(); - - let config: Config = config_read(&deps.storage).load().unwrap(); - assert_eq!( - config, - Config { - mirror_token: CanonicalAddr::default(), - owner: CanonicalAddr::default(), - quorum: Decimal::one(), - threshold: Decimal::one(), - voting_period: 100u64, - effective_delay: 100u64, - expiration_period: 100u64, - proposal_deposit: Uint128(100000u128), - voter_weight: Decimal::percent(50u64), - snapshot_period: 50u64, - } - ) - } - - #[test] - fn test_state_migration() { - let mut deps = mock_dependencies(20, &[]); - let mut legacy_state_store = state_old_store(&mut deps.storage); - legacy_state_store - .save(&LegacyState { - contract_addr: CanonicalAddr::default(), - poll_count: 0, - total_share: Uint128::zero(), - total_deposit: Uint128::zero(), - }) - .unwrap(); - - migrate_state(&mut deps.storage).unwrap(); - - let state: State = state_read(&deps.storage).load().unwrap(); - assert_eq!( - state, - State { - contract_addr: CanonicalAddr::default(), - poll_count: 0, - total_share: Uint128::zero(), - total_deposit: Uint128::zero(), - pending_voting_rewards: Uint128::zero(), - } - ) - } -} diff --git a/contracts/mirror_gov/src/mock_querier.rs b/contracts/mirror_gov/src/mock_querier.rs deleted file mode 100644 index c17a2f08e..000000000 --- a/contracts/mirror_gov/src/mock_querier.rs +++ /dev/null @@ -1,149 +0,0 @@ -use cosmwasm_std::testing::{MockApi, MockQuerier, MockStorage, MOCK_CONTRACT_ADDR}; -use cosmwasm_std::{ - from_slice, to_binary, Api, CanonicalAddr, Coin, Empty, Extern, HumanAddr, Querier, - QuerierResult, QueryRequest, SystemError, Uint128, WasmQuery, -}; -use cosmwasm_storage::to_length_prefixed; -use std::collections::HashMap; - -/// mock_dependencies is a drop-in replacement for cosmwasm_std::testing::mock_dependencies -/// this uses our CustomQuerier. -pub fn mock_dependencies( - canonical_length: usize, - contract_balance: &[Coin], -) -> Extern { - let contract_addr = HumanAddr::from(MOCK_CONTRACT_ADDR); - let custom_querier: WasmMockQuerier = WasmMockQuerier::new( - MockQuerier::new(&[(&contract_addr, contract_balance)]), - canonical_length, - MockApi::new(canonical_length), - ); - - Extern { - storage: MockStorage::default(), - api: MockApi::new(canonical_length), - querier: custom_querier, - } -} - -pub struct WasmMockQuerier { - base: MockQuerier, - token_querier: TokenQuerier, - canonical_length: usize, -} - -#[derive(Clone, Default)] -pub struct TokenQuerier { - // this lets us iterate over all pairs that match the first string - balances: HashMap>, -} - -impl TokenQuerier { - pub fn new(balances: &[(&HumanAddr, &[(&HumanAddr, &Uint128)])]) -> Self { - TokenQuerier { - balances: balances_to_map(balances), - } - } -} - -pub(crate) fn balances_to_map( - balances: &[(&HumanAddr, &[(&HumanAddr, &Uint128)])], -) -> HashMap> { - let mut balances_map: HashMap> = HashMap::new(); - for (contract_addr, balances) in balances.iter() { - let mut contract_balances_map: HashMap = HashMap::new(); - for (addr, balance) in balances.iter() { - contract_balances_map.insert(HumanAddr::from(addr), **balance); - } - - balances_map.insert(HumanAddr::from(contract_addr), contract_balances_map); - } - balances_map -} - -impl Querier for WasmMockQuerier { - fn raw_query(&self, bin_request: &[u8]) -> QuerierResult { - // MockQuerier doesn't support Custom, so we ignore it completely here - let request: QueryRequest = match from_slice(bin_request) { - Ok(v) => v, - Err(e) => { - return Err(SystemError::InvalidRequest { - error: format!("Parsing query request: {}", e), - request: bin_request.into(), - }) - } - }; - self.handle_query(&request) - } -} - -impl WasmMockQuerier { - pub fn handle_query(&self, request: &QueryRequest) -> QuerierResult { - match &request { - QueryRequest::Wasm(WasmQuery::Raw { contract_addr, key }) => { - let key: &[u8] = key.as_slice(); - - let balances: &HashMap = - match self.token_querier.balances.get(contract_addr) { - Some(balances) => balances, - None => { - return Err(SystemError::InvalidRequest { - error: format!( - "No balance info exists for the contract {}", - contract_addr - ), - request: key.into(), - }) - } - }; - - let prefix_balance = to_length_prefixed(b"balance").to_vec(); - if key[..prefix_balance.len()].to_vec() == prefix_balance { - let key_address: &[u8] = &key[prefix_balance.len()..]; - let address_raw: CanonicalAddr = CanonicalAddr::from(key_address); - - let api: MockApi = MockApi::new(self.canonical_length); - let address: HumanAddr = match api.human_address(&address_raw) { - Ok(v) => v, - Err(e) => { - return Err(SystemError::InvalidRequest { - error: format!("Parsing query request: {}", e), - request: key.into(), - }) - } - }; - - let balance = match balances.get(&address) { - Some(v) => v, - None => { - return Err(SystemError::InvalidRequest { - error: "Balance not found".to_string(), - request: key.into(), - }) - } - }; - - Ok(to_binary(&to_binary(&balance).unwrap())) - } else { - panic!("DO NOT ENTER HERE") - } - } - _ => self.base.handle_query(request), - } - } -} - -impl WasmMockQuerier { - pub fn new(base: MockQuerier, canonical_length: usize, _api: A) -> Self { - WasmMockQuerier { - base, - token_querier: TokenQuerier::default(), - canonical_length, - } - } - - // configure the mint whitelist mock querier - pub fn with_token_balances(&mut self, balances: &[(&HumanAddr, &[(&HumanAddr, &Uint128)])]) { - self.token_querier = TokenQuerier::new(balances); - } -} diff --git a/contracts/mirror_gov/src/querier.rs b/contracts/mirror_gov/src/querier.rs deleted file mode 100644 index dbea6b28a..000000000 --- a/contracts/mirror_gov/src/querier.rs +++ /dev/null @@ -1,33 +0,0 @@ -use cosmwasm_std::{ - from_binary, to_binary, Api, Binary, CanonicalAddr, Extern, HumanAddr, Querier, QueryRequest, - StdResult, Storage, Uint128, WasmQuery, -}; - -use cosmwasm_storage::to_length_prefixed; - -pub fn load_token_balance( - deps: &Extern, - contract_addr: &HumanAddr, - account_addr: &CanonicalAddr, -) -> StdResult { - // load balance form the token contract - let res: Binary = deps - .querier - .query(&QueryRequest::Wasm(WasmQuery::Raw { - contract_addr: HumanAddr::from(contract_addr), - key: Binary::from(concat( - &to_length_prefixed(b"balance").to_vec(), - account_addr.as_slice(), - )), - })) - .unwrap_or_else(|_| to_binary(&Uint128::zero()).unwrap()); - - from_binary(&res) -} - -#[inline] -fn concat(namespace: &[u8], key: &[u8]) -> Vec { - let mut k = namespace.to_vec(); - k.extend_from_slice(key); - k -} diff --git a/contracts/mirror_gov/src/staking.rs b/contracts/mirror_gov/src/staking.rs deleted file mode 100644 index 451828c1e..000000000 --- a/contracts/mirror_gov/src/staking.rs +++ /dev/null @@ -1,430 +0,0 @@ -use crate::querier::load_token_balance; -use crate::state::{ - bank_read, bank_store, config_read, config_store, poll_read, poll_store, poll_voter_read, - poll_voter_store, read_polls, state_read, state_store, Config, Poll, State, TokenManager, -}; - -use cosmwasm_std::{ - log, to_binary, Api, CanonicalAddr, CosmosMsg, Env, Extern, HandleResponse, HandleResult, - HumanAddr, Querier, StdError, StdResult, Storage, Uint128, WasmMsg, -}; -use cw20::Cw20HandleMsg; -use mirror_protocol::gov::{PollStatus, StakerResponse, VoterInfo}; - -pub fn stake_voting_tokens( - deps: &mut Extern, - _env: Env, - sender: HumanAddr, - amount: Uint128, -) -> HandleResult { - if amount.is_zero() { - return Err(StdError::generic_err("Insufficient funds sent")); - } - - let sender_address_raw = deps.api.canonical_address(&sender)?; - let key = &sender_address_raw.as_slice(); - - let mut token_manager = bank_read(&deps.storage).may_load(key)?.unwrap_or_default(); - let config: Config = config_store(&mut deps.storage).load()?; - let mut state: State = state_store(&mut deps.storage).load()?; - - // balance already increased, so subtract deposit amount - let total_locked_balance = state.total_deposit + state.pending_voting_rewards; - let total_balance = (load_token_balance( - &deps, - &deps.api.human_address(&config.mirror_token)?, - &state.contract_addr, - )? - (total_locked_balance + amount))?; - - let share = if total_balance.is_zero() || state.total_share.is_zero() { - amount - } else { - amount.multiply_ratio(state.total_share, total_balance) - }; - - token_manager.share += share; - state.total_share += share; - - state_store(&mut deps.storage).save(&state)?; - bank_store(&mut deps.storage).save(key, &token_manager)?; - - Ok(HandleResponse { - messages: vec![], - data: None, - log: vec![ - log("action", "staking"), - log("sender", sender.as_str()), - log("share", share.to_string()), - log("amount", amount.to_string()), - ], - }) -} - -// Withdraw amount if not staked. By default all funds will be withdrawn. -pub fn withdraw_voting_tokens( - deps: &mut Extern, - env: Env, - amount: Option, -) -> HandleResult { - let sender_address_raw = deps.api.canonical_address(&env.message.sender)?; - let key = sender_address_raw.as_slice(); - - if let Some(mut token_manager) = bank_read(&deps.storage).may_load(key)? { - let config: Config = config_store(&mut deps.storage).load()?; - let mut state: State = state_store(&mut deps.storage).load()?; - - // Load total share & total balance except proposal deposit amount - let total_share = state.total_share.u128(); - let total_locked_balance = state.total_deposit + state.pending_voting_rewards; - let total_balance = (load_token_balance( - &deps, - &deps.api.human_address(&config.mirror_token)?, - &state.contract_addr, - )? - total_locked_balance)? - .u128(); - - let user_locked_balance = compute_locked_balance(deps, &mut token_manager, &&sender_address_raw)?; - let user_locked_share = user_locked_balance * total_share / total_balance; - let user_share = token_manager.share.u128(); - - let withdraw_share = amount - .map(|v| std::cmp::max(v.multiply_ratio(total_share, total_balance).u128(), 1u128)) - .unwrap_or_else(|| user_share - user_locked_share); - let withdraw_amount = amount - .map(|v| v.u128()) - .unwrap_or_else(|| withdraw_share * total_balance / total_share); - - if user_locked_share + withdraw_share > user_share { - Err(StdError::generic_err( - "User is trying to withdraw too many tokens.", - )) - } else { - let share = user_share - withdraw_share; - token_manager.share = Uint128::from(share); - - bank_store(&mut deps.storage).save(key, &token_manager)?; - - state.total_share = Uint128::from(total_share - withdraw_share); - state_store(&mut deps.storage).save(&state)?; - - send_tokens( - &deps.api, - &config.mirror_token, - &sender_address_raw, - withdraw_amount, - "withdraw", - ) - } - } else { - Err(StdError::generic_err("Nothing staked")) - } -} - -// returns the largest locked amount in participated polls. -fn compute_locked_balance( - deps: &mut Extern, - token_manager: &mut TokenManager, - voter: &CanonicalAddr, -) -> StdResult { - // filter out not in-progress polls - token_manager.locked_balance.retain(|(poll_id, _)| { - let poll: Poll = poll_read(&deps.storage) - .load(&poll_id.to_be_bytes()) - .unwrap(); - - // cleanup not needed information, voting info in polls with no rewards - if poll.status != PollStatus::InProgress && poll.voters_reward.is_zero() { - poll_voter_store(&mut deps.storage, *poll_id).remove(&voter.as_slice()); - } - - poll.status == PollStatus::InProgress - }); - - Ok(token_manager - .locked_balance - .iter() - .map(|(_, v)| v.balance.u128()) - .max() - .unwrap_or_default()) -} - -pub fn deposit_reward( - deps: &mut Extern, - _env: Env, - _sender: HumanAddr, - amount: Uint128, -) -> HandleResult { - let config = config_read(&deps.storage).load()?; - - let mut polls_in_progress = read_polls( - &deps.storage, - Some(PollStatus::InProgress), - None, - None, - None, - Some(true), // remove hard cap to get all polls - )?; - - if config.voter_weight.is_zero() || polls_in_progress.is_empty() { - return Ok(HandleResponse { - messages: vec![], - log: vec![ - log("action", "deposit_reward"), - log("amount", amount.to_string()), - ], - data: None, - }); - } - - let voter_rewards = amount * config.voter_weight; - let rewards_per_poll = - voter_rewards.multiply_ratio(Uint128(1), polls_in_progress.len() as u128); - if rewards_per_poll.is_zero() { - return Err(StdError::generic_err("Reward deposited is too small")); - } - for poll in polls_in_progress.iter_mut() { - poll.voters_reward += rewards_per_poll; - poll_store(&mut deps.storage) - .save(&poll.id.to_be_bytes(), &poll) - .unwrap() - } - - state_store(&mut deps.storage).update(|mut state| { - state.pending_voting_rewards += voter_rewards; - Ok(state) - })?; - - Ok(HandleResponse { - messages: vec![], - log: vec![ - log("action", "deposit_reward"), - log("amount", amount.to_string()), - ], - data: None, - }) -} - -pub fn withdraw_voting_rewards( - deps: &mut Extern, - env: Env, -) -> HandleResult { - let config: Config = config_store(&mut deps.storage).load()?; - let sender_address_raw = deps.api.canonical_address(&env.message.sender)?; - let key = sender_address_raw.as_slice(); - - let token_manager = bank_read(&deps.storage) - .load(key) - .or(Err(StdError::generic_err("Nothing staked")))?; - - let user_reward_amount: u128 = - withdraw_user_voting_rewards(&mut deps.storage, &sender_address_raw, &token_manager); - if user_reward_amount.eq(&0u128) { - return Err(StdError::generic_err("Nothing to withdraw")); - } - - state_store(&mut deps.storage).update(|mut state| { - state.pending_voting_rewards = - (state.pending_voting_rewards - Uint128(user_reward_amount))?; - Ok(state) - })?; - - send_tokens( - &deps.api, - &config.mirror_token, - &sender_address_raw, - user_reward_amount, - "withdraw_voting_rewards", - ) -} - -pub fn stake_voting_rewards( - deps: &mut Extern, - env: Env, -) -> HandleResult { - let config: Config = config_store(&mut deps.storage).load()?; - let mut state: State = state_store(&mut deps.storage).load()?; - let sender_address_raw = deps.api.canonical_address(&env.message.sender)?; - let key = sender_address_raw.as_slice(); - - let mut token_manager = bank_read(&deps.storage) - .load(key) - .or(Err(StdError::generic_err("Nothing staked")))?; - - let user_reward_amount: u128 = - withdraw_user_voting_rewards(&mut deps.storage, &sender_address_raw, &token_manager); - if user_reward_amount.eq(&0u128) { - return Err(StdError::generic_err("Nothing to withdraw")); - } - - // add the withdrawn rewards to stake pool and calculate share - let total_locked_balance = state.total_deposit + state.pending_voting_rewards; - let total_balance = (load_token_balance( - &deps, - &deps.api.human_address(&config.mirror_token)?, - &state.contract_addr, - )? - total_locked_balance)?; - - state.pending_voting_rewards = (state.pending_voting_rewards - Uint128(user_reward_amount))?; - - let share: Uint128 = if total_balance.is_zero() || state.total_share.is_zero() { - Uint128(user_reward_amount) - } else { - Uint128(user_reward_amount).multiply_ratio(state.total_share, total_balance) - }; - - token_manager.share += share; - state.total_share += share; - - state_store(&mut deps.storage).save(&state)?; - bank_store(&mut deps.storage).save(key, &token_manager)?; - - Ok(HandleResponse { - messages: vec![], - data: None, - log: vec![ - log("action", "stake_voting_rewards"), - log("staker", env.message.sender.as_str()), - log("share", share.to_string()), - log("amount", user_reward_amount.to_string()), - ], - }) -} - -fn withdraw_user_voting_rewards( - storage: &mut S, - user_address: &CanonicalAddr, - token_manager: &TokenManager, -) -> u128 { - let w_polls: Vec<(Poll, VoterInfo)> = - get_withdrawable_polls(storage, token_manager, user_address); - let user_reward_amount: u128 = w_polls - .iter() - .map(|(poll, voting_info)| { - // remove voter info from the poll - poll_voter_store(storage, poll.id).remove(user_address.as_slice()); - - // calculate reward share - let total_votes = - poll.no_votes.u128() + poll.yes_votes.u128() + poll.abstain_votes.u128(); - let poll_voting_reward = poll - .voters_reward - .multiply_ratio(voting_info.balance, total_votes); - poll_voting_reward.u128() - }) - .sum(); - user_reward_amount -} - -fn get_withdrawable_polls( - storage: &S, - token_manager: &TokenManager, - user_address: &CanonicalAddr, -) -> Vec<(Poll, VoterInfo)> { - let w_polls: Vec<(Poll, VoterInfo)> = token_manager - .locked_balance - .iter() - .map(|(poll_id, _)| { - let poll: Poll = poll_read(storage).load(&poll_id.to_be_bytes()).unwrap(); - let voter_info_res: StdResult = - poll_voter_read(storage, *poll_id).load(&user_address.as_slice()); - (poll, voter_info_res) - }) - .filter(|(poll, voter_info_res)| { - poll.status != PollStatus::InProgress && voter_info_res.is_ok() && !poll.voters_reward.is_zero() - }) - .map(|(poll, voter_info_res)| (poll, voter_info_res.unwrap())) - .collect(); - w_polls -} - -fn send_tokens( - api: &A, - asset_token: &CanonicalAddr, - recipient: &CanonicalAddr, - amount: u128, - action: &str, -) -> HandleResult { - let contract_human = api.human_address(asset_token)?; - let recipient_human = api.human_address(recipient)?; - let log = vec![ - log("action", action), - log("recipient", recipient_human.as_str()), - log("amount", &amount.to_string()), - ]; - - let r = HandleResponse { - messages: vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: contract_human, - msg: to_binary(&Cw20HandleMsg::Transfer { - recipient: recipient_human, - amount: Uint128::from(amount), - })?, - send: vec![], - })], - log, - data: None, - }; - Ok(r) -} - -pub fn query_staker( - deps: &Extern, - address: HumanAddr, -) -> StdResult { - let addr_raw = deps.api.canonical_address(&address).unwrap(); - let config: Config = config_read(&deps.storage).load()?; - let state: State = state_read(&deps.storage).load()?; - let mut token_manager = bank_read(&deps.storage) - .may_load(addr_raw.as_slice())? - .unwrap_or_default(); - - // calculate pending voting rewards - let w_polls: Vec<(Poll, VoterInfo)> = - get_withdrawable_polls(&deps.storage, &token_manager, &addr_raw); - - let mut user_reward_amount = Uint128::zero(); - let w_polls_res: Vec<(u64, Uint128)> = w_polls - .iter() - .map(|(poll, voting_info)| { - // calculate reward share - let total_votes = - poll.no_votes + poll.yes_votes + poll.abstain_votes; - let poll_voting_reward = poll - .voters_reward - .multiply_ratio(voting_info.balance, total_votes); - user_reward_amount += poll_voting_reward; - - (poll.id, poll_voting_reward) - }) - .collect(); - - // filter out not in-progress polls - token_manager.locked_balance.retain(|(poll_id, _)| { - let poll: Poll = poll_read(&deps.storage) - .load(&poll_id.to_be_bytes()) - .unwrap(); - - poll.status == PollStatus::InProgress - }); - - let total_locked_balance = state.total_deposit + state.pending_voting_rewards; - let total_balance = (load_token_balance( - &deps, - &deps.api.human_address(&config.mirror_token)?, - &state.contract_addr, - )? - total_locked_balance)?; - - Ok(StakerResponse { - balance: if !state.total_share.is_zero() { - token_manager - .share - .multiply_ratio(total_balance, state.total_share) - } else { - Uint128::zero() - }, - share: token_manager.share, - locked_balance: token_manager.locked_balance, - pending_voting_rewards: user_reward_amount, - withdrawable_polls: w_polls_res, - }) -} diff --git a/contracts/mirror_gov/src/state.rs b/contracts/mirror_gov/src/state.rs deleted file mode 100644 index a4492eff9..000000000 --- a/contracts/mirror_gov/src/state.rs +++ /dev/null @@ -1,228 +0,0 @@ -use cosmwasm_std::{Binary, CanonicalAddr, Decimal, ReadonlyStorage, StdResult, Storage, Uint128}; -use cosmwasm_storage::{ - bucket, bucket_read, singleton, singleton_read, Bucket, ReadonlyBucket, ReadonlySingleton, - Singleton, -}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use mirror_protocol::common::OrderBy; -use mirror_protocol::gov::{PollStatus, VoterInfo}; - -static KEY_CONFIG: &[u8] = b"config"; -static KEY_STATE: &[u8] = b"state"; - -static PREFIX_POLL_INDEXER: &[u8] = b"poll_indexer"; -static PREFIX_POLL_VOTER: &[u8] = b"poll_voter"; -static PREFIX_POLL: &[u8] = b"poll"; -static PREFIX_BANK: &[u8] = b"bank"; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct Config { - pub owner: CanonicalAddr, - pub mirror_token: CanonicalAddr, - pub quorum: Decimal, - pub threshold: Decimal, - pub voting_period: u64, - pub effective_delay: u64, - pub expiration_period: u64, - pub proposal_deposit: Uint128, - pub voter_weight: Decimal, - pub snapshot_period: u64, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct State { - pub contract_addr: CanonicalAddr, - pub poll_count: u64, - pub total_share: Uint128, - pub total_deposit: Uint128, - pub pending_voting_rewards: Uint128, -} - -#[derive(Default, Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct TokenManager { - pub share: Uint128, // total staked balance - pub locked_balance: Vec<(u64, VoterInfo)>, // maps poll_id to weight voted - pub participated_polls: Vec, // poll_id -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct Poll { - pub id: u64, - pub creator: CanonicalAddr, - pub status: PollStatus, - pub yes_votes: Uint128, - pub no_votes: Uint128, - pub abstain_votes: Uint128, - pub end_height: u64, - pub title: String, - pub description: String, - pub link: Option, - pub execute_data: Option, - pub deposit_amount: Uint128, - /// Total balance at the end poll - pub total_balance_at_end_poll: Option, - pub voters_reward: Uint128, - pub staked_amount: Option, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct ExecuteData { - pub contract: CanonicalAddr, - pub msg: Binary, -} - -pub fn config_store(storage: &mut S) -> Singleton { - singleton(storage, KEY_CONFIG) -} - -pub fn config_read(storage: &S) -> ReadonlySingleton { - singleton_read(storage, KEY_CONFIG) -} - -pub fn state_store(storage: &mut S) -> Singleton { - singleton(storage, KEY_STATE) -} - -pub fn state_read(storage: &S) -> ReadonlySingleton { - singleton_read(storage, KEY_STATE) -} - -pub fn poll_store(storage: &mut S) -> Bucket { - bucket(PREFIX_POLL, storage) -} - -pub fn poll_read(storage: &S) -> ReadonlyBucket { - bucket_read(PREFIX_POLL, storage) -} - -pub fn poll_indexer_store<'a, S: Storage>( - storage: &'a mut S, - status: &PollStatus, -) -> Bucket<'a, S, bool> { - Bucket::multilevel( - &[PREFIX_POLL_INDEXER, status.to_string().as_bytes()], - storage, - ) -} - -pub fn poll_voter_store(storage: &mut S, poll_id: u64) -> Bucket { - Bucket::multilevel(&[PREFIX_POLL_VOTER, &poll_id.to_be_bytes()], storage) -} - -pub fn poll_voter_read( - storage: &S, - poll_id: u64, -) -> ReadonlyBucket { - ReadonlyBucket::multilevel(&[PREFIX_POLL_VOTER, &poll_id.to_be_bytes()], storage) -} - -pub fn read_poll_voters<'a, S: ReadonlyStorage>( - storage: &'a S, - poll_id: u64, - start_after: Option, - limit: Option, - order_by: Option, -) -> StdResult> { - let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - let (start, end, order_by) = match order_by { - Some(OrderBy::Asc) => (calc_range_start_addr(start_after), None, OrderBy::Asc), - _ => (None, calc_range_end_addr(start_after), OrderBy::Desc), - }; - - let voters: ReadonlyBucket<'a, S, VoterInfo> = - ReadonlyBucket::multilevel(&[PREFIX_POLL_VOTER, &poll_id.to_be_bytes()], storage); - voters - .range(start.as_deref(), end.as_deref(), order_by.into()) - .take(limit) - .map(|item| { - let (k, v) = item?; - Ok((CanonicalAddr::from(k), v)) - }) - .collect() -} - -const MAX_LIMIT: u32 = 30; -const DEFAULT_LIMIT: u32 = 10; -pub fn read_polls<'a, S: ReadonlyStorage>( - storage: &'a S, - filter: Option, - start_after: Option, - limit: Option, - order_by: Option, - remove_hard_cap: Option, -) -> StdResult> { - let mut limit: usize = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - if let Some(remove_hard_cap) = remove_hard_cap { - if remove_hard_cap { - limit = usize::MAX; - } - } - let (start, end, order_by) = match order_by { - Some(OrderBy::Asc) => (calc_range_start(start_after), None, OrderBy::Asc), - _ => (None, calc_range_end(start_after), OrderBy::Desc), - }; - - if let Some(status) = filter { - let poll_indexer: ReadonlyBucket<'a, S, bool> = ReadonlyBucket::multilevel( - &[PREFIX_POLL_INDEXER, status.to_string().as_bytes()], - storage, - ); - poll_indexer - .range(start.as_deref(), end.as_deref(), order_by.into()) - .take(limit) - .map(|item| { - let (k, _) = item?; - poll_read(storage).load(&k) - }) - .collect() - } else { - let polls: ReadonlyBucket<'a, S, Poll> = ReadonlyBucket::new(PREFIX_POLL, storage); - - polls - .range(start.as_deref(), end.as_deref(), order_by.into()) - .take(limit) - .map(|item| { - let (_, v) = item?; - Ok(v) - }) - .collect() - } -} - -pub fn bank_store(storage: &mut S) -> Bucket { - bucket(PREFIX_BANK, storage) -} - -pub fn bank_read(storage: &S) -> ReadonlyBucket { - bucket_read(PREFIX_BANK, storage) -} - -// this will set the first key after the provided key, by appending a 1 byte -fn calc_range_start(start_after: Option) -> Option> { - start_after.map(|id| { - let mut v = id.to_be_bytes().to_vec(); - v.push(1); - v - }) -} - -// this will set the first key after the provided key, by appending a 1 byte -fn calc_range_end(start_after: Option) -> Option> { - start_after.map(|id| id.to_be_bytes().to_vec()) -} - -// this will set the first key after the provided key, by appending a 1 byte -fn calc_range_start_addr(start_after: Option) -> Option> { - start_after.map(|addr| { - let mut v = addr.as_slice().to_vec(); - v.push(1); - v - }) -} - -// this will set the first key after the provided key, by appending a 1 byte -fn calc_range_end_addr(start_after: Option) -> Option> { - start_after.map(|addr| addr.as_slice().to_vec()) -} diff --git a/contracts/mirror_gov/src/tests.rs b/contracts/mirror_gov/src/tests.rs deleted file mode 100644 index 4d19c8f4b..000000000 --- a/contracts/mirror_gov/src/tests.rs +++ /dev/null @@ -1,3968 +0,0 @@ -use crate::contract::{handle, init, query}; -use crate::mock_querier::{mock_dependencies, WasmMockQuerier}; -use crate::querier::load_token_balance; -use crate::state::{ - bank_read, bank_store, config_read, poll_indexer_store, poll_store, poll_voter_read, - poll_voter_store, state_read, Config, Poll, State, TokenManager, -}; - -use cosmwasm_std::testing::{mock_env, MockApi, MockStorage, MOCK_CONTRACT_ADDR}; -use cosmwasm_std::{ - coins, from_binary, log, to_binary, Api, CanonicalAddr, Coin, CosmosMsg, Decimal, Env, Extern, - HandleResponse, HumanAddr, StdError, Uint128, WasmMsg, -}; -use cw20::{Cw20HandleMsg, Cw20ReceiveMsg}; -use mirror_protocol::common::OrderBy; -use mirror_protocol::gov::{ - ConfigResponse, Cw20HookMsg, ExecuteMsg, HandleMsg, InitMsg, PollResponse, PollStatus, - PollsResponse, QueryMsg, StakerResponse, StateResponse, VoteOption, VoterInfo, VotersResponse, - VotersResponseItem, -}; - -const VOTING_TOKEN: &str = "voting_token"; -const TEST_CREATOR: &str = "creator"; -const TEST_VOTER: &str = "voter1"; -const TEST_VOTER_2: &str = "voter2"; -const TEST_VOTER_3: &str = "voter3"; -const TEST_COLLECTOR: &str = "collector"; -const DEFAULT_QUORUM: u64 = 30u64; -const DEFAULT_THRESHOLD: u64 = 50u64; -const DEFAULT_VOTING_PERIOD: u64 = 10000u64; -const DEFAULT_EFFECTIVE_DELAY: u64 = 10000u64; -const DEFAULT_EXPIRATION_PERIOD: u64 = 20000u64; -const DEFAULT_PROPOSAL_DEPOSIT: u128 = 10000000000u128; -const DEFAULT_VOTER_WEIGHT: Decimal = Decimal::zero(); -const DEFAULT_SNAPSHOT_PERIOD: u64 = 10u64; - -fn mock_init(mut deps: &mut Extern) { - let msg = InitMsg { - mirror_token: HumanAddr::from(VOTING_TOKEN), - quorum: Decimal::percent(DEFAULT_QUORUM), - threshold: Decimal::percent(DEFAULT_THRESHOLD), - voting_period: DEFAULT_VOTING_PERIOD, - effective_delay: DEFAULT_EFFECTIVE_DELAY, - expiration_period: DEFAULT_EXPIRATION_PERIOD, - proposal_deposit: Uint128(DEFAULT_PROPOSAL_DEPOSIT), - voter_weight: DEFAULT_VOTER_WEIGHT, - snapshot_period: DEFAULT_SNAPSHOT_PERIOD, - }; - - let env = mock_env(TEST_CREATOR, &[]); - let _res = init(&mut deps, env, msg).expect("contract successfully handles InitMsg"); -} - -fn mock_env_height(sender: &str, sent: &[Coin], height: u64, time: u64) -> Env { - let mut env = mock_env(sender, sent); - env.block.height = height; - env.block.time = time; - env -} - -fn init_msg() -> InitMsg { - InitMsg { - mirror_token: HumanAddr::from(VOTING_TOKEN), - quorum: Decimal::percent(DEFAULT_QUORUM), - threshold: Decimal::percent(DEFAULT_THRESHOLD), - voting_period: DEFAULT_VOTING_PERIOD, - effective_delay: DEFAULT_EFFECTIVE_DELAY, - expiration_period: DEFAULT_EXPIRATION_PERIOD, - proposal_deposit: Uint128(DEFAULT_PROPOSAL_DEPOSIT), - voter_weight: DEFAULT_VOTER_WEIGHT, - snapshot_period: DEFAULT_SNAPSHOT_PERIOD, - } -} - -#[test] -fn proper_initialization() { - let mut deps = mock_dependencies(20, &[]); - - let msg = init_msg(); - let env = mock_env(TEST_CREATOR, &coins(2, VOTING_TOKEN)); - let res = init(&mut deps, env, msg).unwrap(); - assert_eq!(0, res.messages.len()); - - let config: Config = config_read(&mut deps.storage).load().unwrap(); - assert_eq!( - config, - Config { - mirror_token: deps - .api - .canonical_address(&HumanAddr::from(VOTING_TOKEN)) - .unwrap(), - owner: deps - .api - .canonical_address(&HumanAddr::from(TEST_CREATOR)) - .unwrap(), - quorum: Decimal::percent(DEFAULT_QUORUM), - threshold: Decimal::percent(DEFAULT_THRESHOLD), - voting_period: DEFAULT_VOTING_PERIOD, - effective_delay: DEFAULT_EFFECTIVE_DELAY, - expiration_period: DEFAULT_EXPIRATION_PERIOD, - proposal_deposit: Uint128(DEFAULT_PROPOSAL_DEPOSIT), - voter_weight: DEFAULT_VOTER_WEIGHT, - snapshot_period: DEFAULT_SNAPSHOT_PERIOD, - } - ); - - let state: State = state_read(&mut deps.storage).load().unwrap(); - assert_eq!( - state, - State { - contract_addr: deps - .api - .canonical_address(&HumanAddr::from(MOCK_CONTRACT_ADDR)) - .unwrap(), - poll_count: 0, - total_share: Uint128::zero(), - total_deposit: Uint128::zero(), - pending_voting_rewards: Uint128::zero(), - } - ); -} - -#[test] -fn poll_not_found() { - let mut deps = mock_dependencies(20, &[]); - mock_init(&mut deps); - - let res = query(&deps, QueryMsg::Poll { poll_id: 1 }); - - match res { - Err(StdError::GenericErr { msg, .. }) => assert_eq!(msg, "Poll does not exist"), - Err(e) => panic!("Unexpected error: {:?}", e), - _ => panic!("Must return error"), - } -} - -#[test] -fn fails_create_poll_invalid_quorum() { - let mut deps = mock_dependencies(20, &[]); - let env = mock_env("voter", &coins(11, VOTING_TOKEN)); - let msg = InitMsg { - mirror_token: HumanAddr::from(VOTING_TOKEN), - quorum: Decimal::percent(101), - threshold: Decimal::percent(DEFAULT_THRESHOLD), - voting_period: DEFAULT_VOTING_PERIOD, - effective_delay: DEFAULT_EFFECTIVE_DELAY, - expiration_period: DEFAULT_EXPIRATION_PERIOD, - proposal_deposit: Uint128(DEFAULT_PROPOSAL_DEPOSIT), - voter_weight: DEFAULT_VOTER_WEIGHT, - snapshot_period: DEFAULT_SNAPSHOT_PERIOD, - }; - - let res = init(&mut deps, env, msg); - - match res { - Ok(_) => panic!("Must return error"), - Err(StdError::GenericErr { msg, .. }) => assert_eq!(msg, "quorum must be 0 to 1"), - Err(e) => panic!("Unexpected error: {:?}", e), - } -} - -#[test] -fn fails_create_poll_invalid_threshold() { - let mut deps = mock_dependencies(20, &[]); - let env = mock_env("voter", &coins(11, VOTING_TOKEN)); - let msg = InitMsg { - mirror_token: HumanAddr::from(VOTING_TOKEN), - quorum: Decimal::percent(DEFAULT_QUORUM), - threshold: Decimal::percent(101), - voting_period: DEFAULT_VOTING_PERIOD, - effective_delay: DEFAULT_EFFECTIVE_DELAY, - expiration_period: DEFAULT_EXPIRATION_PERIOD, - proposal_deposit: Uint128(DEFAULT_PROPOSAL_DEPOSIT), - voter_weight: DEFAULT_VOTER_WEIGHT, - snapshot_period: DEFAULT_SNAPSHOT_PERIOD, - }; - - let res = init(&mut deps, env, msg); - - match res { - Ok(_) => panic!("Must return error"), - Err(StdError::GenericErr { msg, .. }) => assert_eq!(msg, "threshold must be 0 to 1"), - Err(e) => panic!("Unexpected error: {:?}", e), - } -} - -#[test] -fn fails_create_poll_invalid_title() { - let mut deps = mock_dependencies(20, &[]); - mock_init(&mut deps); - - let msg = create_poll_msg("a".to_string(), "test".to_string(), None, None); - let env = mock_env(VOTING_TOKEN, &vec![]); - match handle(&mut deps, env.clone(), msg) { - Ok(_) => panic!("Must return error"), - Err(StdError::GenericErr { msg, .. }) => assert_eq!(msg, "Title too short"), - Err(_) => panic!("Unknown error"), - } - - let msg = create_poll_msg( - "0123456789012345678901234567890123456789012345678901234567890123401234567890123456789012345678901234567890123456789012345678901234012345678901234567890123456789012345678901234567890123456789012340123456789012345678901234567890123456789012345678901234567890123401234567890123456789012345678901234567890123456789012345678901234".to_string(), - "test".to_string(), - None, - None, - ); - - match handle(&mut deps, env.clone(), msg) { - Ok(_) => panic!("Must return error"), - Err(StdError::GenericErr { msg, .. }) => assert_eq!(msg, "Title too long"), - Err(_) => panic!("Unknown error"), - } -} - -#[test] -fn fails_create_poll_invalid_description() { - let mut deps = mock_dependencies(20, &[]); - mock_init(&mut deps); - - let msg = create_poll_msg("test".to_string(), "a".to_string(), None, None); - let env = mock_env(VOTING_TOKEN, &vec![]); - match handle(&mut deps, env.clone(), msg) { - Ok(_) => panic!("Must return error"), - Err(StdError::GenericErr { msg, .. }) => assert_eq!(msg, "Description too short"), - Err(_) => panic!("Unknown error"), - } - - let msg = create_poll_msg( - "test".to_string(), - "0123456789012345678901234567890123456789012345678901234567890123401234567890123456789012345678901234567890123456789012345678901234012345678901234567890123456789012345678901234567890123456789012340123456789012345678901234567890123456789012345678901234567890123401234567890123456789012345678901234567890123456789012345678901234".to_string(), - None, - None, - ); - - match handle(&mut deps, env.clone(), msg) { - Ok(_) => panic!("Must return error"), - Err(StdError::GenericErr { msg, .. }) => assert_eq!(msg, "Description too long"), - Err(_) => panic!("Unknown error"), - } -} - -#[test] -fn fails_create_poll_invalid_link() { - let mut deps = mock_dependencies(20, &[]); - mock_init(&mut deps); - - let msg = create_poll_msg( - "test".to_string(), - "test".to_string(), - Some("http://hih".to_string()), - None, - ); - let env = mock_env(VOTING_TOKEN, &vec![]); - match handle(&mut deps, env.clone(), msg) { - Ok(_) => panic!("Must return error"), - Err(StdError::GenericErr { msg, .. }) => assert_eq!(msg, "Link too short"), - Err(_) => panic!("Unknown error"), - } - - let msg = create_poll_msg( - "test".to_string(), - "test".to_string(), - Some("0123456789012345678901234567890123456789012345678901234567890123401234567890123456789012345678901234567890123456789012345678901234012345678901234567890123456789012345678901234567890123456789012340123456789012345678901234567890123456789012345678901234567890123401234567890123456789012345678901234567890123456789012345678901234".to_string()), - None, - ); - - match handle(&mut deps, env.clone(), msg) { - Ok(_) => panic!("Must return error"), - Err(StdError::GenericErr { msg, .. }) => assert_eq!(msg, "Link too long"), - Err(_) => panic!("Unknown error"), - } -} - -#[test] -fn fails_create_poll_invalid_deposit() { - let mut deps = mock_dependencies(20, &[]); - mock_init(&mut deps); - - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(TEST_CREATOR), - amount: Uint128(DEFAULT_PROPOSAL_DEPOSIT - 1), - msg: Some( - to_binary(&Cw20HookMsg::CreatePoll { - title: "TESTTEST".to_string(), - description: "TESTTEST".to_string(), - link: None, - execute_msg: None, - }) - .unwrap(), - ), - }); - let env = mock_env(VOTING_TOKEN, &vec![]); - match handle(&mut deps, env.clone(), msg) { - Ok(_) => panic!("Must return error"), - Err(StdError::GenericErr { msg, .. }) => assert_eq!( - msg, - format!("Must deposit more than {} token", DEFAULT_PROPOSAL_DEPOSIT) - ), - Err(_) => panic!("Unknown error"), - } -} - -fn create_poll_msg( - title: String, - description: String, - link: Option, - execute_msg: Option, -) -> HandleMsg { - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(TEST_CREATOR), - amount: Uint128(DEFAULT_PROPOSAL_DEPOSIT), - msg: Some( - to_binary(&Cw20HookMsg::CreatePoll { - title, - description, - link, - execute_msg, - }) - .unwrap(), - ), - }); - msg -} - -#[test] -fn happy_days_create_poll() { - let mut deps = mock_dependencies(20, &[]); - mock_init(&mut deps); - let env = mock_env_height(VOTING_TOKEN, &vec![], 0, 10000); - - let msg = create_poll_msg("test".to_string(), "test".to_string(), None, None); - - let handle_res = handle(&mut deps, env.clone(), msg.clone()).unwrap(); - assert_create_poll_result( - 1, - env.block.height + DEFAULT_VOTING_PERIOD, - TEST_CREATOR, - handle_res, - &mut deps, - ); -} - -#[test] -fn query_polls() { - let mut deps = mock_dependencies(20, &[]); - mock_init(&mut deps); - let env = mock_env_height(VOTING_TOKEN, &vec![], 0, 10000); - - let msg = create_poll_msg( - "test".to_string(), - "test".to_string(), - Some("http://google.com".to_string()), - None, - ); - let _handle_res = handle(&mut deps, env.clone(), msg.clone()).unwrap(); - let msg = create_poll_msg("test2".to_string(), "test2".to_string(), None, None); - let _handle_res = handle(&mut deps, env.clone(), msg.clone()).unwrap(); - - let res = query( - &deps, - QueryMsg::Polls { - filter: None, - start_after: None, - limit: None, - order_by: Some(OrderBy::Asc), - }, - ) - .unwrap(); - let response: PollsResponse = from_binary(&res).unwrap(); - assert_eq!( - response.polls, - vec![ - PollResponse { - id: 1u64, - creator: HumanAddr::from(TEST_CREATOR), - status: PollStatus::InProgress, - end_height: 10000u64, - title: "test".to_string(), - description: "test".to_string(), - link: Some("http://google.com".to_string()), - deposit_amount: Uint128(DEFAULT_PROPOSAL_DEPOSIT), - execute_data: None, - yes_votes: Uint128::zero(), - no_votes: Uint128::zero(), - total_balance_at_end_poll: None, - voters_reward: Uint128::zero(), - abstain_votes: Uint128::zero(), - staked_amount: None, - }, - PollResponse { - id: 2u64, - creator: HumanAddr::from(TEST_CREATOR), - status: PollStatus::InProgress, - end_height: 10000u64, - title: "test2".to_string(), - description: "test2".to_string(), - link: None, - deposit_amount: Uint128(DEFAULT_PROPOSAL_DEPOSIT), - execute_data: None, - yes_votes: Uint128::zero(), - no_votes: Uint128::zero(), - total_balance_at_end_poll: None, - voters_reward: Uint128::zero(), - abstain_votes: Uint128::zero(), - staked_amount: None, - }, - ] - ); - - let res = query( - &deps, - QueryMsg::Polls { - filter: None, - start_after: Some(1u64), - limit: None, - order_by: Some(OrderBy::Asc), - }, - ) - .unwrap(); - let response: PollsResponse = from_binary(&res).unwrap(); - assert_eq!( - response.polls, - vec![PollResponse { - id: 2u64, - creator: HumanAddr::from(TEST_CREATOR), - status: PollStatus::InProgress, - end_height: 10000u64, - title: "test2".to_string(), - description: "test2".to_string(), - link: None, - deposit_amount: Uint128(DEFAULT_PROPOSAL_DEPOSIT), - execute_data: None, - yes_votes: Uint128::zero(), - no_votes: Uint128::zero(), - total_balance_at_end_poll: None, - voters_reward: Uint128::zero(), - abstain_votes: Uint128::zero(), - staked_amount: None, - },] - ); - - let res = query( - &deps, - QueryMsg::Polls { - filter: None, - start_after: Some(2u64), - limit: None, - order_by: Some(OrderBy::Desc), - }, - ) - .unwrap(); - let response: PollsResponse = from_binary(&res).unwrap(); - assert_eq!( - response.polls, - vec![PollResponse { - id: 1u64, - creator: HumanAddr::from(TEST_CREATOR), - status: PollStatus::InProgress, - end_height: 10000u64, - title: "test".to_string(), - description: "test".to_string(), - link: Some("http://google.com".to_string()), - deposit_amount: Uint128(DEFAULT_PROPOSAL_DEPOSIT), - execute_data: None, - yes_votes: Uint128::zero(), - no_votes: Uint128::zero(), - total_balance_at_end_poll: None, - voters_reward: Uint128::zero(), - abstain_votes: Uint128::zero(), - staked_amount: None, - }] - ); - - let res = query( - &deps, - QueryMsg::Polls { - filter: Some(PollStatus::InProgress), - start_after: Some(1u64), - limit: None, - order_by: Some(OrderBy::Asc), - }, - ) - .unwrap(); - let response: PollsResponse = from_binary(&res).unwrap(); - assert_eq!( - response.polls, - vec![PollResponse { - id: 2u64, - creator: HumanAddr::from(TEST_CREATOR), - status: PollStatus::InProgress, - end_height: 10000u64, - title: "test2".to_string(), - description: "test2".to_string(), - link: None, - deposit_amount: Uint128(DEFAULT_PROPOSAL_DEPOSIT), - execute_data: None, - yes_votes: Uint128::zero(), - no_votes: Uint128::zero(), - total_balance_at_end_poll: None, - voters_reward: Uint128::zero(), - abstain_votes: Uint128::zero(), - staked_amount: None, - },] - ); - - let res = query( - &deps, - QueryMsg::Polls { - filter: Some(PollStatus::Passed), - start_after: None, - limit: None, - order_by: None, - }, - ) - .unwrap(); - let response: PollsResponse = from_binary(&res).unwrap(); - assert_eq!(response.polls, vec![]); -} - -#[test] -fn create_poll_no_quorum() { - let mut deps = mock_dependencies(20, &[]); - mock_init(&mut deps); - let env = mock_env_height(VOTING_TOKEN, &vec![], 0, 10000); - - let msg = create_poll_msg("test".to_string(), "test".to_string(), None, None); - - let handle_res = handle(&mut deps, env, msg.clone()).unwrap(); - assert_create_poll_result( - 1, - DEFAULT_VOTING_PERIOD, - TEST_CREATOR, - handle_res, - &mut deps, - ); -} - -#[test] -fn fails_end_poll_before_end_height() { - let mut deps = mock_dependencies(20, &[]); - mock_init(&mut deps); - let env = mock_env_height(VOTING_TOKEN, &vec![], 0, 10000); - - let msg = create_poll_msg("test".to_string(), "test".to_string(), None, None); - - let handle_res = handle(&mut deps, env.clone(), msg.clone()).unwrap(); - assert_create_poll_result( - 1, - DEFAULT_VOTING_PERIOD, - TEST_CREATOR, - handle_res, - &mut deps, - ); - - let res = query(&deps, QueryMsg::Poll { poll_id: 1 }).unwrap(); - let value: PollResponse = from_binary(&res).unwrap(); - assert_eq!(DEFAULT_VOTING_PERIOD, value.end_height); - - let msg = HandleMsg::EndPoll { poll_id: 1 }; - let env = mock_env_height(TEST_CREATOR, &vec![], 0, 10000); - let handle_res = handle(&mut deps, env, msg); - - match handle_res { - Ok(_) => panic!("Must return error"), - Err(StdError::GenericErr { msg, .. }) => assert_eq!(msg, "Voting period has not expired"), - Err(e) => panic!("Unexpected error: {:?}", e), - } -} - -#[test] -fn happy_days_end_poll() { - const POLL_START_HEIGHT: u64 = 1000; - const POLL_ID: u64 = 1; - let stake_amount = 1000; - - let mut deps = mock_dependencies(20, &coins(1000, VOTING_TOKEN)); - mock_init(&mut deps); - let mut creator_env = mock_env_height( - VOTING_TOKEN, - &coins(2, VOTING_TOKEN), - POLL_START_HEIGHT, - 10000, - ); - - let exec_msg_bz = to_binary(&Cw20HandleMsg::Burn { - amount: Uint128(123), - }) - .unwrap(); - let msg = create_poll_msg( - "test".to_string(), - "test".to_string(), - None, - Some(ExecuteMsg { - contract: HumanAddr::from(VOTING_TOKEN), - msg: exec_msg_bz.clone(), - }), - ); - - let handle_res = handle(&mut deps, creator_env.clone(), msg).unwrap(); - - assert_create_poll_result( - 1, - creator_env.block.height + DEFAULT_VOTING_PERIOD, - TEST_CREATOR, - handle_res, - &mut deps, - ); - - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128((stake_amount + DEFAULT_PROPOSAL_DEPOSIT) as u128), - )], - )]); - - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(TEST_VOTER), - amount: Uint128::from(stake_amount as u128), - msg: Some(to_binary(&Cw20HookMsg::StakeVotingTokens {}).unwrap()), - }); - - let env = mock_env(VOTING_TOKEN, &[]); - let handle_res = handle(&mut deps, env, msg.clone()).unwrap(); - assert_stake_tokens_result( - stake_amount, - DEFAULT_PROPOSAL_DEPOSIT, - stake_amount, - 1, - handle_res, - &mut deps, - ); - - let msg = HandleMsg::CastVote { - poll_id: 1, - vote: VoteOption::Yes, - amount: Uint128::from(stake_amount), - }; - let env = mock_env_height(TEST_VOTER, &[], POLL_START_HEIGHT, 10000); - let handle_res = handle(&mut deps, env, msg).unwrap(); - - assert_eq!( - handle_res.log, - vec![ - log("action", "cast_vote"), - log("poll_id", POLL_ID), - log("amount", "1000"), - log("voter", TEST_VOTER), - log("vote_option", "yes"), - ] - ); - - // not in passed status - let msg = HandleMsg::ExecutePoll { poll_id: 1 }; - let handle_res = handle(&mut deps, creator_env.clone(), msg).unwrap_err(); - match handle_res { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Poll is not in passed status"), - _ => panic!("DO NOT ENTER HERE"), - } - - creator_env.message.sender = HumanAddr::from(TEST_CREATOR); - creator_env.block.height = &creator_env.block.height + DEFAULT_VOTING_PERIOD; - - let msg = HandleMsg::EndPoll { poll_id: 1 }; - let handle_res = handle(&mut deps, creator_env.clone(), msg).unwrap(); - - assert_eq!( - handle_res.log, - vec![ - log("action", "end_poll"), - log("poll_id", "1"), - log("rejected_reason", ""), - log("passed", "true"), - ] - ); - assert_eq!( - handle_res.messages, - vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from(VOTING_TOKEN), - msg: to_binary(&Cw20HandleMsg::Transfer { - recipient: HumanAddr::from(TEST_CREATOR), - amount: Uint128(DEFAULT_PROPOSAL_DEPOSIT), - }) - .unwrap(), - send: vec![], - })] - ); - - // End poll will withdraw deposit balance - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128(stake_amount as u128), - )], - )]); - - // effective delay has not expired - let msg = HandleMsg::ExecutePoll { poll_id: 1 }; - let handle_res = handle(&mut deps, creator_env.clone(), msg).unwrap_err(); - match handle_res { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Effective delay has not expired"), - _ => panic!("DO NOT ENTER HERE"), - } - - creator_env.block.height = &creator_env.block.height + DEFAULT_EFFECTIVE_DELAY; - let msg = HandleMsg::ExecutePoll { poll_id: 1 }; - let handle_res = handle(&mut deps, creator_env, msg).unwrap(); - assert_eq!( - handle_res.messages, - vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from(VOTING_TOKEN), - msg: exec_msg_bz, - send: vec![], - }),] - ); - assert_eq!( - handle_res.log, - vec![log("action", "execute_poll"), log("poll_id", "1"),] - ); - - // Query executed polls - let res = query( - &deps, - QueryMsg::Polls { - filter: Some(PollStatus::Passed), - start_after: None, - limit: None, - order_by: None, - }, - ) - .unwrap(); - let response: PollsResponse = from_binary(&res).unwrap(); - assert_eq!(response.polls.len(), 0); - - let res = query( - &deps, - QueryMsg::Polls { - filter: Some(PollStatus::InProgress), - start_after: None, - limit: None, - order_by: None, - }, - ) - .unwrap(); - let response: PollsResponse = from_binary(&res).unwrap(); - assert_eq!(response.polls.len(), 0); - - let res = query( - &deps, - QueryMsg::Polls { - filter: Some(PollStatus::Executed), - start_after: None, - limit: None, - order_by: Some(OrderBy::Desc), - }, - ) - .unwrap(); - let response: PollsResponse = from_binary(&res).unwrap(); - assert_eq!(response.polls.len(), 1); - - // voter info must be deleted - let res = query( - &deps, - QueryMsg::Voters { - poll_id: 1u64, - start_after: None, - limit: None, - order_by: None, - }, - ) - .unwrap(); - let response: VotersResponse = from_binary(&res).unwrap(); - assert_eq!(response.voters.len(), 0); - - // staker locked token must disappeared - let res = query( - &deps, - QueryMsg::Staker { - address: HumanAddr::from(TEST_VOTER), - }, - ) - .unwrap(); - let response: StakerResponse = from_binary(&res).unwrap(); - assert_eq!( - response, - StakerResponse { - balance: Uint128(stake_amount), - share: Uint128(stake_amount), - locked_balance: vec![], - pending_voting_rewards: Uint128::zero(), - withdrawable_polls: vec![], - } - ); -} - -#[test] -fn expire_poll() { - const POLL_START_HEIGHT: u64 = 1000; - const POLL_ID: u64 = 1; - let stake_amount = 1000; - - let mut deps = mock_dependencies(20, &coins(1000, VOTING_TOKEN)); - mock_init(&mut deps); - let mut creator_env = mock_env_height( - VOTING_TOKEN, - &coins(2, VOTING_TOKEN), - POLL_START_HEIGHT, - 10000, - ); - - let exec_msg_bz = to_binary(&Cw20HandleMsg::Burn { - amount: Uint128(123), - }) - .unwrap(); - let msg = create_poll_msg( - "test".to_string(), - "test".to_string(), - None, - Some(ExecuteMsg { - contract: HumanAddr::from(VOTING_TOKEN), - msg: exec_msg_bz.clone(), - }), - ); - - let handle_res = handle(&mut deps, creator_env.clone(), msg).unwrap(); - - assert_create_poll_result( - 1, - creator_env.block.height + DEFAULT_VOTING_PERIOD, - TEST_CREATOR, - handle_res, - &mut deps, - ); - - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128((stake_amount + DEFAULT_PROPOSAL_DEPOSIT) as u128), - )], - )]); - - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(TEST_VOTER), - amount: Uint128::from(stake_amount as u128), - msg: Some(to_binary(&Cw20HookMsg::StakeVotingTokens {}).unwrap()), - }); - - let env = mock_env(VOTING_TOKEN, &[]); - let handle_res = handle(&mut deps, env, msg.clone()).unwrap(); - assert_stake_tokens_result( - stake_amount, - DEFAULT_PROPOSAL_DEPOSIT, - stake_amount, - 1, - handle_res, - &mut deps, - ); - - let msg = HandleMsg::CastVote { - poll_id: 1, - vote: VoteOption::Yes, - amount: Uint128::from(stake_amount), - }; - let env = mock_env_height(TEST_VOTER, &[], POLL_START_HEIGHT, 10000); - let handle_res = handle(&mut deps, env, msg).unwrap(); - - assert_eq!( - handle_res.log, - vec![ - log("action", "cast_vote"), - log("poll_id", POLL_ID), - log("amount", "1000"), - log("voter", TEST_VOTER), - log("vote_option", "yes"), - ] - ); - - // Poll is not in passed status - creator_env.block.height = &creator_env.block.height + DEFAULT_EFFECTIVE_DELAY; - let msg = HandleMsg::ExpirePoll { poll_id: 1 }; - let handle_res = handle(&mut deps, creator_env.clone(), msg); - match handle_res { - Err(StdError::GenericErr { msg, .. }) => assert_eq!(msg, "Poll is not in passed status"), - _ => panic!("DO NOT ENTER HERE"), - } - - let msg = HandleMsg::EndPoll { poll_id: 1 }; - let handle_res = handle(&mut deps, creator_env.clone(), msg).unwrap(); - - assert_eq!( - handle_res.log, - vec![ - log("action", "end_poll"), - log("poll_id", "1"), - log("rejected_reason", ""), - log("passed", "true"), - ] - ); - assert_eq!( - handle_res.messages, - vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from(VOTING_TOKEN), - msg: to_binary(&Cw20HandleMsg::Transfer { - recipient: HumanAddr::from(TEST_CREATOR), - amount: Uint128(DEFAULT_PROPOSAL_DEPOSIT), - }) - .unwrap(), - send: vec![], - })] - ); - - // Expiration period has not been passed - let msg = HandleMsg::ExpirePoll { poll_id: 1 }; - let handle_res = handle(&mut deps, creator_env.clone(), msg); - match handle_res { - Err(StdError::GenericErr { msg, .. }) => { - assert_eq!(msg, "Expire height has not been reached") - } - _ => panic!("DO NOT ENTER HERE"), - } - - creator_env.block.height = &creator_env.block.height + DEFAULT_EXPIRATION_PERIOD; - let msg = HandleMsg::ExpirePoll { poll_id: 1 }; - let _handle_res = handle(&mut deps, creator_env.clone(), msg).unwrap(); - - let res = query(&deps, QueryMsg::Poll { poll_id: 1 }).unwrap(); - let poll_res: PollResponse = from_binary(&res).unwrap(); - assert_eq!(poll_res.status, PollStatus::Expired); - - let res = query( - &deps, - QueryMsg::Polls { - filter: Some(PollStatus::Expired), - start_after: None, - limit: None, - order_by: Some(OrderBy::Desc), - }, - ) - .unwrap(); - let polls_res: PollsResponse = from_binary(&res).unwrap(); - assert_eq!(polls_res.polls[0], poll_res); -} - -#[test] -fn end_poll_zero_quorum() { - let mut deps = mock_dependencies(20, &coins(1000, VOTING_TOKEN)); - mock_init(&mut deps); - let mut creator_env = mock_env_height(VOTING_TOKEN, &vec![], 1000, 10000); - - let msg = create_poll_msg( - "test".to_string(), - "test".to_string(), - None, - Some(ExecuteMsg { - contract: HumanAddr::from(VOTING_TOKEN), - msg: to_binary(&Cw20HandleMsg::Burn { - amount: Uint128(123), - }) - .unwrap(), - }), - ); - - let handle_res = handle(&mut deps, creator_env.clone(), msg).unwrap(); - assert_create_poll_result( - 1, - creator_env.block.height + DEFAULT_VOTING_PERIOD, - TEST_CREATOR, - handle_res, - &mut deps, - ); - let stake_amount = 100; - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128(100u128 + DEFAULT_PROPOSAL_DEPOSIT), - )], - )]); - - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(TEST_VOTER), - amount: Uint128::from(stake_amount as u128), - msg: Some(to_binary(&Cw20HookMsg::StakeVotingTokens {}).unwrap()), - }); - - let env = mock_env(VOTING_TOKEN, &[]); - handle(&mut deps, env, msg.clone()).unwrap(); - - let msg = HandleMsg::EndPoll { poll_id: 1 }; - creator_env.message.sender = HumanAddr::from(TEST_CREATOR); - creator_env.block.height = &creator_env.block.height + DEFAULT_VOTING_PERIOD; - - let handle_res = handle(&mut deps, creator_env.clone(), msg).unwrap(); - - assert_eq!( - handle_res.log, - vec![ - log("action", "end_poll"), - log("poll_id", "1"), - log("rejected_reason", "Quorum not reached"), - log("passed", "false"), - ] - ); - - assert_eq!(handle_res.messages.len(), 0usize); - - // Query rejected polls - let res = query( - &deps, - QueryMsg::Polls { - filter: Some(PollStatus::Rejected), - start_after: None, - limit: None, - order_by: Some(OrderBy::Desc), - }, - ) - .unwrap(); - let response: PollsResponse = from_binary(&res).unwrap(); - assert_eq!(response.polls.len(), 1); - - let res = query( - &deps, - QueryMsg::Polls { - filter: Some(PollStatus::InProgress), - start_after: None, - limit: None, - order_by: None, - }, - ) - .unwrap(); - let response: PollsResponse = from_binary(&res).unwrap(); - assert_eq!(response.polls.len(), 0); - - let res = query( - &deps, - QueryMsg::Polls { - filter: Some(PollStatus::Passed), - start_after: None, - limit: None, - order_by: None, - }, - ) - .unwrap(); - let response: PollsResponse = from_binary(&res).unwrap(); - assert_eq!(response.polls.len(), 0); -} - -#[test] -fn end_poll_quorum_rejected() { - let mut deps = mock_dependencies(20, &coins(100, VOTING_TOKEN)); - mock_init(&mut deps); - - let msg = create_poll_msg("test".to_string(), "test".to_string(), None, None); - let mut creator_env = mock_env(VOTING_TOKEN, &vec![]); - let handle_res = handle(&mut deps, creator_env.clone(), msg.clone()).unwrap(); - assert_eq!( - handle_res.log, - vec![ - log("action", "create_poll"), - log("creator", TEST_CREATOR), - log("poll_id", "1"), - log("end_height", "22345"), - ] - ); - - let stake_amount = 100; - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128(100u128 + DEFAULT_PROPOSAL_DEPOSIT), - )], - )]); - - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(TEST_VOTER), - amount: Uint128::from(stake_amount as u128), - msg: Some(to_binary(&Cw20HookMsg::StakeVotingTokens {}).unwrap()), - }); - - let env = mock_env(VOTING_TOKEN, &[]); - let handle_res = handle(&mut deps, env, msg.clone()).unwrap(); - assert_stake_tokens_result( - stake_amount, - DEFAULT_PROPOSAL_DEPOSIT, - stake_amount, - 1, - handle_res, - &mut deps, - ); - - let msg = HandleMsg::CastVote { - poll_id: 1, - vote: VoteOption::Yes, - amount: Uint128::from(10u128), - }; - let env = mock_env(TEST_VOTER, &[]); - let handle_res = handle(&mut deps, env, msg).unwrap(); - - assert_eq!( - handle_res.log, - vec![ - log("action", "cast_vote"), - log("poll_id", "1"), - log("amount", "10"), - log("voter", TEST_VOTER), - log("vote_option", "yes"), - ] - ); - - let msg = HandleMsg::EndPoll { poll_id: 1 }; - - creator_env.message.sender = HumanAddr::from(TEST_CREATOR); - creator_env.block.height = &creator_env.block.height + DEFAULT_VOTING_PERIOD; - - let handle_res = handle(&mut deps, creator_env.clone(), msg.clone()).unwrap(); - assert_eq!( - handle_res.log, - vec![ - log("action", "end_poll"), - log("poll_id", "1"), - log("rejected_reason", "Quorum not reached"), - log("passed", "false"), - ] - ); -} - -#[test] -fn end_poll_quorum_rejected_noting_staked() { - let mut deps = mock_dependencies(20, &coins(100, VOTING_TOKEN)); - mock_init(&mut deps); - - let msg = create_poll_msg("test".to_string(), "test".to_string(), None, None); - let mut creator_env = mock_env(VOTING_TOKEN, &vec![]); - let handle_res = handle(&mut deps, creator_env.clone(), msg.clone()).unwrap(); - assert_eq!( - handle_res.log, - vec![ - log("action", "create_poll"), - log("creator", TEST_CREATOR), - log("poll_id", "1"), - log("end_height", "22345"), - ] - ); - - let msg = HandleMsg::EndPoll { poll_id: 1 }; - - creator_env.message.sender = HumanAddr::from(TEST_CREATOR); - creator_env.block.height = &creator_env.block.height + DEFAULT_VOTING_PERIOD; - - let handle_res = handle(&mut deps, creator_env.clone(), msg.clone()).unwrap(); - assert_eq!( - handle_res.log, - vec![ - log("action", "end_poll"), - log("poll_id", "1"), - log("rejected_reason", "Quorum not reached"), - log("passed", "false"), - ] - ); -} - -#[test] -fn end_poll_nay_rejected() { - let voter1_stake = 100; - let voter2_stake = 1000; - let mut deps = mock_dependencies(20, &[]); - mock_init(&mut deps); - let mut creator_env = mock_env(VOTING_TOKEN, &coins(2, VOTING_TOKEN)); - - let msg = create_poll_msg("test".to_string(), "test".to_string(), None, None); - - let handle_res = handle(&mut deps, creator_env.clone(), msg.clone()).unwrap(); - assert_eq!( - handle_res.log, - vec![ - log("action", "create_poll"), - log("creator", TEST_CREATOR), - log("poll_id", "1"), - log("end_height", "22345"), - ] - ); - - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128((voter1_stake + DEFAULT_PROPOSAL_DEPOSIT) as u128), - )], - )]); - - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(TEST_VOTER), - amount: Uint128::from(voter1_stake as u128), - msg: Some(to_binary(&Cw20HookMsg::StakeVotingTokens {}).unwrap()), - }); - - let env = mock_env(VOTING_TOKEN, &[]); - let handle_res = handle(&mut deps, env, msg).unwrap(); - assert_stake_tokens_result( - voter1_stake, - DEFAULT_PROPOSAL_DEPOSIT, - voter1_stake, - 1, - handle_res, - &mut deps, - ); - - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128((voter1_stake + voter2_stake + DEFAULT_PROPOSAL_DEPOSIT) as u128), - )], - )]); - - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(TEST_VOTER_2), - amount: Uint128::from(voter2_stake as u128), - msg: Some(to_binary(&Cw20HookMsg::StakeVotingTokens {}).unwrap()), - }); - - let env = mock_env(VOTING_TOKEN, &[]); - let handle_res = handle(&mut deps, env, msg).unwrap(); - assert_stake_tokens_result( - voter1_stake + voter2_stake, - DEFAULT_PROPOSAL_DEPOSIT, - voter2_stake, - 1, - handle_res, - &mut deps, - ); - - let env = mock_env(TEST_VOTER_2, &[]); - let msg = HandleMsg::CastVote { - poll_id: 1, - vote: VoteOption::No, - amount: Uint128::from(voter2_stake), - }; - let handle_res = handle(&mut deps, env, msg).unwrap(); - assert_cast_vote_success(TEST_VOTER_2, voter2_stake, 1, VoteOption::No, handle_res); - - let msg = HandleMsg::EndPoll { poll_id: 1 }; - - creator_env.message.sender = HumanAddr::from(TEST_CREATOR); - creator_env.block.height = &creator_env.block.height + DEFAULT_VOTING_PERIOD; - let handle_res = handle(&mut deps, creator_env.clone(), msg.clone()).unwrap(); - assert_eq!( - handle_res.log, - vec![ - log("action", "end_poll"), - log("poll_id", "1"), - log("rejected_reason", "Threshold not reached"), - log("passed", "false"), - ] - ); -} - -#[test] -fn fails_cast_vote_not_enough_staked() { - let mut deps = mock_dependencies(20, &[]); - mock_init(&mut deps); - let env = mock_env_height(VOTING_TOKEN, &vec![], 0, 10000); - - let msg = create_poll_msg("test".to_string(), "test".to_string(), None, None); - - let handle_res = handle(&mut deps, env, msg.clone()).unwrap(); - assert_create_poll_result( - 1, - DEFAULT_VOTING_PERIOD, - TEST_CREATOR, - handle_res, - &mut deps, - ); - - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128(10u128 + DEFAULT_PROPOSAL_DEPOSIT), - )], - )]); - - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(TEST_VOTER), - amount: Uint128::from(10u128), - msg: Some(to_binary(&Cw20HookMsg::StakeVotingTokens {}).unwrap()), - }); - - let env = mock_env(VOTING_TOKEN, &[]); - let handle_res = handle(&mut deps, env, msg.clone()).unwrap(); - assert_stake_tokens_result(10, DEFAULT_PROPOSAL_DEPOSIT, 10, 1, handle_res, &mut deps); - - let env = mock_env_height(TEST_VOTER, &coins(11, VOTING_TOKEN), 0, 10000); - let msg = HandleMsg::CastVote { - poll_id: 1, - vote: VoteOption::Yes, - amount: Uint128::from(11u128), - }; - - let res = handle(&mut deps, env, msg); - - match res { - Ok(_) => panic!("Must return error"), - Err(StdError::GenericErr { msg, .. }) => { - assert_eq!(msg, "User does not have enough staked tokens.") - } - Err(e) => panic!("Unexpected error: {:?}", e), - } -} - -#[test] -fn happy_days_cast_vote() { - let mut deps = mock_dependencies(20, &[]); - mock_init(&mut deps); - - let env = mock_env_height(VOTING_TOKEN, &vec![], 0, 10000); - let msg = create_poll_msg("test".to_string(), "test".to_string(), None, None); - - let handle_res = handle(&mut deps, env, msg.clone()).unwrap(); - assert_create_poll_result( - 1, - DEFAULT_VOTING_PERIOD, - TEST_CREATOR, - handle_res, - &mut deps, - ); - - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128(11u128 + DEFAULT_PROPOSAL_DEPOSIT), - )], - )]); - - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(TEST_VOTER), - amount: Uint128::from(11u128), - msg: Some(to_binary(&Cw20HookMsg::StakeVotingTokens {}).unwrap()), - }); - - let env = mock_env(VOTING_TOKEN, &[]); - let handle_res = handle(&mut deps, env, msg.clone()).unwrap(); - assert_stake_tokens_result(11, DEFAULT_PROPOSAL_DEPOSIT, 11, 1, handle_res, &mut deps); - - let env = mock_env_height(TEST_VOTER, &coins(11, VOTING_TOKEN), 0, 10000); - let amount = 10u128; - let msg = HandleMsg::CastVote { - poll_id: 1, - vote: VoteOption::Yes, - amount: Uint128::from(amount), - }; - - let handle_res = handle(&mut deps, env, msg.clone()).unwrap(); - assert_cast_vote_success(TEST_VOTER, amount, 1, VoteOption::Yes, handle_res); - - // balance be double - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128(22u128 + DEFAULT_PROPOSAL_DEPOSIT), - )], - )]); - - // Query staker - let res = query( - &deps, - QueryMsg::Staker { - address: HumanAddr::from(TEST_VOTER), - }, - ) - .unwrap(); - let response: StakerResponse = from_binary(&res).unwrap(); - assert_eq!( - response, - StakerResponse { - balance: Uint128(22u128), - share: Uint128(11u128), - locked_balance: vec![( - 1u64, - VoterInfo { - vote: VoteOption::Yes, - balance: Uint128::from(amount), - } - )], - pending_voting_rewards: Uint128::zero(), - withdrawable_polls: vec![], - } - ); - - // Query voters - let res = query( - &deps, - QueryMsg::Voters { - poll_id: 1u64, - start_after: None, - limit: None, - order_by: Some(OrderBy::Desc), - }, - ) - .unwrap(); - let response: VotersResponse = from_binary(&res).unwrap(); - assert_eq!( - response.voters, - vec![VotersResponseItem { - voter: HumanAddr::from(TEST_VOTER), - vote: VoteOption::Yes, - balance: Uint128::from(amount), - }] - ); - - let res = query( - &deps, - QueryMsg::Voters { - poll_id: 1u64, - start_after: Some(HumanAddr::from(TEST_VOTER)), - limit: None, - order_by: None, - }, - ) - .unwrap(); - let response: VotersResponse = from_binary(&res).unwrap(); - assert_eq!(response.voters.len(), 0); -} - -#[test] -fn happy_days_withdraw_voting_tokens() { - let mut deps = mock_dependencies(20, &[]); - mock_init(&mut deps); - - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[(&HumanAddr::from(MOCK_CONTRACT_ADDR), &Uint128(11u128))], - )]); - - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(TEST_VOTER), - amount: Uint128::from(11u128), - msg: Some(to_binary(&Cw20HookMsg::StakeVotingTokens {}).unwrap()), - }); - - let env = mock_env(VOTING_TOKEN, &[]); - let handle_res = handle(&mut deps, env, msg.clone()).unwrap(); - assert_stake_tokens_result(11, 0, 11, 0, handle_res, &mut deps); - - let state: State = state_read(&mut deps.storage).load().unwrap(); - assert_eq!( - state, - State { - contract_addr: deps - .api - .canonical_address(&HumanAddr::from(MOCK_CONTRACT_ADDR)) - .unwrap(), - poll_count: 0, - total_share: Uint128::from(11u128), - total_deposit: Uint128::zero(), - pending_voting_rewards: Uint128::zero(), - } - ); - - // double the balance, only half will be withdrawn - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[(&HumanAddr::from(MOCK_CONTRACT_ADDR), &Uint128(22u128))], - )]); - - let env = mock_env(TEST_VOTER, &[]); - let msg = HandleMsg::WithdrawVotingTokens { - amount: Some(Uint128::from(11u128)), - }; - - let handle_res = handle(&mut deps, env, msg.clone()).unwrap(); - let msg = handle_res.messages.get(0).expect("no message"); - - assert_eq!( - msg, - &CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from(VOTING_TOKEN), - msg: to_binary(&Cw20HandleMsg::Transfer { - recipient: HumanAddr::from(TEST_VOTER), - amount: Uint128::from(11u128), - }) - .unwrap(), - send: vec![], - }) - ); - - let state: State = state_read(&mut deps.storage).load().unwrap(); - assert_eq!( - state, - State { - contract_addr: deps - .api - .canonical_address(&HumanAddr::from(MOCK_CONTRACT_ADDR)) - .unwrap(), - poll_count: 0, - total_share: Uint128::from(6u128), - total_deposit: Uint128::zero(), - pending_voting_rewards: Uint128::zero(), - } - ); -} - -#[test] -fn happy_days_withdraw_voting_tokens_all() { - let mut deps = mock_dependencies(20, &[]); - mock_init(&mut deps); - - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[(&HumanAddr::from(MOCK_CONTRACT_ADDR), &Uint128(11u128))], - )]); - - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(TEST_VOTER), - amount: Uint128::from(11u128), - msg: Some(to_binary(&Cw20HookMsg::StakeVotingTokens {}).unwrap()), - }); - - let env = mock_env(VOTING_TOKEN, &[]); - let handle_res = handle(&mut deps, env, msg.clone()).unwrap(); - assert_stake_tokens_result(11, 0, 11, 0, handle_res, &mut deps); - - let state: State = state_read(&mut deps.storage).load().unwrap(); - assert_eq!( - state, - State { - contract_addr: deps - .api - .canonical_address(&HumanAddr::from(MOCK_CONTRACT_ADDR)) - .unwrap(), - poll_count: 0, - total_share: Uint128::from(11u128), - total_deposit: Uint128::zero(), - pending_voting_rewards: Uint128::zero(), - } - ); - - // double the balance, all balance withdrawn - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[(&HumanAddr::from(MOCK_CONTRACT_ADDR), &Uint128(22u128))], - )]); - - let env = mock_env(TEST_VOTER, &[]); - let msg = HandleMsg::WithdrawVotingTokens { amount: None }; - - let handle_res = handle(&mut deps, env, msg.clone()).unwrap(); - let msg = handle_res.messages.get(0).expect("no message"); - - assert_eq!( - msg, - &CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from(VOTING_TOKEN), - msg: to_binary(&Cw20HandleMsg::Transfer { - recipient: HumanAddr::from(TEST_VOTER), - amount: Uint128::from(22u128), - }) - .unwrap(), - send: vec![], - }) - ); - - let state: State = state_read(&mut deps.storage).load().unwrap(); - assert_eq!( - state, - State { - contract_addr: deps - .api - .canonical_address(&HumanAddr::from(MOCK_CONTRACT_ADDR)) - .unwrap(), - poll_count: 0, - total_share: Uint128::zero(), - total_deposit: Uint128::zero(), - pending_voting_rewards: Uint128::zero(), - } - ); -} - -#[test] -fn withdraw_voting_tokens() { - let mut deps = mock_dependencies(20, &[]); - mock_init(&mut deps); - - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[(&HumanAddr::from(MOCK_CONTRACT_ADDR), &Uint128(11u128))], - )]); - - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(TEST_VOTER), - amount: Uint128::from(11u128), - msg: Some(to_binary(&Cw20HookMsg::StakeVotingTokens {}).unwrap()), - }); - - let env = mock_env(VOTING_TOKEN, &[]); - let handle_res = handle(&mut deps, env, msg.clone()).unwrap(); - assert_stake_tokens_result(11, 0, 11, 0, handle_res, &mut deps); - - // make fake polls; one in progress & one in passed - poll_store(&mut deps.storage) - .save( - &1u64.to_be_bytes(), - &Poll { - id: 1u64, - creator: CanonicalAddr::default(), - status: PollStatus::InProgress, - yes_votes: Uint128::zero(), - no_votes: Uint128::zero(), - abstain_votes: Uint128::zero(), - end_height: 0u64, - title: "title".to_string(), - description: "description".to_string(), - deposit_amount: Uint128::zero(), - link: None, - execute_data: None, - total_balance_at_end_poll: None, - voters_reward: Uint128::zero(), - staked_amount: None, - }, - ) - .unwrap(); - - poll_store(&mut deps.storage) - .save( - &2u64.to_be_bytes(), - &Poll { - id: 1u64, - creator: CanonicalAddr::default(), - status: PollStatus::Passed, - yes_votes: Uint128::zero(), - no_votes: Uint128::zero(), - abstain_votes: Uint128::zero(), - end_height: 0u64, - title: "title".to_string(), - description: "description".to_string(), - deposit_amount: Uint128::zero(), - link: None, - execute_data: None, - total_balance_at_end_poll: None, - voters_reward: Uint128::zero(), - staked_amount: None, - }, - ) - .unwrap(); - - let voter_addr_raw = deps - .api - .canonical_address(&HumanAddr::from(TEST_VOTER)) - .unwrap(); - poll_voter_store(&mut deps.storage, 1u64) - .save( - &voter_addr_raw.as_slice(), - &VoterInfo { - vote: VoteOption::Yes, - balance: Uint128(5u128), - }, - ) - .unwrap(); - poll_voter_store(&mut deps.storage, 2u64) - .save( - &voter_addr_raw.as_slice(), - &VoterInfo { - vote: VoteOption::Yes, - balance: Uint128(5u128), - }, - ) - .unwrap(); - bank_store(&mut deps.storage) - .save( - &voter_addr_raw.as_slice(), - &TokenManager { - share: Uint128(11u128), - locked_balance: vec![ - ( - 1u64, - VoterInfo { - vote: VoteOption::Yes, - balance: Uint128(5u128), - }, - ), - ( - 2u64, - VoterInfo { - vote: VoteOption::Yes, - balance: Uint128(5u128), - }, - ), - ], - participated_polls: vec![], - }, - ) - .unwrap(); - - let env = mock_env(TEST_VOTER, &[]); - let msg = HandleMsg::WithdrawVotingTokens { - amount: Some(Uint128::from(5u128)), - }; - - let _ = handle(&mut deps, env, msg).unwrap(); - let voter = poll_voter_read(&deps.storage, 1u64) - .load(&voter_addr_raw.as_slice()) - .unwrap(); - assert_eq!( - voter, - VoterInfo { - vote: VoteOption::Yes, - balance: Uint128(5u128), - } - ); - - let token_manager = bank_read(&deps.storage) - .load(&voter_addr_raw.as_slice()) - .unwrap(); - assert_eq!( - token_manager.locked_balance, - vec![( - 1u64, - VoterInfo { - vote: VoteOption::Yes, - balance: Uint128(5u128), - } - )] - ); -} - -#[test] -fn fails_withdraw_voting_tokens_no_stake() { - let mut deps = mock_dependencies(20, &[]); - mock_init(&mut deps); - - let env = mock_env(TEST_VOTER, &coins(11, VOTING_TOKEN)); - let msg = HandleMsg::WithdrawVotingTokens { - amount: Some(Uint128::from(11u128)), - }; - - let res = handle(&mut deps, env, msg); - - match res { - Ok(_) => panic!("Must return error"), - Err(StdError::GenericErr { msg, .. }) => assert_eq!(msg, "Nothing staked"), - Err(e) => panic!("Unexpected error: {:?}", e), - } -} - -#[test] -fn fails_withdraw_too_many_tokens() { - let mut deps = mock_dependencies(20, &[]); - mock_init(&mut deps); - - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[(&HumanAddr::from(MOCK_CONTRACT_ADDR), &Uint128(10u128))], - )]); - - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(TEST_VOTER), - amount: Uint128::from(10u128), - msg: Some(to_binary(&Cw20HookMsg::StakeVotingTokens {}).unwrap()), - }); - - let env = mock_env(VOTING_TOKEN, &[]); - let handle_res = handle(&mut deps, env, msg.clone()).unwrap(); - assert_stake_tokens_result(10, 0, 10, 0, handle_res, &mut deps); - - let env = mock_env(TEST_VOTER, &[]); - let msg = HandleMsg::WithdrawVotingTokens { - amount: Some(Uint128::from(11u128)), - }; - - let res = handle(&mut deps, env, msg); - - match res { - Ok(_) => panic!("Must return error"), - Err(StdError::GenericErr { msg, .. }) => { - assert_eq!(msg, "User is trying to withdraw too many tokens.") - } - Err(e) => panic!("Unexpected error: {:?}", e), - } -} - -#[test] -fn fails_cast_vote_twice() { - let mut deps = mock_dependencies(20, &[]); - mock_init(&mut deps); - - let env = mock_env_height(VOTING_TOKEN, &coins(2, VOTING_TOKEN), 0, 10000); - - let msg = create_poll_msg("test".to_string(), "test".to_string(), None, None); - let handle_res = handle(&mut deps, env.clone(), msg.clone()).unwrap(); - - assert_create_poll_result( - 1, - env.block.height + DEFAULT_VOTING_PERIOD, - TEST_CREATOR, - handle_res, - &mut deps, - ); - - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128(11u128 + DEFAULT_PROPOSAL_DEPOSIT), - )], - )]); - - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(TEST_VOTER), - amount: Uint128::from(11u128), - msg: Some(to_binary(&Cw20HookMsg::StakeVotingTokens {}).unwrap()), - }); - - let env = mock_env(VOTING_TOKEN, &[]); - let handle_res = handle(&mut deps, env, msg.clone()).unwrap(); - assert_stake_tokens_result(11, DEFAULT_PROPOSAL_DEPOSIT, 11, 1, handle_res, &mut deps); - - let amount = 1u128; - let msg = HandleMsg::CastVote { - poll_id: 1, - vote: VoteOption::Yes, - amount: Uint128::from(amount), - }; - let env = mock_env_height(TEST_VOTER, &[], 0, 10000); - let handle_res = handle(&mut deps, env.clone(), msg).unwrap(); - assert_cast_vote_success(TEST_VOTER, amount, 1, VoteOption::Yes, handle_res); - - let msg = HandleMsg::CastVote { - poll_id: 1, - vote: VoteOption::Yes, - amount: Uint128::from(amount), - }; - let res = handle(&mut deps, env, msg); - - match res { - Ok(_) => panic!("Must return error"), - Err(StdError::GenericErr { msg, .. }) => assert_eq!(msg, "User has already voted."), - Err(e) => panic!("Unexpected error: {:?}", e), - } -} - -#[test] -fn fails_cast_vote_without_poll() { - let mut deps = mock_dependencies(20, &[]); - mock_init(&mut deps); - - let msg = HandleMsg::CastVote { - poll_id: 0, - vote: VoteOption::Yes, - amount: Uint128::from(1u128), - }; - let env = mock_env(TEST_VOTER, &coins(11, VOTING_TOKEN)); - - let res = handle(&mut deps, env, msg); - - match res { - Ok(_) => panic!("Must return error"), - Err(StdError::GenericErr { msg, .. }) => assert_eq!(msg, "Poll does not exist"), - Err(e) => panic!("Unexpected error: {:?}", e), - } -} - -#[test] -fn happy_days_stake_voting_tokens() { - let mut deps = mock_dependencies(20, &[]); - mock_init(&mut deps); - - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[(&HumanAddr::from(MOCK_CONTRACT_ADDR), &Uint128(11u128))], - )]); - - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(TEST_VOTER), - amount: Uint128::from(11u128), - msg: Some(to_binary(&Cw20HookMsg::StakeVotingTokens {}).unwrap()), - }); - - let env = mock_env(VOTING_TOKEN, &[]); - let handle_res = handle(&mut deps, env, msg.clone()).unwrap(); - assert_stake_tokens_result(11, 0, 11, 0, handle_res, &mut deps); -} - -#[test] -fn fails_insufficient_funds() { - let mut deps = mock_dependencies(20, &[]); - - // initialize the store - let msg = init_msg(); - let env = mock_env(TEST_VOTER, &coins(2, VOTING_TOKEN)); - let init_res = init(&mut deps, env, msg).unwrap(); - assert_eq!(0, init_res.messages.len()); - - // insufficient token - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(TEST_VOTER), - amount: Uint128::from(0u128), - msg: Some(to_binary(&Cw20HookMsg::StakeVotingTokens {}).unwrap()), - }); - - let env = mock_env(VOTING_TOKEN, &[]); - let res = handle(&mut deps, env, msg); - - match res { - Ok(_) => panic!("Must return error"), - Err(StdError::GenericErr { msg, .. }) => assert_eq!(msg, "Insufficient funds sent"), - Err(e) => panic!("Unexpected error: {:?}", e), - } -} - -#[test] -fn fails_staking_wrong_token() { - let mut deps = mock_dependencies(20, &[]); - - // initialize the store - let msg = init_msg(); - let env = mock_env(TEST_VOTER, &coins(2, VOTING_TOKEN)); - let init_res = init(&mut deps, env, msg).unwrap(); - assert_eq!(0, init_res.messages.len()); - - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[(&HumanAddr::from(MOCK_CONTRACT_ADDR), &Uint128(11u128))], - )]); - - // wrong token - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(TEST_VOTER), - amount: Uint128::from(11u128), - msg: Some(to_binary(&Cw20HookMsg::StakeVotingTokens {}).unwrap()), - }); - - let env = mock_env(VOTING_TOKEN.to_string() + "2", &[]); - let res = handle(&mut deps, env, msg); - - match res { - Ok(_) => panic!("Must return error"), - Err(StdError::Unauthorized { .. }) => {} - Err(e) => panic!("Unexpected error: {:?}", e), - } -} - -#[test] -fn share_calculation() { - let mut deps = mock_dependencies(20, &[]); - - // initialize the store - let msg = init_msg(); - let env = mock_env(TEST_VOTER, &coins(2, VOTING_TOKEN)); - let init_res = init(&mut deps, env, msg).unwrap(); - assert_eq!(0, init_res.messages.len()); - - // create 100 share - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[(&HumanAddr::from(MOCK_CONTRACT_ADDR), &Uint128(100u128))], - )]); - - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(TEST_VOTER), - amount: Uint128::from(100u128), - msg: Some(to_binary(&Cw20HookMsg::StakeVotingTokens {}).unwrap()), - }); - - let env = mock_env(VOTING_TOKEN.to_string(), &[]); - let _res = handle(&mut deps, env, msg); - - // add more balance(100) to make share:balance = 1:2 - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128(200u128 + 100u128), - )], - )]); - - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(TEST_VOTER), - amount: Uint128::from(100u128), - msg: Some(to_binary(&Cw20HookMsg::StakeVotingTokens {}).unwrap()), - }); - - let env = mock_env(VOTING_TOKEN.to_string(), &[]); - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!( - res.log, - vec![ - log("action", "staking"), - log("sender", TEST_VOTER), - log("share", "50"), - log("amount", "100"), - ] - ); - - let msg = HandleMsg::WithdrawVotingTokens { - amount: Some(Uint128(100u128)), - }; - let env = mock_env(TEST_VOTER.to_string(), &[]); - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!( - res.log, - vec![ - log("action", "withdraw"), - log("recipient", TEST_VOTER), - log("amount", "100"), - ] - ); - - // 100 tokens withdrawn - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[(&HumanAddr::from(MOCK_CONTRACT_ADDR), &Uint128(200u128))], - )]); - - let res = query( - &mut deps, - QueryMsg::Staker { - address: HumanAddr::from(TEST_VOTER), - }, - ) - .unwrap(); - let stake_info: StakerResponse = from_binary(&res).unwrap(); - assert_eq!(stake_info.share, Uint128(100)); - assert_eq!(stake_info.balance, Uint128(200)); - assert_eq!(stake_info.locked_balance, vec![]); -} - -#[test] -fn share_calculation_with_voter_rewards() { - let mut deps = mock_dependencies(20, &[]); - - // initialize the store - let msg = InitMsg { - mirror_token: HumanAddr::from(VOTING_TOKEN), - quorum: Decimal::percent(DEFAULT_QUORUM), - threshold: Decimal::percent(DEFAULT_THRESHOLD), - voting_period: DEFAULT_VOTING_PERIOD, - effective_delay: DEFAULT_EFFECTIVE_DELAY, - expiration_period: DEFAULT_EXPIRATION_PERIOD, - proposal_deposit: Uint128(DEFAULT_PROPOSAL_DEPOSIT), - voter_weight: Decimal::percent(50), // distribute 50% rewards to voters - snapshot_period: DEFAULT_SNAPSHOT_PERIOD, - }; - let env = mock_env(TEST_VOTER, &coins(2, VOTING_TOKEN)); - let init_res = init(&mut deps, env, msg).unwrap(); - assert_eq!(0, init_res.messages.len()); - - // create poll - let env = mock_env_height(VOTING_TOKEN, &coins(2, VOTING_TOKEN), 0, 10000); - let msg = create_poll_msg("test".to_string(), "test".to_string(), None, None); - let handle_res = handle(&mut deps, env.clone(), msg.clone()).unwrap(); - assert_create_poll_result( - 1, - env.block.height + DEFAULT_VOTING_PERIOD, - TEST_CREATOR, - handle_res, - &mut deps, - ); - - // create 100 share - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128(DEFAULT_PROPOSAL_DEPOSIT + 100u128), - )], - )]); - - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(TEST_VOTER), - amount: Uint128::from(100u128), - msg: Some(to_binary(&Cw20HookMsg::StakeVotingTokens {}).unwrap()), - }); - let env = mock_env(VOTING_TOKEN.to_string(), &[]); - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!( - res.log, - vec![ - log("action", "staking"), - log("sender", TEST_VOTER), - log("share", "100"), - log("amount", "100"), - ] - ); - - // add more balance through dept reward, 50% reserved for voters - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128(DEFAULT_PROPOSAL_DEPOSIT + 400u128 + 100u128), - )], - )]); - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(TEST_COLLECTOR), - amount: Uint128::from(400u128), - msg: Some(to_binary(&Cw20HookMsg::DepositReward {}).unwrap()), - }); - let env = mock_env(VOTING_TOKEN.to_string(), &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128(DEFAULT_PROPOSAL_DEPOSIT + 400u128 + 100u128), - )], - )]); - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(TEST_VOTER), - amount: Uint128::from(100u128), - msg: Some(to_binary(&Cw20HookMsg::StakeVotingTokens {}).unwrap()), - }); - - let env = mock_env(VOTING_TOKEN.to_string(), &[]); - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!( - res.log, - vec![ - log("action", "staking"), - log("sender", TEST_VOTER), - log("share", "50"), - log("amount", "100"), - ] - ); - - let msg = HandleMsg::WithdrawVotingTokens { - amount: Some(Uint128(100u128)), - }; - let env = mock_env(TEST_VOTER.to_string(), &[]); - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!( - res.log, - vec![ - log("action", "withdraw"), - log("recipient", TEST_VOTER), - log("amount", "100"), - ] - ); - - // 100 tokens withdrawn - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128(DEFAULT_PROPOSAL_DEPOSIT + 400u128), - )], - )]); - - let res = query( - &mut deps, - QueryMsg::Staker { - address: HumanAddr::from(TEST_VOTER), - }, - ) - .unwrap(); - let stake_info: StakerResponse = from_binary(&res).unwrap(); - assert_eq!(stake_info.share, Uint128(100)); - assert_eq!(stake_info.balance, Uint128(200)); - assert_eq!(stake_info.locked_balance, vec![]); -} - -// helper to confirm the expected create_poll response -fn assert_create_poll_result( - poll_id: u64, - end_height: u64, - creator: &str, - handle_res: HandleResponse, - deps: &mut Extern, -) { - assert_eq!( - handle_res.log, - vec![ - log("action", "create_poll"), - log("creator", creator), - log("poll_id", poll_id.to_string()), - log("end_height", end_height.to_string()), - ] - ); - - //confirm poll count - let state: State = state_read(&mut deps.storage).load().unwrap(); - assert_eq!( - state, - State { - contract_addr: deps - .api - .canonical_address(&HumanAddr::from(MOCK_CONTRACT_ADDR)) - .unwrap(), - poll_count: 1, - total_share: Uint128::zero(), - total_deposit: Uint128(DEFAULT_PROPOSAL_DEPOSIT), - pending_voting_rewards: Uint128::zero(), - } - ); -} - -fn assert_stake_tokens_result( - total_share: u128, - total_deposit: u128, - new_share: u128, - poll_count: u64, - handle_res: HandleResponse, - deps: &mut Extern, -) { - assert_eq!( - handle_res.log.get(2).expect("no log"), - &log("share", new_share.to_string()) - ); - - let state: State = state_read(&mut deps.storage).load().unwrap(); - assert_eq!( - state, - State { - contract_addr: deps - .api - .canonical_address(&HumanAddr::from(MOCK_CONTRACT_ADDR)) - .unwrap(), - poll_count, - total_share: Uint128(total_share), - total_deposit: Uint128(total_deposit), - pending_voting_rewards: Uint128::zero(), - } - ); -} - -fn assert_cast_vote_success( - voter: &str, - amount: u128, - poll_id: u64, - vote_option: VoteOption, - handle_res: HandleResponse, -) { - assert_eq!( - handle_res.log, - vec![ - log("action", "cast_vote"), - log("poll_id", poll_id.to_string()), - log("amount", amount.to_string()), - log("voter", voter), - log("vote_option", vote_option.to_string()), - ] - ); -} - -#[test] -fn update_config() { - let mut deps = mock_dependencies(20, &[]); - mock_init(&mut deps); - - // update owner - let env = mock_env(TEST_CREATOR, &[]); - let msg = HandleMsg::UpdateConfig { - owner: Some(HumanAddr("addr0001".to_string())), - quorum: None, - threshold: None, - voting_period: None, - effective_delay: None, - expiration_period: None, - proposal_deposit: None, - voter_weight: None, - snapshot_period: None, - }; - - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!(0, res.messages.len()); - - // it worked, let's query the state - let res = query(&deps, QueryMsg::Config {}).unwrap(); - let config: ConfigResponse = from_binary(&res).unwrap(); - assert_eq!("addr0001", config.owner.as_str()); - assert_eq!(Decimal::percent(DEFAULT_QUORUM), config.quorum); - assert_eq!(Decimal::percent(DEFAULT_THRESHOLD), config.threshold); - assert_eq!(DEFAULT_VOTING_PERIOD, config.voting_period); - assert_eq!(DEFAULT_EFFECTIVE_DELAY, config.effective_delay); - assert_eq!(DEFAULT_PROPOSAL_DEPOSIT, config.proposal_deposit.u128()); - - // update left items - let env = mock_env("addr0001", &[]); - let msg = HandleMsg::UpdateConfig { - owner: None, - quorum: Some(Decimal::percent(20)), - threshold: Some(Decimal::percent(75)), - voting_period: Some(20000u64), - effective_delay: Some(20000u64), - expiration_period: Some(30000u64), - proposal_deposit: Some(Uint128(123u128)), - voter_weight: Some(Decimal::percent(1)), - snapshot_period: Some(60u64), - }; - - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!(0, res.messages.len()); - - // it worked, let's query the state - let res = query(&deps, QueryMsg::Config {}).unwrap(); - let config: ConfigResponse = from_binary(&res).unwrap(); - assert_eq!("addr0001", config.owner.as_str()); - assert_eq!(Decimal::percent(20), config.quorum); - assert_eq!(Decimal::percent(75), config.threshold); - assert_eq!(20000u64, config.voting_period); - assert_eq!(20000u64, config.effective_delay); - assert_eq!(30000u64, config.expiration_period); - assert_eq!(123u128, config.proposal_deposit.u128()); - assert_eq!(Decimal::percent(1), config.voter_weight); - assert_eq!(60u64, config.snapshot_period); - - // Unauthorzied err - let env = mock_env(TEST_CREATOR, &[]); - let msg = HandleMsg::UpdateConfig { - owner: None, - quorum: None, - threshold: None, - voting_period: None, - effective_delay: None, - expiration_period: None, - proposal_deposit: None, - voter_weight: None, - snapshot_period: None, - }; - - let res = handle(&mut deps, env, msg); - match res { - Err(StdError::Unauthorized { .. }) => {} - _ => panic!("Must return unauthorized error"), - } -} - -#[test] -fn distribute_voting_rewards() { - let mut deps = mock_dependencies(20, &[]); - let msg = InitMsg { - mirror_token: HumanAddr::from(VOTING_TOKEN), - quorum: Decimal::percent(DEFAULT_QUORUM), - threshold: Decimal::percent(DEFAULT_THRESHOLD), - voting_period: DEFAULT_VOTING_PERIOD, - effective_delay: DEFAULT_EFFECTIVE_DELAY, - expiration_period: DEFAULT_EXPIRATION_PERIOD, - proposal_deposit: Uint128(DEFAULT_PROPOSAL_DEPOSIT), - voter_weight: Decimal::percent(50), // distribute 50% rewards to voters - snapshot_period: DEFAULT_SNAPSHOT_PERIOD, - }; - - let env = mock_env(TEST_CREATOR, &[]); - let _res = init(&mut deps, env, msg).expect("contract successfully handles InitMsg"); - - let env = mock_env_height(VOTING_TOKEN, &coins(2, VOTING_TOKEN), 0, 10000); - let poll_end_height = env.block.height.clone() + DEFAULT_VOTING_PERIOD; - let msg = create_poll_msg("test".to_string(), "test".to_string(), None, None); - let handle_res = handle(&mut deps, env.clone(), msg.clone()).unwrap(); - - assert_create_poll_result( - 1, - env.block.height + DEFAULT_VOTING_PERIOD, - TEST_CREATOR, - handle_res, - &mut deps, - ); - - let stake_amount = 100u128; - - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128((stake_amount + DEFAULT_PROPOSAL_DEPOSIT) as u128), - )], - )]); - - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(TEST_VOTER), - amount: Uint128::from(stake_amount), - msg: Some(to_binary(&Cw20HookMsg::StakeVotingTokens {}).unwrap()), - }); - - let env = mock_env(VOTING_TOKEN.to_string(), &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::CastVote { - poll_id: 1, - vote: VoteOption::Yes, - amount: Uint128::from(stake_amount), - }; - let env = mock_env_height(TEST_VOTER, &[], 0, 10000); - let _res = handle(&mut deps, env, msg).unwrap(); - - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128((stake_amount + DEFAULT_PROPOSAL_DEPOSIT + 100u128) as u128), - )], - )]); - - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(TEST_COLLECTOR), - amount: Uint128::from(100u128), - msg: Some(to_binary(&Cw20HookMsg::DepositReward {}).unwrap()), - }); - - let env = mock_env(VOTING_TOKEN.to_string(), &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // FAIL - there is no finished polls, amount to withdraw is 0, returning error - let msg = HandleMsg::WithdrawVotingRewards {}; - let env = mock_env_height(TEST_VOTER, &[], 0, 10000); - let res = handle(&mut deps, env, msg).unwrap_err(); - assert_eq!(res, StdError::generic_err("Nothing to withdraw")); - - let env = mock_env_height(TEST_VOTER, &[], poll_end_height, 10000); - let msg = HandleMsg::EndPoll { poll_id: 1 }; - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - - // SUCCESS - let msg = HandleMsg::WithdrawVotingRewards {}; - let env = mock_env_height(TEST_VOTER, &[], 0, 10000); - let res = handle(&mut deps, env.clone(), msg).unwrap(); - - // user can withdraw 50% of total staked (weight = 50% poll share = 100%) - assert_eq!( - res.log, - vec![ - log("action", "withdraw_voting_rewards"), - log("recipient", TEST_VOTER), - log("amount", 50), - ] - ); - assert_eq!( - res.messages, - vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from(VOTING_TOKEN), - msg: to_binary(&Cw20HandleMsg::Transfer { - recipient: HumanAddr::from(TEST_VOTER), - amount: Uint128::from(50u128), - }) - .unwrap(), - send: vec![], - })] - ); - - // voting info has been deleted - assert_eq!( - poll_voter_read(&deps.storage, 1u64) - .load( - &deps - .api - .canonical_address(&HumanAddr::from(TEST_VOTER)) - .unwrap() - .as_slice() - ) - .is_err(), - true - ); -} - -#[test] -fn stake_voting_rewards() { - let mut deps = mock_dependencies(20, &[]); - let msg = InitMsg { - mirror_token: HumanAddr::from(VOTING_TOKEN), - quorum: Decimal::percent(DEFAULT_QUORUM), - threshold: Decimal::percent(DEFAULT_THRESHOLD), - voting_period: DEFAULT_VOTING_PERIOD, - effective_delay: DEFAULT_EFFECTIVE_DELAY, - expiration_period: DEFAULT_EXPIRATION_PERIOD, - proposal_deposit: Uint128(DEFAULT_PROPOSAL_DEPOSIT), - voter_weight: Decimal::percent(50), // distribute 50% rewards to voters - snapshot_period: DEFAULT_SNAPSHOT_PERIOD, - }; - - let env = mock_env(TEST_CREATOR, &[]); - let _res = init(&mut deps, env, msg).expect("contract successfully handles InitMsg"); - - let env = mock_env_height(VOTING_TOKEN, &coins(2, VOTING_TOKEN), 0, 10000); - let poll_end_height = env.block.height.clone() + DEFAULT_VOTING_PERIOD; - let msg = create_poll_msg("test".to_string(), "test".to_string(), None, None); - let handle_res = handle(&mut deps, env.clone(), msg.clone()).unwrap(); - - assert_create_poll_result( - 1, - env.block.height + DEFAULT_VOTING_PERIOD, - TEST_CREATOR, - handle_res, - &mut deps, - ); - - let stake_amount = 100u128; - - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128((stake_amount + DEFAULT_PROPOSAL_DEPOSIT) as u128), - )], - )]); - - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(TEST_VOTER), - amount: Uint128::from(stake_amount), - msg: Some(to_binary(&Cw20HookMsg::StakeVotingTokens {}).unwrap()), - }); - - let env = mock_env(VOTING_TOKEN.to_string(), &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::CastVote { - poll_id: 1, - vote: VoteOption::Yes, - amount: Uint128::from(stake_amount), - }; - let env = mock_env_height(TEST_VOTER, &[], 0, 10000); - let _res = handle(&mut deps, env, msg).unwrap(); - - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128((stake_amount + DEFAULT_PROPOSAL_DEPOSIT + 100u128) as u128), - )], - )]); - - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(TEST_COLLECTOR), - amount: Uint128::from(100u128), - msg: Some(to_binary(&Cw20HookMsg::DepositReward {}).unwrap()), - }); - - let env = mock_env(VOTING_TOKEN.to_string(), &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // FAIL - there is no finished polls, amount to withdraw is 0, returning error - let msg = HandleMsg::StakeVotingRewards {}; - let env = mock_env_height(TEST_VOTER, &[], 0, 10000); - let res = handle(&mut deps, env, msg).unwrap_err(); - assert_eq!(res, StdError::generic_err("Nothing to withdraw")); - - let env = mock_env_height(TEST_VOTER, &[], poll_end_height, 10000); - let msg = HandleMsg::EndPoll { poll_id: 1 }; - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128((stake_amount + 100u128) as u128), - )], - )]); - - // SUCCESS - let msg = HandleMsg::StakeVotingRewards {}; - let env = mock_env_height(TEST_VOTER, &[], 0, 10000); - let res = handle(&mut deps, env.clone(), msg).unwrap(); - - // user can stake 50% of the deposited reward (weight = 50% poll share = 100%) - assert_eq!( - res.log, - vec![ - log("action", "stake_voting_rewards"), - log("staker", TEST_VOTER), - log("share", 33), - log("amount", 50), - ] - ); - assert_eq!(res.messages, vec![]); - - // FAIL - already withdrawn - let msg = HandleMsg::StakeVotingRewards {}; - let env = mock_env_height(TEST_VOTER, &[], 0, 10000); - let res = handle(&mut deps, env, msg).unwrap_err(); - assert_eq!(res, StdError::generic_err("Nothing to withdraw")); - - // voting info has been deleted - assert_eq!( - poll_voter_read(&deps.storage, 1u64) - .load( - &deps - .api - .canonical_address(&HumanAddr::from(TEST_VOTER)) - .unwrap() - .as_slice() - ) - .is_err(), - true - ); - - let res = query( - &deps, - QueryMsg::Staker { - address: HumanAddr::from(TEST_VOTER), - }, - ) - .unwrap(); - let response: StakerResponse = from_binary(&res).unwrap(); - assert_eq!( - response, - StakerResponse { - balance: Uint128(stake_amount + 100u128), - share: Uint128(stake_amount + 33u128), - locked_balance: vec![], - pending_voting_rewards: Uint128(0u128), - withdrawable_polls: vec![], - } - ); -} - -#[test] -fn distribute_voting_rewards_with_multiple_active_polls_and_voters() { - let mut deps = mock_dependencies(20, &[]); - let msg = InitMsg { - mirror_token: HumanAddr::from(VOTING_TOKEN), - quorum: Decimal::percent(DEFAULT_QUORUM), - threshold: Decimal::percent(DEFAULT_THRESHOLD), - voting_period: DEFAULT_VOTING_PERIOD, - effective_delay: DEFAULT_EFFECTIVE_DELAY, - expiration_period: DEFAULT_EXPIRATION_PERIOD, - proposal_deposit: Uint128(DEFAULT_PROPOSAL_DEPOSIT), - voter_weight: Decimal::percent(50), // distribute 50% rewards to voters - snapshot_period: DEFAULT_SNAPSHOT_PERIOD, - }; - let env = mock_env(TEST_CREATOR, &[]); - let _res = init(&mut deps, env, msg).expect("contract successfully handles InitMsg"); - - // create polls - let env = mock_env_height(VOTING_TOKEN, &coins(2, VOTING_TOKEN), 0, 10000); - let poll_end_height = env.block.height.clone() + DEFAULT_VOTING_PERIOD; - // poll 1 - let msg = create_poll_msg("test".to_string(), "test".to_string(), None, None); - let _res = handle(&mut deps, env.clone(), msg.clone()).unwrap(); - // poll 2 - let msg = create_poll_msg("test2".to_string(), "test2".to_string(), None, None); - let _res = handle(&mut deps, env.clone(), msg.clone()).unwrap(); - - const ALICE: &str = "alice"; - const ALICE_STAKE: u128 = 750_000_000u128; - const BOB: &str = "bob"; - const BOB_STAKE: u128 = 250_000_000u128; - const CINDY: &str = "cindy"; - const CINDY_STAKE: u128 = 250_000_000u128; - - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128((ALICE_STAKE + DEFAULT_PROPOSAL_DEPOSIT * 2) as u128), - )], - )]); - // Alice stakes 750 MIR - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(ALICE), - amount: Uint128::from(ALICE_STAKE), - msg: Some(to_binary(&Cw20HookMsg::StakeVotingTokens {}).unwrap()), - }); - let env = mock_env(VOTING_TOKEN.to_string(), &[]); - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128((ALICE_STAKE + BOB_STAKE + DEFAULT_PROPOSAL_DEPOSIT * 2) as u128), - )], - )]); - // Bob stakes 250 MIR - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(BOB), - amount: Uint128::from(BOB_STAKE), - msg: Some(to_binary(&Cw20HookMsg::StakeVotingTokens {}).unwrap()), - }); - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128( - (ALICE_STAKE + BOB_STAKE + CINDY_STAKE + DEFAULT_PROPOSAL_DEPOSIT * 2) as u128, - ), - )], - )]); - // Cindy stakes 250 MIR - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(CINDY), - amount: Uint128::from(CINDY_STAKE), - msg: Some(to_binary(&Cw20HookMsg::StakeVotingTokens {}).unwrap()), - }); - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - - // Alice votes on proposal 1 - let msg = HandleMsg::CastVote { - poll_id: 1, - vote: VoteOption::Yes, - amount: Uint128::from(ALICE_STAKE), - }; - let env = mock_env_height(ALICE, &[], 0, 10000); - let _res = handle(&mut deps, env, msg).unwrap(); - // Bob votes on proposals 1 and 2 - let msg = HandleMsg::CastVote { - poll_id: 1, - vote: VoteOption::Abstain, - amount: Uint128::from(BOB_STAKE), - }; - let env = mock_env_height(BOB, &[], 0, 10000); - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - let msg = HandleMsg::CastVote { - poll_id: 2, - vote: VoteOption::No, - amount: Uint128::from(BOB_STAKE), - }; - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - // Cindy votes on proposal 2 - let msg = HandleMsg::CastVote { - poll_id: 2, - vote: VoteOption::Abstain, - amount: Uint128::from(CINDY_STAKE), - }; - let env = mock_env_height(CINDY, &[], 0, 10000); - let _res = handle(&mut deps, env, msg).unwrap(); - - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128( - (ALICE_STAKE - + BOB_STAKE - + CINDY_STAKE - + DEFAULT_PROPOSAL_DEPOSIT * 2 - + 2000000000u128) as u128, - ), - )], - )]); - - // Collector sends 2000 MIR with 50% voting weight - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(TEST_COLLECTOR), - amount: Uint128::from(2000000000u128), - msg: Some(to_binary(&Cw20HookMsg::DepositReward {}).unwrap()), - }); - - let env = mock_env(VOTING_TOKEN.to_string(), &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // End the polls - let env = mock_env_height(TEST_VOTER, &[], poll_end_height, 10000); - let msg = HandleMsg::EndPoll { poll_id: 1 }; - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - let msg = HandleMsg::EndPoll { poll_id: 2 }; - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - - let msg = HandleMsg::WithdrawVotingRewards {}; - // ALICE withdraws voting rewards - let env = mock_env_height(ALICE, &[], 0, 10000); - let res = handle(&mut deps, env, msg.clone()).unwrap(); - assert_eq!( - res.log, - vec![ - log("action", "withdraw_voting_rewards"), - log("recipient", ALICE), - log("amount", 375000000), - ] - ); - - // BOB withdraws voting rewards - let env = mock_env_height(BOB, &[], 0, 10000); - let res = handle(&mut deps, env.clone(), msg.clone()).unwrap(); - assert_eq!( - res.log, - vec![ - log("action", "withdraw_voting_rewards"), - log("recipient", BOB), - log("amount", 375000000), // 125 from poll 1 + 250 from poll 2 - ] - ); - - // CINDY - let env = mock_env_height(CINDY, &[], 0, 10000); - let res = handle(&mut deps, env.clone(), msg.clone()).unwrap(); - assert_eq!( - res.log, - vec![ - log("action", "withdraw_voting_rewards"), - log("recipient", CINDY), - log("amount", 250000000), - ] - ); -} - -#[test] -fn distribute_voting_rewards_only_to_polls_in_progress() { - let mut deps = mock_dependencies(20, &[]); - let msg = InitMsg { - mirror_token: HumanAddr::from(VOTING_TOKEN), - quorum: Decimal::percent(DEFAULT_QUORUM), - threshold: Decimal::percent(DEFAULT_THRESHOLD), - voting_period: DEFAULT_VOTING_PERIOD, - effective_delay: DEFAULT_EFFECTIVE_DELAY, - expiration_period: DEFAULT_EXPIRATION_PERIOD, - proposal_deposit: Uint128(DEFAULT_PROPOSAL_DEPOSIT), - voter_weight: Decimal::percent(50), // distribute 50% rewards to voters - snapshot_period: DEFAULT_SNAPSHOT_PERIOD, - }; - let env = mock_env(TEST_CREATOR, &[]); - let _res = init(&mut deps, env, msg).expect("contract successfully handles InitMsg"); - - // make fake polls; one in progress & one in passed - poll_store(&mut deps.storage) - .save( - &1u64.to_be_bytes(), - &Poll { - id: 1u64, - creator: deps - .api - .canonical_address(&HumanAddr::from(TEST_CREATOR)) - .unwrap(), - status: PollStatus::InProgress, - yes_votes: Uint128::zero(), - no_votes: Uint128::zero(), - abstain_votes: Uint128::zero(), - end_height: 0u64, - title: "title".to_string(), - description: "description".to_string(), - deposit_amount: Uint128::zero(), - link: None, - execute_data: None, - total_balance_at_end_poll: None, - voters_reward: Uint128::zero(), - staked_amount: None, - }, - ) - .unwrap(); - - poll_store(&mut deps.storage) - .save( - &2u64.to_be_bytes(), - &Poll { - id: 2u64, - creator: deps - .api - .canonical_address(&HumanAddr::from(TEST_CREATOR)) - .unwrap(), - status: PollStatus::Passed, - yes_votes: Uint128::zero(), - no_votes: Uint128::zero(), - abstain_votes: Uint128::zero(), - end_height: 0u64, - title: "title".to_string(), - description: "description".to_string(), - deposit_amount: Uint128::zero(), - link: None, - execute_data: None, - total_balance_at_end_poll: None, - voters_reward: Uint128::zero(), - staked_amount: None, - }, - ) - .unwrap(); - - poll_indexer_store(&mut deps.storage, &PollStatus::InProgress) - .save(&1u64.to_be_bytes(), &true) - .unwrap(); - poll_indexer_store(&mut deps.storage, &PollStatus::Passed) - .save(&2u64.to_be_bytes(), &true) - .unwrap(); - - // Collector sends 2000 MIR with 50% voting weight - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(TEST_COLLECTOR), - amount: Uint128::from(2000000000u128), - msg: Some(to_binary(&Cw20HookMsg::DepositReward {}).unwrap()), - }); - - let env = mock_env(VOTING_TOKEN.to_string(), &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - let res = query( - &deps, - QueryMsg::Polls { - filter: None, - start_after: None, - limit: None, - order_by: Some(OrderBy::Asc), - }, - ) - .unwrap(); - let response: PollsResponse = from_binary(&res).unwrap(); - assert_eq!( - response.polls, - vec![ - PollResponse { - id: 1u64, - creator: HumanAddr::from(TEST_CREATOR), - status: PollStatus::InProgress, - yes_votes: Uint128::zero(), - no_votes: Uint128::zero(), - abstain_votes: Uint128::zero(), - end_height: 0u64, - title: "title".to_string(), - description: "description".to_string(), - deposit_amount: Uint128::zero(), - link: None, - execute_data: None, - total_balance_at_end_poll: None, - voters_reward: Uint128::from(1000000000u128), - staked_amount: None, - }, - PollResponse { - id: 2u64, - creator: HumanAddr::from(TEST_CREATOR), - status: PollStatus::Passed, - yes_votes: Uint128::zero(), - no_votes: Uint128::zero(), - abstain_votes: Uint128::zero(), - end_height: 0u64, - title: "title".to_string(), - description: "description".to_string(), - deposit_amount: Uint128::zero(), - link: None, - execute_data: None, - total_balance_at_end_poll: None, - voters_reward: Uint128::zero(), - staked_amount: None, - }, - ] - ); -} - -#[test] -fn test_staking_and_voting_rewards() { - let mut deps = mock_dependencies(20, &[]); - let msg = InitMsg { - mirror_token: HumanAddr::from(VOTING_TOKEN), - quorum: Decimal::percent(DEFAULT_QUORUM), - threshold: Decimal::percent(DEFAULT_THRESHOLD), - voting_period: DEFAULT_VOTING_PERIOD, - effective_delay: DEFAULT_EFFECTIVE_DELAY, - expiration_period: DEFAULT_EXPIRATION_PERIOD, - proposal_deposit: Uint128(DEFAULT_PROPOSAL_DEPOSIT), - voter_weight: Decimal::percent(50), // distribute 50% rewards to voters - snapshot_period: DEFAULT_SNAPSHOT_PERIOD, - }; - let env = mock_env(TEST_CREATOR, &[]); - let _res = init(&mut deps, env, msg).expect("contract successfully handles InitMsg"); - - let env = mock_env_height(VOTING_TOKEN, &coins(2, VOTING_TOKEN), 0, 10000); - let poll_end_height = env.block.height.clone() + DEFAULT_VOTING_PERIOD; - // poll 1 - let msg = create_poll_msg("test".to_string(), "test".to_string(), None, None); - let _res = handle(&mut deps, env.clone(), msg.clone()).unwrap(); - - const ALICE: &str = "alice"; - const ALICE_STAKE: u128 = 750_000_000u128; - const BOB: &str = "bob"; - const BOB_STAKE: u128 = 250_000_000u128; - - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128((ALICE_STAKE + DEFAULT_PROPOSAL_DEPOSIT) as u128), - )], - )]); - // Alice stakes 750 MIR - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(ALICE), - amount: Uint128::from(ALICE_STAKE), - msg: Some(to_binary(&Cw20HookMsg::StakeVotingTokens {}).unwrap()), - }); - let env = mock_env(VOTING_TOKEN.to_string(), &[]); - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128((ALICE_STAKE + BOB_STAKE + DEFAULT_PROPOSAL_DEPOSIT) as u128), - )], - )]); - // Bob stakes 250 MIR - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(BOB), - amount: Uint128::from(BOB_STAKE), - msg: Some(to_binary(&Cw20HookMsg::StakeVotingTokens {}).unwrap()), - }); - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - - // Alice votes - let msg = HandleMsg::CastVote { - poll_id: 1, - vote: VoteOption::Yes, - amount: Uint128::from(ALICE_STAKE), - }; - let env = mock_env_height(ALICE, &[], 0, 10000); - let _res = handle(&mut deps, env, msg).unwrap(); - // Bob votes - let msg = HandleMsg::CastVote { - poll_id: 1, - vote: VoteOption::Abstain, - amount: Uint128::from(BOB_STAKE), - }; - let env = mock_env_height(BOB, &[], 0, 10000); - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128( - (ALICE_STAKE + BOB_STAKE + DEFAULT_PROPOSAL_DEPOSIT + 2_000_000_000u128) as u128, - ), - )], - )]); - - // Collector sends 2000 MIR with 50% voting weight - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(TEST_COLLECTOR), - amount: Uint128::from(2_000_000_000u128), - msg: Some(to_binary(&Cw20HookMsg::DepositReward {}).unwrap()), - }); - - let env = mock_env(VOTING_TOKEN.to_string(), &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // End the poll - let env = mock_env_height(TEST_VOTER, &[], poll_end_height, 10000); - let msg = HandleMsg::EndPoll { poll_id: 1 }; - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - - // deposit is returned to creator and collector deposit is added - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128((ALICE_STAKE + BOB_STAKE + 2_000_000_000) as u128), - )], - )]); - - let res = query(&deps, QueryMsg::State {}).unwrap(); - let response: StateResponse = from_binary(&res).unwrap(); - assert_eq!(response.total_share, Uint128(1_000_000_000u128)); - assert_eq!(response.total_deposit, Uint128::zero()); - assert_eq!(response.pending_voting_rewards, Uint128(1_000_000_000u128)); - - let res = query( - &deps, - QueryMsg::Staker { - address: HumanAddr::from(ALICE), - }, - ) - .unwrap(); - let response: StakerResponse = from_binary(&res).unwrap(); - assert_eq!( - response, - StakerResponse { - balance: Uint128(ALICE_STAKE + 750_000_000u128), - share: Uint128(ALICE_STAKE), - locked_balance: vec![], - pending_voting_rewards: Uint128(750_000_000u128), - withdrawable_polls: vec![ - (1u64, Uint128(750_000_000u128)) - ], - } - ); - let res = query( - &deps, - QueryMsg::Staker { - address: HumanAddr::from(BOB), - }, - ) - .unwrap(); - let response: StakerResponse = from_binary(&res).unwrap(); - assert_eq!( - response, - StakerResponse { - balance: Uint128(BOB_STAKE + 250_000_000u128), - share: Uint128(BOB_STAKE), - locked_balance: vec![], - pending_voting_rewards: Uint128(250_000_000u128), - withdrawable_polls: vec![ - (1u64, Uint128(250_000_000u128)) - ], - } - ); - - let msg = HandleMsg::WithdrawVotingRewards {}; - // ALICE withdraws voting rewards - let env = mock_env_height(ALICE, &[], 0, 10000); - let res = handle(&mut deps, env, msg.clone()).unwrap(); - assert_eq!( - res.log, - vec![ - log("action", "withdraw_voting_rewards"), - log("recipient", ALICE), - log("amount", ALICE_STAKE), - ] - ); - - // BOB withdraws voting rewards - let env = mock_env_height(BOB, &[], 0, 10000); - let res = handle(&mut deps, env.clone(), msg.clone()).unwrap(); - assert_eq!( - res.log, - vec![ - log("action", "withdraw_voting_rewards"), - log("recipient", BOB), - log("amount", BOB_STAKE), - ] - ); - - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128((ALICE_STAKE + BOB_STAKE + 1_000_000_000) as u128), - )], - )]); - - // withdraw remaining voting tokens - let msg = HandleMsg::WithdrawVotingTokens { amount: None }; - let env = mock_env(ALICE.to_string(), &[]); - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!( - res.log, - vec![ - log("action", "withdraw"), - log("recipient", ALICE), - log("amount", "1500000000"), - ] - ); - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128((BOB_STAKE + 250_000_000) as u128), - )], - )]); - // withdraw remaining voting tokens - let msg = HandleMsg::WithdrawVotingTokens { amount: None }; - let env = mock_env(BOB.to_string(), &[]); - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!( - res.log, - vec![ - log("action", "withdraw"), - log("recipient", BOB), - log("amount", "500000000"), - ] - ); -} - -#[test] -fn test_abstain_votes_theshold() { - let mut deps = mock_dependencies(20, &[]); - let msg = InitMsg { - mirror_token: HumanAddr::from(VOTING_TOKEN), - quorum: Decimal::percent(DEFAULT_QUORUM), - threshold: Decimal::percent(DEFAULT_THRESHOLD), - voting_period: DEFAULT_VOTING_PERIOD, - effective_delay: DEFAULT_EFFECTIVE_DELAY, - expiration_period: DEFAULT_EXPIRATION_PERIOD, - proposal_deposit: Uint128(DEFAULT_PROPOSAL_DEPOSIT), - voter_weight: Decimal::percent(50), // distribute 50% rewards to voters - snapshot_period: DEFAULT_SNAPSHOT_PERIOD, - }; - - let env = mock_env(TEST_CREATOR, &[]); - let _res = init(&mut deps, env, msg).expect("contract successfully handles InitMsg"); - - let env = mock_env_height(VOTING_TOKEN, &coins(2, VOTING_TOKEN), 0, 10000); - let poll_end_height = env.block.height.clone() + DEFAULT_VOTING_PERIOD; - let msg = create_poll_msg("test".to_string(), "test".to_string(), None, None); - let _res = handle(&mut deps, env.clone(), msg.clone()).unwrap(); - - const ALICE: &str = "alice"; - const ALICE_STAKE: u128 = 750_000_000u128; - const BOB: &str = "bob"; - const BOB_STAKE: u128 = 250_000_000u128; - const CINDY: &str = "cindy"; - const CINDY_STAKE: u128 = 260_000_000u128; - - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128((ALICE_STAKE + DEFAULT_PROPOSAL_DEPOSIT) as u128), - )], - )]); - // Alice stakes 750 MIR - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(ALICE), - amount: Uint128::from(ALICE_STAKE), - msg: Some(to_binary(&Cw20HookMsg::StakeVotingTokens {}).unwrap()), - }); - let env = mock_env(VOTING_TOKEN.to_string(), &[]); - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128((ALICE_STAKE + BOB_STAKE + DEFAULT_PROPOSAL_DEPOSIT) as u128), - )], - )]); - // Bob stakes 250 MIR - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(BOB), - amount: Uint128::from(BOB_STAKE), - msg: Some(to_binary(&Cw20HookMsg::StakeVotingTokens {}).unwrap()), - }); - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128((ALICE_STAKE + BOB_STAKE + CINDY_STAKE + DEFAULT_PROPOSAL_DEPOSIT) as u128), - )], - )]); - // Cindy stakes 260 MIR - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(CINDY), - amount: Uint128::from(CINDY_STAKE), - msg: Some(to_binary(&Cw20HookMsg::StakeVotingTokens {}).unwrap()), - }); - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - - // Alice votes - let msg = HandleMsg::CastVote { - poll_id: 1, - vote: VoteOption::Abstain, - amount: Uint128::from(ALICE_STAKE), - }; - let env = mock_env_height(ALICE, &[], 0, 10000); - let _res = handle(&mut deps, env, msg).unwrap(); - // Bob votes - let msg = HandleMsg::CastVote { - poll_id: 1, - vote: VoteOption::No, - amount: Uint128::from(BOB_STAKE), - }; - let env = mock_env_height(BOB, &[], 0, 10000); - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - // Cindy votes - let msg = HandleMsg::CastVote { - poll_id: 1, - vote: VoteOption::Yes, - amount: Uint128::from(CINDY_STAKE), - }; - let env = mock_env_height(CINDY, &[], 0, 10000); - let _res = handle(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::EndPoll { poll_id: 1 }; - - let env = mock_env_height(TEST_VOTER, &[], poll_end_height, 10000); - let handle_res = handle(&mut deps, env.clone(), msg.clone()).unwrap(); - // abstain votes should not affect threshold, so poll is passed - assert_eq!( - handle_res.log, - vec![ - log("action", "end_poll"), - log("poll_id", "1"), - log("rejected_reason", ""), - log("passed", "true"), - ] - ); -} - -#[test] -fn test_abstain_votes_quorum() { - let mut deps = mock_dependencies(20, &[]); - let msg = InitMsg { - mirror_token: HumanAddr::from(VOTING_TOKEN), - quorum: Decimal::percent(DEFAULT_QUORUM), - threshold: Decimal::percent(DEFAULT_THRESHOLD), - voting_period: DEFAULT_VOTING_PERIOD, - effective_delay: DEFAULT_EFFECTIVE_DELAY, - expiration_period: DEFAULT_EXPIRATION_PERIOD, - proposal_deposit: Uint128(DEFAULT_PROPOSAL_DEPOSIT), - voter_weight: Decimal::percent(50), // distribute 50% rewards to voters - snapshot_period: DEFAULT_SNAPSHOT_PERIOD, - }; - - let env = mock_env(TEST_CREATOR, &[]); - let _res = init(&mut deps, env, msg).expect("contract successfully handles InitMsg"); - - let env = mock_env_height(VOTING_TOKEN, &coins(2, VOTING_TOKEN), 0, 10000); - let poll_end_height = env.block.height.clone() + DEFAULT_VOTING_PERIOD; - let msg = create_poll_msg("test".to_string(), "test".to_string(), None, None); - let _res = handle(&mut deps, env.clone(), msg.clone()).unwrap(); - - const ALICE: &str = "alice"; - const ALICE_STAKE: u128 = 750_000_000u128; - const BOB: &str = "bob"; - const BOB_STAKE: u128 = 50_000_000u128; - const CINDY: &str = "cindy"; - const CINDY_STAKE: u128 = 20_000_000u128; - - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128((ALICE_STAKE + DEFAULT_PROPOSAL_DEPOSIT) as u128), - )], - )]); - // Alice stakes 750 MIR - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(ALICE), - amount: Uint128::from(ALICE_STAKE), - msg: Some(to_binary(&Cw20HookMsg::StakeVotingTokens {}).unwrap()), - }); - let env = mock_env(VOTING_TOKEN.to_string(), &[]); - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128((ALICE_STAKE + BOB_STAKE + DEFAULT_PROPOSAL_DEPOSIT) as u128), - )], - )]); - // Bob stakes 50 MIR - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(BOB), - amount: Uint128::from(BOB_STAKE), - msg: Some(to_binary(&Cw20HookMsg::StakeVotingTokens {}).unwrap()), - }); - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128((ALICE_STAKE + BOB_STAKE + CINDY_STAKE + DEFAULT_PROPOSAL_DEPOSIT) as u128), - )], - )]); - // Cindy stakes 50 MIR - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(CINDY), - amount: Uint128::from(CINDY_STAKE), - msg: Some(to_binary(&Cw20HookMsg::StakeVotingTokens {}).unwrap()), - }); - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - - // Alice votes - let msg = HandleMsg::CastVote { - poll_id: 1, - vote: VoteOption::Abstain, - amount: Uint128::from(ALICE_STAKE), - }; - let env = mock_env_height(ALICE, &[], 0, 10000); - let _res = handle(&mut deps, env, msg).unwrap(); - // Bob votes - let msg = HandleMsg::CastVote { - poll_id: 1, - vote: VoteOption::Yes, - amount: Uint128::from(BOB_STAKE), - }; - let env = mock_env_height(BOB, &[], 0, 10000); - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - // Cindy votes - let msg = HandleMsg::CastVote { - poll_id: 1, - vote: VoteOption::Yes, - amount: Uint128::from(CINDY_STAKE), - }; - let env = mock_env_height(CINDY, &[], 0, 10000); - let _res = handle(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::EndPoll { poll_id: 1 }; - - let env = mock_env_height(TEST_VOTER, &[], poll_end_height, 10000); - let handle_res = handle(&mut deps, env.clone(), msg.clone()).unwrap(); - // abstain votes make the poll surpass quorum - assert_eq!( - handle_res.log, - vec![ - log("action", "end_poll"), - log("poll_id", "1"), - log("rejected_reason", ""), - log("passed", "true"), - ] - ); - - let env = mock_env_height(VOTING_TOKEN, &coins(2, VOTING_TOKEN), 0, 10000); - let poll_end_height = env.block.height.clone() + DEFAULT_VOTING_PERIOD; - let msg = create_poll_msg("test".to_string(), "test".to_string(), None, None); - let _res = handle(&mut deps, env.clone(), msg.clone()).unwrap(); - - // Alice doesn't vote - - // Bob votes - let msg = HandleMsg::CastVote { - poll_id: 2, - vote: VoteOption::Yes, - amount: Uint128::from(BOB_STAKE), - }; - let env = mock_env_height(BOB, &[], 0, 10000); - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - // Cindy votes - let msg = HandleMsg::CastVote { - poll_id: 2, - vote: VoteOption::Yes, - amount: Uint128::from(CINDY_STAKE), - }; - let env = mock_env_height(CINDY, &[], 0, 10000); - let _res = handle(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::EndPoll { poll_id: 2 }; - - let env = mock_env_height(TEST_VOTER, &[], poll_end_height, 10000); - let handle_res = handle(&mut deps, env.clone(), msg.clone()).unwrap(); - // without abstain votes, quroum is not reached - assert_eq!( - handle_res.log, - vec![ - log("action", "end_poll"), - log("poll_id", "2"), - log("rejected_reason", "Quorum not reached"), - log("passed", "false"), - ] - ); -} - -#[test] -fn snapshot_poll() { - let stake_amount = 1000; - - let mut deps = mock_dependencies(20, &coins(100, VOTING_TOKEN)); - mock_init(&mut deps); - - let msg = create_poll_msg("test".to_string(), "test".to_string(), None, None); - let mut creator_env = mock_env(VOTING_TOKEN, &vec![]); - let handle_res = handle(&mut deps, creator_env.clone(), msg.clone()).unwrap(); - assert_eq!( - handle_res.log, - vec![ - log("action", "create_poll"), - log("creator", TEST_CREATOR), - log("poll_id", "1"), - log("end_height", "22345"), - ] - ); - - //must not be executed - let snapshot_err = handle( - &mut deps, - creator_env.clone(), - HandleMsg::SnapshotPoll { poll_id: 1 }, - ) - .unwrap_err(); - assert_eq!( - StdError::generic_err("Cannot snapshot at this height",), - snapshot_err - ); - - // change time - creator_env.block.height = 22345 - 10; - - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128((stake_amount + DEFAULT_PROPOSAL_DEPOSIT) as u128), - )], - )]); - - let fix_res = handle( - &mut deps, - creator_env.clone(), - HandleMsg::SnapshotPoll { poll_id: 1 }, - ) - .unwrap(); - - assert_eq!( - fix_res.log, - vec![ - log("action", "snapshot_poll"), - log("poll_id", "1"), - log("staked_amount", stake_amount), - ] - ); - - //must not be executed - let snapshot_error = handle( - &mut deps, - creator_env.clone(), - HandleMsg::SnapshotPoll { poll_id: 1 }, - ) - .unwrap_err(); - assert_eq!( - StdError::generic_err("Snapshot has already occurred"), - snapshot_error - ); -} - -#[test] -fn happy_days_cast_vote_with_snapshot() { - let mut deps = mock_dependencies(20, &[]); - mock_init(&mut deps); - - let env = mock_env_height(VOTING_TOKEN, &vec![], 0, 10000); - let msg = create_poll_msg("test".to_string(), "test".to_string(), None, None); - - let handle_res = handle(&mut deps, env, msg.clone()).unwrap(); - assert_create_poll_result( - 1, - DEFAULT_VOTING_PERIOD, - TEST_CREATOR, - handle_res, - &mut deps, - ); - - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128(11u128 + DEFAULT_PROPOSAL_DEPOSIT), - )], - )]); - - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(TEST_VOTER), - amount: Uint128::from(11u128), - msg: Some(to_binary(&Cw20HookMsg::StakeVotingTokens {}).unwrap()), - }); - - let env = mock_env(VOTING_TOKEN, &[]); - let handle_res = handle(&mut deps, env, msg.clone()).unwrap(); - assert_stake_tokens_result(11, DEFAULT_PROPOSAL_DEPOSIT, 11, 1, handle_res, &mut deps); - - //cast_vote without snapshot - let env = mock_env_height(TEST_VOTER, &coins(11, VOTING_TOKEN), 0, 10000); - let amount = 10u128; - - let msg = HandleMsg::CastVote { - poll_id: 1, - vote: VoteOption::Yes, - amount: Uint128::from(amount), - }; - - let handle_res = handle(&mut deps, env, msg.clone()).unwrap(); - assert_cast_vote_success(TEST_VOTER, amount, 1, VoteOption::Yes, handle_res); - - // balance be double - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128(22u128 + DEFAULT_PROPOSAL_DEPOSIT), - )], - )]); - - let res = query(&deps, QueryMsg::Poll { poll_id: 1 }).unwrap(); - let value: PollResponse = from_binary(&res).unwrap(); - assert_eq!(value.staked_amount, None); - let end_height = value.end_height; - - //cast another vote - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(TEST_VOTER_2), - amount: Uint128::from(11u128), - msg: Some(to_binary(&Cw20HookMsg::StakeVotingTokens {}).unwrap()), - }); - - let env = mock_env(VOTING_TOKEN, &[]); - let _handle_res = handle(&mut deps, env, msg.clone()).unwrap(); - - // another voter cast a vote - let msg = HandleMsg::CastVote { - poll_id: 1, - vote: VoteOption::Yes, - amount: Uint128::from(10u128), - }; - let env = mock_env_height(TEST_VOTER_2, &[], end_height - 9, 10000); - let handle_res = handle(&mut deps, env.clone(), msg).unwrap(); - assert_cast_vote_success(TEST_VOTER_2, amount, 1, VoteOption::Yes, handle_res); - - let res = query(&deps, QueryMsg::Poll { poll_id: 1 }).unwrap(); - let value: PollResponse = from_binary(&res).unwrap(); - assert_eq!(value.staked_amount, Some(Uint128(22))); - - // snanpshot poll will not go through - let snap_error = handle( - &mut deps, - env.clone(), - HandleMsg::SnapshotPoll { poll_id: 1 }, - ) - .unwrap_err(); - assert_eq!( - StdError::generic_err("Snapshot has already occurred"), - snap_error - ); - - // balance be double - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128(33u128 + DEFAULT_PROPOSAL_DEPOSIT), - )], - )]); - - // another voter cast a vote but the snapshot is already occurred - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(TEST_VOTER_3), - amount: Uint128::from(11u128), - msg: Some(to_binary(&Cw20HookMsg::StakeVotingTokens {}).unwrap()), - }); - - let env = mock_env(VOTING_TOKEN, &[]); - let _handle_res = handle(&mut deps, env, msg.clone()).unwrap(); - let msg = HandleMsg::CastVote { - poll_id: 1, - vote: VoteOption::Yes, - amount: Uint128::from(10u128), - }; - let env = mock_env_height(TEST_VOTER_3, &[], end_height - 8, 10000); - let handle_res = handle(&mut deps, env.clone(), msg).unwrap(); - assert_cast_vote_success(TEST_VOTER_3, amount, 1, VoteOption::Yes, handle_res); - - let res = query(&deps, QueryMsg::Poll { poll_id: 1 }).unwrap(); - let value: PollResponse = from_binary(&res).unwrap(); - assert_eq!(value.staked_amount, Some(Uint128(22))); -} - -#[test] -fn fails_end_poll_quorum_inflation_without_snapshot_poll() { - const POLL_START_HEIGHT: u64 = 1000; - const POLL_ID: u64 = 1; - let stake_amount = 1000; - - let mut deps = mock_dependencies(20, &coins(1000, VOTING_TOKEN)); - mock_init(&mut deps); - - let mut creator_env = mock_env_height( - VOTING_TOKEN, - &coins(2, VOTING_TOKEN), - POLL_START_HEIGHT, - 10000, - ); - - let exec_msg_bz = to_binary(&Cw20HandleMsg::Burn { - amount: Uint128(123), - }) - .unwrap(); - - let msg = create_poll_msg( - "test".to_string(), - "test".to_string(), - None, - Some(ExecuteMsg { - contract: HumanAddr::from(VOTING_TOKEN), - msg: exec_msg_bz.clone(), - }), - ); - - let handle_res = handle(&mut deps, creator_env.clone(), msg).unwrap(); - - assert_create_poll_result( - 1, - creator_env.block.height + DEFAULT_VOTING_PERIOD, - TEST_CREATOR, - handle_res, - &mut deps, - ); - - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128((stake_amount + DEFAULT_PROPOSAL_DEPOSIT) as u128), - )], - )]); - - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(TEST_VOTER), - amount: Uint128::from(stake_amount as u128), - msg: Some(to_binary(&Cw20HookMsg::StakeVotingTokens {}).unwrap()), - }); - - let env = mock_env(VOTING_TOKEN, &[]); - let handle_res = handle(&mut deps, env, msg.clone()).unwrap(); - assert_stake_tokens_result( - stake_amount, - DEFAULT_PROPOSAL_DEPOSIT, - stake_amount, - 1, - handle_res, - &mut deps, - ); - - let msg = HandleMsg::CastVote { - poll_id: 1, - vote: VoteOption::Yes, - amount: Uint128::from(stake_amount), - }; - let env = mock_env_height(TEST_VOTER, &[], POLL_START_HEIGHT, 10000); - let handle_res = handle(&mut deps, env, msg).unwrap(); - - assert_eq!( - handle_res.log, - vec![ - log("action", "cast_vote"), - log("poll_id", POLL_ID), - log("amount", "1000"), - log("voter", TEST_VOTER), - log("vote_option", "yes"), - ] - ); - - creator_env.block.height = &creator_env.block.height + DEFAULT_VOTING_PERIOD - 10; - - // did not SnapshotPoll - - // staked amount get increased 10 times - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128(((10 * stake_amount) + DEFAULT_PROPOSAL_DEPOSIT) as u128), - )], - )]); - - //cast another vote - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(TEST_VOTER_2), - amount: Uint128::from(8 * stake_amount as u128), - msg: Some(to_binary(&Cw20HookMsg::StakeVotingTokens {}).unwrap()), - }); - - let env = mock_env(VOTING_TOKEN, &[]); - let _handle_res = handle(&mut deps, env, msg.clone()).unwrap(); - - // another voter cast a vote - let msg = HandleMsg::CastVote { - poll_id: 1, - vote: VoteOption::Yes, - amount: Uint128::from(stake_amount), - }; - let env = mock_env_height(TEST_VOTER_2, &[], creator_env.block.height, 10000); - let handle_res = handle(&mut deps, env, msg).unwrap(); - - assert_eq!( - handle_res.log, - vec![ - log("action", "cast_vote"), - log("poll_id", POLL_ID), - log("amount", "1000"), - log("voter", TEST_VOTER_2), - log("vote_option", "yes"), - ] - ); - - creator_env.message.sender = HumanAddr::from(TEST_CREATOR); - creator_env.block.height += 10; - - // quorum must reach - let msg = HandleMsg::EndPoll { poll_id: 1 }; - let handle_res = handle(&mut deps, creator_env.clone(), msg).unwrap(); - - assert_eq!( - handle_res.log, - vec![ - log("action", "end_poll"), - log("poll_id", "1"), - log("rejected_reason", "Quorum not reached"), - log("passed", "false"), - ] - ); - - let res = query(&deps, QueryMsg::Poll { poll_id: 1 }).unwrap(); - let value: PollResponse = from_binary(&res).unwrap(); - assert_eq!( - 10 * stake_amount, - value.total_balance_at_end_poll.unwrap().u128() - ); -} - -#[test] -fn happy_days_end_poll_with_controlled_quorum() { - const POLL_START_HEIGHT: u64 = 1000; - const POLL_ID: u64 = 1; - let stake_amount = 1000; - - let mut deps = mock_dependencies(20, &coins(1000, VOTING_TOKEN)); - mock_init(&mut deps); - - let mut creator_env = mock_env_height( - VOTING_TOKEN, - &coins(2, VOTING_TOKEN), - POLL_START_HEIGHT, - 10000, - ); - - let exec_msg_bz = to_binary(&Cw20HandleMsg::Burn { - amount: Uint128(123), - }) - .unwrap(); - - let msg = create_poll_msg( - "test".to_string(), - "test".to_string(), - None, - Some(ExecuteMsg { - contract: HumanAddr::from(VOTING_TOKEN), - msg: exec_msg_bz.clone(), - }), - ); - - let handle_res = handle(&mut deps, creator_env.clone(), msg).unwrap(); - - assert_create_poll_result( - 1, - creator_env.block.height + DEFAULT_VOTING_PERIOD, - TEST_CREATOR, - handle_res, - &mut deps, - ); - - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128((stake_amount + DEFAULT_PROPOSAL_DEPOSIT) as u128), - )], - )]); - - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(TEST_VOTER), - amount: Uint128::from(stake_amount as u128), - msg: Some(to_binary(&Cw20HookMsg::StakeVotingTokens {}).unwrap()), - }); - - let env = mock_env(VOTING_TOKEN, &[]); - let handle_res = handle(&mut deps, env, msg.clone()).unwrap(); - assert_stake_tokens_result( - stake_amount, - DEFAULT_PROPOSAL_DEPOSIT, - stake_amount, - 1, - handle_res, - &mut deps, - ); - - let msg = HandleMsg::CastVote { - poll_id: 1, - vote: VoteOption::Yes, - amount: Uint128::from(stake_amount), - }; - let env = mock_env_height(TEST_VOTER, &[], POLL_START_HEIGHT, 10000); - let handle_res = handle(&mut deps, env, msg).unwrap(); - - assert_eq!( - handle_res.log, - vec![ - log("action", "cast_vote"), - log("poll_id", POLL_ID), - log("amount", "1000"), - log("voter", TEST_VOTER), - log("vote_option", "yes"), - ] - ); - - creator_env.block.height = &creator_env.block.height + DEFAULT_VOTING_PERIOD - 10; - - // send SnapshotPoll - let fix_res = handle( - &mut deps, - creator_env.clone(), - HandleMsg::SnapshotPoll { poll_id: 1 }, - ) - .unwrap(); - - assert_eq!( - fix_res.log, - vec![ - log("action", "snapshot_poll"), - log("poll_id", "1"), - log("staked_amount", stake_amount), - ] - ); - - // staked amount get increased 10 times - deps.querier.with_token_balances(&[( - &HumanAddr::from(VOTING_TOKEN), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128(((10 * stake_amount) + DEFAULT_PROPOSAL_DEPOSIT) as u128), - )], - )]); - - //cast another vote - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from(TEST_VOTER_2), - amount: Uint128::from(8 * stake_amount as u128), - msg: Some(to_binary(&Cw20HookMsg::StakeVotingTokens {}).unwrap()), - }); - - let env = mock_env(VOTING_TOKEN, &[]); - let _handle_res = handle(&mut deps, env, msg.clone()).unwrap(); - - let msg = HandleMsg::CastVote { - poll_id: 1, - vote: VoteOption::Yes, - amount: Uint128::from(8 * stake_amount), - }; - let env = mock_env_height(TEST_VOTER_2, &[], creator_env.block.height, 10000); - let handle_res = handle(&mut deps, env, msg).unwrap(); - - assert_eq!( - handle_res.log, - vec![ - log("action", "cast_vote"), - log("poll_id", POLL_ID), - log("amount", "8000"), - log("voter", TEST_VOTER_2), - log("vote_option", "yes"), - ] - ); - - creator_env.message.sender = HumanAddr::from(TEST_CREATOR); - creator_env.block.height += 10; - - // quorum must reach - let msg = HandleMsg::EndPoll { poll_id: 1 }; - let handle_res = handle(&mut deps, creator_env.clone(), msg).unwrap(); - - assert_eq!( - handle_res.log, - vec![ - log("action", "end_poll"), - log("poll_id", "1"), - log("rejected_reason", ""), - log("passed", "true"), - ] - ); - assert_eq!( - handle_res.messages, - vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from(VOTING_TOKEN), - msg: to_binary(&Cw20HandleMsg::Transfer { - recipient: HumanAddr::from(TEST_CREATOR), - amount: Uint128(DEFAULT_PROPOSAL_DEPOSIT), - }) - .unwrap(), - send: vec![], - })] - ); - - let res = query(&deps, QueryMsg::Poll { poll_id: 1 }).unwrap(); - let value: PollResponse = from_binary(&res).unwrap(); - assert_eq!( - stake_amount, - value.total_balance_at_end_poll.unwrap().u128() - ); - - assert_eq!(value.yes_votes.u128(), 9 * stake_amount); - - // actual staked amount is 10 times bigger than staked amount - let actual_staked_weight = (load_token_balance( - &deps, - &HumanAddr::from(VOTING_TOKEN), - &deps - .api - .canonical_address(&HumanAddr::from(MOCK_CONTRACT_ADDR)) - .unwrap(), - ) - .unwrap() - - Uint128(DEFAULT_PROPOSAL_DEPOSIT)) - .unwrap(); - - assert_eq!(actual_staked_weight.u128(), (10 * stake_amount)) -} diff --git a/contracts/mirror_gov/tests/integration.rs b/contracts/mirror_gov/tests/integration.rs deleted file mode 100644 index 8cb2d0a2c..000000000 --- a/contracts/mirror_gov/tests/integration.rs +++ /dev/null @@ -1,206 +0,0 @@ -//! This integration test tries to run and call the generated wasm. -//! It depends on a Wasm build being available, which you can create with `cargo wasm`. -//! Then running `cargo integration-test` will validate we can properly call into that generated Wasm. -//! -//! You can easily convert unit tests to integration tests as follows: -//! 1. Copy them over verbatim -//! 2. Then change -//! let mut deps = mock_dependencies(20, &[]); -//! to -//! let mut deps = mock_instance(WASM, &[]); -//! // now you don't mock_init anymore -//! 3. If you access raw storage, where ever you see something like: -//! deps.storage.get(CONFIG_KEY).expect("no data stored"); -//! replace it with: -//! deps.with_storage(|store| { -//! let data = store.get(CONFIG_KEY).expect("no data stored"); -//! //... -//! }); -//! 4. Anywhere you see query(&deps, ...) you must replace it with query(&mut deps, ...) - -use cosmwasm_std::{ - coins, from_binary, Coin, Decimal, HandleResponse, HandleResult, HumanAddr, InitResponse, - StdError, Uint128, -}; -use cosmwasm_storage::to_length_prefixed; -use cosmwasm_vm::testing::{ - handle, init, mock_dependencies, mock_env, query, MockApi, MockQuerier, MockStorage, -}; -use cosmwasm_vm::Instance; -use cosmwasm_vm::{from_slice, Api, Storage}; - -use mirror_gov::state::Config; -use mirror_protocol::gov::{ConfigResponse, HandleMsg, InitMsg, QueryMsg}; - -// This line will test the output of cargo wasm -static WASM: &[u8] = - include_bytes!("../../../target/wasm32-unknown-unknown/release/mirror_gov.wasm"); -// You can uncomment this line instead to test productionified build from rust-optimizer -// static WASM: &[u8] = include_bytes!("../contract.wasm"); - -const DEFAULT_GAS_LIMIT: u64 = 500_000; - -pub fn mock_instance( - wasm: &[u8], - contract_balance: &[Coin], -) -> Instance { - // TODO: check_wasm is not exported from cosmwasm_vm - // let terra_features = features_from_csv("staking,terra"); - // check_wasm(wasm, &terra_features).unwrap(); - let deps = mock_dependencies(20, contract_balance); - Instance::from_code(wasm, deps, DEFAULT_GAS_LIMIT).unwrap() -} - -const VOTING_TOKEN: &str = "voting_token"; -const TEST_CREATOR: &str = "creator"; -const DEFAULT_QUORUM: u64 = 30u64; -const DEFAULT_THRESHOLD: u64 = 50u64; -const DEFAULT_VOTING_PERIOD: u64 = 10000u64; -const DEFAULT_EFFECTIVE_DELAY: u64 = 10000u64; -const DEFAULT_EXPIRATION_PERIOD: u64 = 20000u64; -const DEFAULT_PROPOSAL_DEPOSIT: u128 = 10000000000u128; -const DEFAULT_VOTER_WEIGHT: u64 = 50u64; -const DEFAULT_SNAPSHOT_PERIOD: u64 = 100u64; - -fn init_msg() -> InitMsg { - InitMsg { - mirror_token: HumanAddr::from(VOTING_TOKEN), - quorum: Decimal::percent(DEFAULT_QUORUM), - threshold: Decimal::percent(DEFAULT_THRESHOLD), - voting_period: DEFAULT_VOTING_PERIOD, - effective_delay: DEFAULT_EFFECTIVE_DELAY, - expiration_period: DEFAULT_EXPIRATION_PERIOD, - proposal_deposit: Uint128(DEFAULT_PROPOSAL_DEPOSIT), - voter_weight: Decimal::percent(DEFAULT_VOTER_WEIGHT), - snapshot_period: DEFAULT_SNAPSHOT_PERIOD, - } -} - -#[test] -fn proper_initialization() { - let mut deps = mock_instance(WASM, &[]); - - let msg = init_msg(); - let env = mock_env( - &HumanAddr(TEST_CREATOR.to_string()), - &coins(2, VOTING_TOKEN), - ); - let res: InitResponse = init(&mut deps, env, msg).unwrap(); - assert_eq!(0, res.messages.len()); - - let api = deps.api; - - deps.with_storage(|store| { - let config_key_raw = to_length_prefixed(b"config"); - let state: Config = from_slice(&store.get(&config_key_raw).0.unwrap().unwrap()).unwrap(); - assert_eq!( - state, - Config { - mirror_token: api - .canonical_address(&HumanAddr::from(VOTING_TOKEN)) - .0 - .unwrap(), - owner: api - .canonical_address(&HumanAddr::from(TEST_CREATOR)) - .0 - .unwrap(), - quorum: Decimal::percent(DEFAULT_QUORUM), - threshold: Decimal::percent(DEFAULT_THRESHOLD), - voting_period: DEFAULT_VOTING_PERIOD, - effective_delay: DEFAULT_EFFECTIVE_DELAY, - expiration_period: DEFAULT_EXPIRATION_PERIOD, - proposal_deposit: Uint128(DEFAULT_PROPOSAL_DEPOSIT), - voter_weight: Decimal::percent(DEFAULT_VOTER_WEIGHT), - snapshot_period: DEFAULT_SNAPSHOT_PERIOD, - } - ); - Ok(()) - }) - .unwrap(); -} - -#[test] -fn update_config() { - let mut deps = mock_instance(WASM, &[]); - let msg = init_msg(); - let env = mock_env( - &HumanAddr(TEST_CREATOR.to_string()), - &coins(2, VOTING_TOKEN), - ); - let res: InitResponse = init(&mut deps, env, msg).unwrap(); - assert_eq!(0, res.messages.len()); - - // update owner - let env = mock_env(TEST_CREATOR, &[]); - let msg = HandleMsg::UpdateConfig { - owner: Some(HumanAddr("addr0001".to_string())), - quorum: None, - threshold: None, - voting_period: None, - effective_delay: None, - expiration_period: None, - proposal_deposit: None, - voter_weight: None, - snapshot_period: None, - }; - - let res: HandleResponse = handle(&mut deps, env, msg).unwrap(); - assert_eq!(0, res.messages.len()); - - // it worked, let's query the state - let res = query(&mut deps, QueryMsg::Config {}).unwrap(); - let config: ConfigResponse = from_binary(&res).unwrap(); - assert_eq!("addr0001", config.owner.as_str()); - assert_eq!(Decimal::percent(DEFAULT_QUORUM), config.quorum); - assert_eq!(Decimal::percent(DEFAULT_THRESHOLD), config.threshold); - assert_eq!(DEFAULT_VOTING_PERIOD, config.voting_period); - assert_eq!(DEFAULT_PROPOSAL_DEPOSIT, config.proposal_deposit.u128()); - - // update left items - let env = mock_env("addr0001", &[]); - let msg = HandleMsg::UpdateConfig { - owner: None, - quorum: Some(Decimal::percent(20)), - threshold: Some(Decimal::percent(75)), - voting_period: Some(20000u64), - effective_delay: Some(20000u64), - expiration_period: Some(30000u64), - proposal_deposit: Some(Uint128(123u128)), - voter_weight: Some(Decimal::percent(30u64)), - snapshot_period: Some(110u64), - }; - - let res: HandleResponse = handle(&mut deps, env, msg).unwrap(); - assert_eq!(0, res.messages.len()); - - // it worked, let's query the state - let res = query(&mut deps, QueryMsg::Config {}).unwrap(); - let config: ConfigResponse = from_binary(&res).unwrap(); - assert_eq!("addr0001", config.owner.as_str()); - assert_eq!(Decimal::percent(20), config.quorum); - assert_eq!(Decimal::percent(75), config.threshold); - assert_eq!(20000u64, config.voting_period); - assert_eq!(123u128, config.proposal_deposit.u128()); - assert_eq!(Decimal::percent(30u64), config.voter_weight); - assert_eq!(110u64, config.snapshot_period); - - // Unauthorized err - let env = mock_env(TEST_CREATOR, &[]); - let msg = HandleMsg::UpdateConfig { - owner: None, - quorum: None, - threshold: None, - voting_period: None, - effective_delay: None, - expiration_period: None, - proposal_deposit: None, - voter_weight: None, - snapshot_period: None, - }; - - let res: HandleResult = handle(&mut deps, env, msg); - match res { - Err(StdError::Unauthorized { .. }) => {} - _ => panic!("Must return unauthorized error"), - } -} diff --git a/contracts/mirror_limit_order/.cargo/config b/contracts/mirror_limit_order/.cargo/config deleted file mode 100644 index 7c115322a..000000000 --- a/contracts/mirror_limit_order/.cargo/config +++ /dev/null @@ -1,6 +0,0 @@ -[alias] -wasm = "build --release --target wasm32-unknown-unknown" -wasm-debug = "build --target wasm32-unknown-unknown" -unit-test = "test --lib --features backtraces" -integration-test = "test --test integration" -schema = "run --example schema" diff --git a/contracts/mirror_limit_order/.editorconfig b/contracts/mirror_limit_order/.editorconfig deleted file mode 100644 index 3d36f20b1..000000000 --- a/contracts/mirror_limit_order/.editorconfig +++ /dev/null @@ -1,11 +0,0 @@ -root = true - -[*] -indent_style = space -indent_size = 2 -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true - -[*.rs] -indent_size = 4 diff --git a/contracts/mirror_limit_order/.gitignore b/contracts/mirror_limit_order/.gitignore deleted file mode 100644 index 10fe5d615..000000000 --- a/contracts/mirror_limit_order/.gitignore +++ /dev/null @@ -1,12 +0,0 @@ -# Build results -/target - -# Text file backups -**/*.rs.bk - -# macOS -.DS_Store - -# IDEs -*.iml -.idea diff --git a/contracts/mirror_limit_order/README.md b/contracts/mirror_limit_order/README.md deleted file mode 100644 index dba83923d..000000000 --- a/contracts/mirror_limit_order/README.md +++ /dev/null @@ -1,129 +0,0 @@ -# Mirror Limit Order - - - -The Limit Order Contract is to provide limit order interface to a bidder and also provide arbitrage opportunity to a market maker. - -## Use Cases - -1. UST -> ASSET -``` -Order -+-----------------+ Terraswap Price (UST-mAAPL) -| OrderId 1 | +------------------------+ -| Offer 100 UST | | price 95 UST : 1 mAAPL | -| Ask 1 mAAPL | +------------------------+ -+-----------------+ | - ^ | - | +-------------+ | - +----- | Arbitrageur |<----+ - Sell +-------------+ Buy - -``` - -2. ASSET => UST - -``` -Order -+-----------------+ Terraswap Price (UST-mAAPL) -| OrderId 1 | +-------------------------+ -| Offer 1 mAAPL | | price 110 UST : 1 mAAPL | -| Ask 100 UST | +-------------------------+ -+-----------------+ ^ - | | - | +-------------+ | - +-----> | Arbitrageur |-----+ - Buy +-------------+ Sell -``` - -## Handlers -### Submit Order - -Depends on the offer asset type - -* Native Token - ``` - MsgExecuteContract( - 'limit_order_contract_addr', - [Coin('denom', 'amount')], - base64(SubmitOrder { - offer_asset: Asset, - ask_asset: Asset, - }) - ) - ``` - -* Token - ``` - MsgExecuteContract( - 'token_contract', - [], - base64(Send { - contract_addr: 'limit_order_contract_addr', - amount: 'amount', - msg: Some(base64(SubmitOrder { - ask_asset: Asset, - })), - }) - ) - ``` - -### Cancel Order -``` -MsgExecuteContract( - 'limit_order_contract_addr', - [], - base64(CancelOrder { - order_id: u64, - }) -) -``` - -### Execute Order - -> Order can be executed partially - -Depends on the `ask asset`(= `execute asset`) type - -* Native Token - ``` - MsgExecuteContract( - 'limit_order_contract_addr', - [Coin('denom', 'amount')], - base64(ExecuteOrder { - execute_asset: Asset, - order_id: u64, - }) - ) - ``` - -* Token - ``` - MsgExecuteContract( - 'token_contract', - [], - base64(Send { - contract_addr: 'limit_order_contract_addr', - amount: 'amount', - msg: Some(base64(ExecuteOrder { - order_id: u64, - })), - }) - ) - ``` - -# Query Orders - -* Query a order - * https://lcd.terra.dev/wasm/contracts/`limit_order_contract`/store?query_msg={"order":{"order_id": 100}} - -* Query orders - * Query with bidder address - * https://lcd.terra.dev/wasm/contracts/`limit_order_contract`/store?query_msg={"orders":{"bidder_addr": "terra~"}} - * https://lcd.terra.dev/wasm/contracts/`limit_order_contract`/store?query_msg={"orders":{"bidder_addr": "terra~", "start_after": 50, "limit": 10, "order_by": "desc"}} - * Query without filter - * https://lcd.terra.dev/wasm/contracts/`limit_order_contract`/store?query_msg={"orders":{}} - * https://lcd.terra.dev/wasm/contracts/`limit_order_contract`/store?query_msg={"orders":{"start_after": 50, "limit": 10, "order_by": "desc"}} - -* Query last order id - * https://lcd.terra.dev/wasm/contracts/`limit_order_contract`/store?query_msg={"last_order_id":{}} diff --git a/contracts/mirror_limit_order/examples/schema.rs b/contracts/mirror_limit_order/examples/schema.rs deleted file mode 100644 index b32b061ca..000000000 --- a/contracts/mirror_limit_order/examples/schema.rs +++ /dev/null @@ -1,21 +0,0 @@ -use std::env::current_dir; -use std::fs::create_dir_all; - -use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; -use mirror_protocol::limit_order::{ - Cw20HookMsg, HandleMsg, InitMsg, OrderResponse, OrdersResponse, QueryMsg, -}; - -fn main() { - let mut out_dir = current_dir().unwrap(); - out_dir.push("schema"); - create_dir_all(&out_dir).unwrap(); - remove_schemas(&out_dir).unwrap(); - - export_schema(&schema_for!(InitMsg), &out_dir); - export_schema(&schema_for!(HandleMsg), &out_dir); - export_schema(&schema_for!(Cw20HookMsg), &out_dir); - export_schema(&schema_for!(QueryMsg), &out_dir); - export_schema(&schema_for!(OrderResponse), &out_dir); - export_schema(&schema_for!(OrdersResponse), &out_dir); -} diff --git a/contracts/mirror_limit_order/schema/cw20_hook_msg.json b/contracts/mirror_limit_order/schema/cw20_hook_msg.json deleted file mode 100644 index dde1626ad..000000000 --- a/contracts/mirror_limit_order/schema/cw20_hook_msg.json +++ /dev/null @@ -1,112 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Cw20HookMsg", - "anyOf": [ - { - "type": "object", - "required": [ - "submit_order" - ], - "properties": { - "submit_order": { - "type": "object", - "required": [ - "ask_asset" - ], - "properties": { - "ask_asset": { - "$ref": "#/definitions/Asset" - } - } - } - } - }, - { - "description": "Arbitrager execute order to get profit", - "type": "object", - "required": [ - "execute_order" - ], - "properties": { - "execute_order": { - "type": "object", - "required": [ - "order_id" - ], - "properties": { - "order_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - } - } - } - ], - "definitions": { - "Asset": { - "type": "object", - "required": [ - "amount", - "info" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "info": { - "$ref": "#/definitions/AssetInfo" - } - } - }, - "AssetInfo": { - "anyOf": [ - { - "type": "object", - "required": [ - "token" - ], - "properties": { - "token": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "$ref": "#/definitions/HumanAddr" - } - } - } - } - }, - { - "type": "object", - "required": [ - "native_token" - ], - "properties": { - "native_token": { - "type": "object", - "required": [ - "denom" - ], - "properties": { - "denom": { - "type": "string" - } - } - } - } - } - ] - }, - "HumanAddr": { - "type": "string" - }, - "Uint128": { - "type": "string" - } - } -} diff --git a/contracts/mirror_limit_order/schema/handle_msg.json b/contracts/mirror_limit_order/schema/handle_msg.json deleted file mode 100644 index 1076c9983..000000000 --- a/contracts/mirror_limit_order/schema/handle_msg.json +++ /dev/null @@ -1,183 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "HandleMsg", - "anyOf": [ - { - "type": "object", - "required": [ - "receive" - ], - "properties": { - "receive": { - "$ref": "#/definitions/Cw20ReceiveMsg" - } - } - }, - { - "description": "User Operations ///", - "type": "object", - "required": [ - "submit_order" - ], - "properties": { - "submit_order": { - "type": "object", - "required": [ - "ask_asset", - "offer_asset" - ], - "properties": { - "ask_asset": { - "$ref": "#/definitions/Asset" - }, - "offer_asset": { - "$ref": "#/definitions/Asset" - } - } - } - } - }, - { - "type": "object", - "required": [ - "cancel_order" - ], - "properties": { - "cancel_order": { - "type": "object", - "required": [ - "order_id" - ], - "properties": { - "order_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - } - } - }, - { - "description": "Arbitrager execute order to get profit", - "type": "object", - "required": [ - "execute_order" - ], - "properties": { - "execute_order": { - "type": "object", - "required": [ - "execute_asset", - "order_id" - ], - "properties": { - "execute_asset": { - "$ref": "#/definitions/Asset" - }, - "order_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - } - } - } - ], - "definitions": { - "Asset": { - "type": "object", - "required": [ - "amount", - "info" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "info": { - "$ref": "#/definitions/AssetInfo" - } - } - }, - "AssetInfo": { - "anyOf": [ - { - "type": "object", - "required": [ - "token" - ], - "properties": { - "token": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "$ref": "#/definitions/HumanAddr" - } - } - } - } - }, - { - "type": "object", - "required": [ - "native_token" - ], - "properties": { - "native_token": { - "type": "object", - "required": [ - "denom" - ], - "properties": { - "denom": { - "type": "string" - } - } - } - } - } - ] - }, - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", - "type": "string" - }, - "Cw20ReceiveMsg": { - "description": "Cw20ReceiveMsg should be de/serialized under `Receive()` variant in a HandleMsg", - "type": "object", - "required": [ - "amount", - "sender" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "msg": { - "anyOf": [ - { - "$ref": "#/definitions/Binary" - }, - { - "type": "null" - } - ] - }, - "sender": { - "$ref": "#/definitions/HumanAddr" - } - } - }, - "HumanAddr": { - "type": "string" - }, - "Uint128": { - "type": "string" - } - } -} diff --git a/contracts/mirror_limit_order/schema/init_msg.json b/contracts/mirror_limit_order/schema/init_msg.json deleted file mode 100644 index 2b274b4e6..000000000 --- a/contracts/mirror_limit_order/schema/init_msg.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InitMsg", - "type": "object" -} diff --git a/contracts/mirror_limit_order/schema/order_response.json b/contracts/mirror_limit_order/schema/order_response.json deleted file mode 100644 index 397a1a358..000000000 --- a/contracts/mirror_limit_order/schema/order_response.json +++ /dev/null @@ -1,100 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "OrderResponse", - "type": "object", - "required": [ - "ask_asset", - "bidder_addr", - "filled_ask_amount", - "filled_offer_amount", - "offer_asset", - "order_id" - ], - "properties": { - "ask_asset": { - "$ref": "#/definitions/Asset" - }, - "bidder_addr": { - "$ref": "#/definitions/HumanAddr" - }, - "filled_ask_amount": { - "$ref": "#/definitions/Uint128" - }, - "filled_offer_amount": { - "$ref": "#/definitions/Uint128" - }, - "offer_asset": { - "$ref": "#/definitions/Asset" - }, - "order_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "definitions": { - "Asset": { - "type": "object", - "required": [ - "amount", - "info" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "info": { - "$ref": "#/definitions/AssetInfo" - } - } - }, - "AssetInfo": { - "anyOf": [ - { - "type": "object", - "required": [ - "token" - ], - "properties": { - "token": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "$ref": "#/definitions/HumanAddr" - } - } - } - } - }, - { - "type": "object", - "required": [ - "native_token" - ], - "properties": { - "native_token": { - "type": "object", - "required": [ - "denom" - ], - "properties": { - "denom": { - "type": "string" - } - } - } - } - } - ] - }, - "HumanAddr": { - "type": "string" - }, - "Uint128": { - "type": "string" - } - } -} diff --git a/contracts/mirror_limit_order/schema/orders_response.json b/contracts/mirror_limit_order/schema/orders_response.json deleted file mode 100644 index 0185ce669..000000000 --- a/contracts/mirror_limit_order/schema/orders_response.json +++ /dev/null @@ -1,114 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "OrdersResponse", - "type": "object", - "required": [ - "orders" - ], - "properties": { - "orders": { - "type": "array", - "items": { - "$ref": "#/definitions/OrderResponse" - } - } - }, - "definitions": { - "Asset": { - "type": "object", - "required": [ - "amount", - "info" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "info": { - "$ref": "#/definitions/AssetInfo" - } - } - }, - "AssetInfo": { - "anyOf": [ - { - "type": "object", - "required": [ - "token" - ], - "properties": { - "token": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "$ref": "#/definitions/HumanAddr" - } - } - } - } - }, - { - "type": "object", - "required": [ - "native_token" - ], - "properties": { - "native_token": { - "type": "object", - "required": [ - "denom" - ], - "properties": { - "denom": { - "type": "string" - } - } - } - } - } - ] - }, - "HumanAddr": { - "type": "string" - }, - "OrderResponse": { - "type": "object", - "required": [ - "ask_asset", - "bidder_addr", - "filled_ask_amount", - "filled_offer_amount", - "offer_asset", - "order_id" - ], - "properties": { - "ask_asset": { - "$ref": "#/definitions/Asset" - }, - "bidder_addr": { - "$ref": "#/definitions/HumanAddr" - }, - "filled_ask_amount": { - "$ref": "#/definitions/Uint128" - }, - "filled_offer_amount": { - "$ref": "#/definitions/Uint128" - }, - "offer_asset": { - "$ref": "#/definitions/Asset" - }, - "order_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - }, - "Uint128": { - "type": "string" - } - } -} diff --git a/contracts/mirror_limit_order/schema/query_msg.json b/contracts/mirror_limit_order/schema/query_msg.json deleted file mode 100644 index 5a23d9134..000000000 --- a/contracts/mirror_limit_order/schema/query_msg.json +++ /dev/null @@ -1,99 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryMsg", - "anyOf": [ - { - "type": "object", - "required": [ - "order" - ], - "properties": { - "order": { - "type": "object", - "required": [ - "order_id" - ], - "properties": { - "order_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - } - } - }, - { - "type": "object", - "required": [ - "orders" - ], - "properties": { - "orders": { - "type": "object", - "properties": { - "bidder_addr": { - "anyOf": [ - { - "$ref": "#/definitions/HumanAddr" - }, - { - "type": "null" - } - ] - }, - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "order_by": { - "anyOf": [ - { - "$ref": "#/definitions/OrderBy" - }, - { - "type": "null" - } - ] - }, - "start_after": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - } - } - } - } - }, - { - "type": "object", - "required": [ - "last_order_id" - ], - "properties": { - "last_order_id": { - "type": "object" - } - } - } - ], - "definitions": { - "HumanAddr": { - "type": "string" - }, - "OrderBy": { - "type": "string", - "enum": [ - "asc", - "desc" - ] - } - } -} diff --git a/contracts/mirror_limit_order/src/contract.rs b/contracts/mirror_limit_order/src/contract.rs deleted file mode 100644 index 7874fbcb7..000000000 --- a/contracts/mirror_limit_order/src/contract.rs +++ /dev/null @@ -1,121 +0,0 @@ -use cosmwasm_std::{ - from_binary, to_binary, Api, Binary, Env, Extern, HandleResponse, HandleResult, InitResponse, - InitResult, MigrateResponse, MigrateResult, Querier, StdError, StdResult, Storage, -}; - -use crate::order::{ - cancel_order, execute_order, query_last_order_id, query_order, query_orders, submit_order, -}; -use crate::state::init_last_order_id; - -use cw20::Cw20ReceiveMsg; -use mirror_protocol::limit_order::{Cw20HookMsg, HandleMsg, InitMsg, MigrateMsg, QueryMsg}; -use terraswap::asset::{Asset, AssetInfo}; - -pub fn init( - deps: &mut Extern, - _env: Env, - _msg: InitMsg, -) -> InitResult { - init_last_order_id(&mut deps.storage)?; - Ok(InitResponse::default()) -} - -pub fn handle( - deps: &mut Extern, - env: Env, - msg: HandleMsg, -) -> StdResult { - match msg { - HandleMsg::Receive(msg) => receive_cw20(deps, env, msg), - HandleMsg::SubmitOrder { - offer_asset, - ask_asset, - } => { - if !offer_asset.is_native_token() { - return Err(StdError::generic_err("must provide native token")); - } - - offer_asset.assert_sent_native_token_balance(&env)?; - submit_order(deps, env.message.sender, offer_asset, ask_asset) - } - HandleMsg::CancelOrder { order_id } => cancel_order(deps, env, order_id), - HandleMsg::ExecuteOrder { - execute_asset, - order_id, - } => { - if !execute_asset.is_native_token() { - return Err(StdError::generic_err("must provide native token")); - } - - execute_asset.assert_sent_native_token_balance(&env)?; - execute_order( - deps, - env.message.sender, - env.contract.address, - execute_asset, - order_id, - ) - } - } -} - -pub fn receive_cw20( - deps: &mut Extern, - env: Env, - cw20_msg: Cw20ReceiveMsg, -) -> HandleResult { - if let Some(msg) = cw20_msg.msg { - let provided_asset = Asset { - info: AssetInfo::Token { - contract_addr: env.message.sender, - }, - amount: cw20_msg.amount, - }; - - match from_binary(&msg)? { - Cw20HookMsg::SubmitOrder { ask_asset } => { - submit_order(deps, cw20_msg.sender, provided_asset, ask_asset) - } - Cw20HookMsg::ExecuteOrder { order_id } => execute_order( - deps, - cw20_msg.sender, - env.contract.address, - provided_asset, - order_id, - ), - } - } else { - Err(StdError::generic_err("data should be given")) - } -} - -pub fn query( - deps: &Extern, - msg: QueryMsg, -) -> StdResult { - match msg { - QueryMsg::Order { order_id } => to_binary(&query_order(deps, order_id)?), - QueryMsg::Orders { - bidder_addr, - start_after, - limit, - order_by, - } => to_binary(&query_orders( - deps, - bidder_addr, - start_after, - limit, - order_by, - )?), - QueryMsg::LastOrderId {} => to_binary(&query_last_order_id(deps)?), - } -} - -pub fn migrate( - _deps: &mut Extern, - _env: Env, - _msg: MigrateMsg, -) -> MigrateResult { - Ok(MigrateResponse::default()) -} diff --git a/contracts/mirror_limit_order/src/lib.rs b/contracts/mirror_limit_order/src/lib.rs deleted file mode 100644 index 9baf66f79..000000000 --- a/contracts/mirror_limit_order/src/lib.rs +++ /dev/null @@ -1,10 +0,0 @@ -pub mod contract; -pub mod state; - -mod order; - -#[cfg(test)] -mod testing; - -#[cfg(target_arch = "wasm32")] -cosmwasm_std::create_entry_points_with_migration!(contract); diff --git a/contracts/mirror_limit_order/src/order.rs b/contracts/mirror_limit_order/src/order.rs deleted file mode 100644 index d3ddaa2e4..000000000 --- a/contracts/mirror_limit_order/src/order.rs +++ /dev/null @@ -1,229 +0,0 @@ -use cosmwasm_std::{ - log, Api, CosmosMsg, Decimal, Env, Extern, HandleResponse, HandleResult, HumanAddr, Querier, - StdError, StdResult, Storage, Uint128, -}; - -use terraswap::asset::Asset; - -use crate::state::{ - increase_last_order_id, read_last_order_id, read_order, read_orders, - read_orders_with_bidder_indexer, remove_order, store_order, Order, -}; -use mirror_protocol::common::OrderBy; -use mirror_protocol::limit_order::{LastOrderIdResponse, OrderResponse, OrdersResponse}; - -pub fn submit_order( - deps: &mut Extern, - sender: HumanAddr, - offer_asset: Asset, - ask_asset: Asset, -) -> HandleResult { - let order_id = increase_last_order_id(&mut deps.storage)?; - - let offer_asset_raw = offer_asset.to_raw(&deps)?; - let ask_asset_raw = ask_asset.to_raw(&deps)?; - store_order( - &mut deps.storage, - &Order { - order_id, - bidder_addr: deps.api.canonical_address(&sender)?, - offer_asset: offer_asset_raw, - ask_asset: ask_asset_raw, - filled_offer_amount: Uint128::zero(), - filled_ask_amount: Uint128::zero(), - }, - )?; - - Ok(HandleResponse { - messages: vec![], - log: vec![ - log("action", "submit_order"), - log("order_id", order_id), - log("bidder_addr", sender), - log("offer_asset", offer_asset.to_string()), - log("ask_asset", ask_asset.to_string()), - ], - data: None, - }) -} - -pub fn cancel_order( - deps: &mut Extern, - env: Env, - order_id: u64, -) -> HandleResult { - let order: Order = read_order(&deps.storage, order_id)?; - if order.bidder_addr != deps.api.canonical_address(&env.message.sender)? { - return Err(StdError::unauthorized()); - } - - // Compute refund asset - let left_offer_amount = (order.offer_asset.amount - order.filled_offer_amount)?; - let bidder_refund = Asset { - info: order.offer_asset.info.to_normal(&deps)?, - amount: left_offer_amount, - }; - - // Build refund msg - let messages = if left_offer_amount > Uint128::zero() { - vec![bidder_refund - .clone() - .into_msg(&deps, env.contract.address, env.message.sender)?] - } else { - vec![] - }; - - remove_order(&mut deps.storage, &order); - - Ok(HandleResponse { - messages, - log: vec![ - log("action", "cancel_order"), - log("order_id", order_id), - log("bidder_refund", bidder_refund), - ], - data: None, - }) -} - -pub fn execute_order( - deps: &mut Extern, - sender: HumanAddr, - contract_addr: HumanAddr, - execute_asset: Asset, - order_id: u64, -) -> HandleResult { - let mut order: Order = read_order(&deps.storage, order_id)?; - if !execute_asset - .info - .equal(&order.ask_asset.info.to_normal(&deps)?) - { - return Err(StdError::generic_err("invalid asset given")); - } - - // Compute left offer & ask amount - let left_offer_amount = (order.offer_asset.amount - order.filled_offer_amount)?; - let left_ask_amount = (order.ask_asset.amount - order.filled_ask_amount)?; - if left_ask_amount < execute_asset.amount || left_offer_amount.is_zero() { - return Err(StdError::generic_err("insufficient order amount left")); - } - - // Cap the send amount to left_offer_amount - let executor_receive = Asset { - info: order.offer_asset.info.to_normal(&deps)?, - amount: if left_ask_amount == execute_asset.amount { - left_offer_amount - } else { - std::cmp::min( - left_offer_amount, - execute_asset.amount - * Decimal::from_ratio(order.offer_asset.amount, order.ask_asset.amount), - ) - }, - }; - - let bidder_addr = deps.api.human_address(&order.bidder_addr)?; - let bidder_receive = execute_asset; - - // When left amount is zero, close order - if left_ask_amount == bidder_receive.amount { - remove_order(&mut deps.storage, &order); - } else { - order.filled_ask_amount = order.filled_ask_amount + bidder_receive.amount; - order.filled_offer_amount = order.filled_offer_amount + executor_receive.amount; - store_order(&mut deps.storage, &order)?; - } - - let mut messages: Vec = vec![]; - if !executor_receive.amount.is_zero() { - messages.push( - executor_receive - .clone() - .into_msg(&deps, contract_addr.clone(), sender)?, - ); - } - - if !bidder_receive.amount.is_zero() { - messages.push( - bidder_receive - .clone() - .into_msg(&deps, contract_addr, bidder_addr)?, - ); - } - - Ok(HandleResponse { - messages, - log: vec![ - log("action", "execute_order"), - log("order_id", order_id), - log("executor_receive", executor_receive), - log("bidder_receive", bidder_receive), - ], - data: None, - }) -} - -pub fn query_order( - deps: &Extern, - order_id: u64, -) -> StdResult { - let order: Order = read_order(&deps.storage, order_id)?; - let resp = OrderResponse { - order_id: order.order_id, - bidder_addr: deps.api.human_address(&order.bidder_addr)?, - offer_asset: order.offer_asset.to_normal(&deps)?, - ask_asset: order.ask_asset.to_normal(&deps)?, - filled_offer_amount: order.filled_offer_amount, - filled_ask_amount: order.filled_ask_amount, - }; - - Ok(resp) -} - -pub fn query_orders( - deps: &Extern, - bidder_addr: Option, - start_after: Option, - limit: Option, - order_by: Option, -) -> StdResult { - let orders: Vec = if let Some(bidder_addr) = bidder_addr { - let bidder_addr_raw = deps.api.canonical_address(&bidder_addr)?; - read_orders_with_bidder_indexer( - &deps.storage, - &bidder_addr_raw, - start_after, - limit, - order_by, - )? - } else { - read_orders(&deps.storage, start_after, limit, order_by)? - }; - - let resp = OrdersResponse { - orders: orders - .iter() - .map(|order| { - Ok(OrderResponse { - order_id: order.order_id, - bidder_addr: deps.api.human_address(&order.bidder_addr)?, - offer_asset: order.offer_asset.to_normal(&deps)?, - ask_asset: order.ask_asset.to_normal(&deps)?, - filled_offer_amount: order.filled_offer_amount, - filled_ask_amount: order.filled_ask_amount, - }) - }) - .collect::>>()?, - }; - - Ok(resp) -} - -pub fn query_last_order_id( - deps: &Extern, -) -> StdResult { - let last_order_id = read_last_order_id(&deps.storage)?; - let resp = LastOrderIdResponse { last_order_id }; - - Ok(resp) -} diff --git a/contracts/mirror_limit_order/src/state.rs b/contracts/mirror_limit_order/src/state.rs deleted file mode 100644 index 80edefb85..000000000 --- a/contracts/mirror_limit_order/src/state.rs +++ /dev/null @@ -1,136 +0,0 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use cosmwasm_std::{CanonicalAddr, ReadonlyStorage, StdError, StdResult, Storage, Uint128}; -use cosmwasm_storage::{singleton, singleton_read, Bucket, ReadonlyBucket}; - -use mirror_protocol::common::OrderBy; -use std::convert::TryInto; -use terraswap::asset::AssetRaw; - -static KEY_LAST_ORDER_ID: &[u8] = b"last_order_id"; - -static PREFIX_ORDER: &[u8] = b"order"; -static PREFIX_ORDER_BY_BIDDER: &[u8] = b"order_by_bidder"; - -pub fn init_last_order_id(storage: &mut S) -> StdResult<()> { - singleton(storage, KEY_LAST_ORDER_ID).save(&0u64) -} - -pub fn increase_last_order_id(storage: &mut S) -> StdResult { - singleton(storage, KEY_LAST_ORDER_ID).update(|v| Ok(v + 1)) -} - -pub fn read_last_order_id(storage: &S) -> StdResult { - singleton_read(storage, KEY_LAST_ORDER_ID).load() -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct Order { - pub order_id: u64, - pub bidder_addr: CanonicalAddr, - pub offer_asset: AssetRaw, - pub ask_asset: AssetRaw, - pub filled_offer_amount: Uint128, - pub filled_ask_amount: Uint128, -} - -pub fn store_order(storage: &mut S, order: &Order) -> StdResult<()> { - Bucket::new(PREFIX_ORDER, storage).save(&order.order_id.to_be_bytes(), order)?; - Bucket::multilevel( - &[PREFIX_ORDER_BY_BIDDER, order.bidder_addr.as_slice()], - storage, - ) - .save(&order.order_id.to_be_bytes(), &true)?; - - Ok(()) -} - -pub fn remove_order(storage: &mut S, order: &Order) { - Bucket::::new(PREFIX_ORDER, storage).remove(&order.order_id.to_be_bytes()); - Bucket::::multilevel( - &[PREFIX_ORDER_BY_BIDDER, order.bidder_addr.as_slice()], - storage, - ) - .remove(&order.order_id.to_be_bytes()); -} - -pub fn read_order(storage: &S, order_id: u64) -> StdResult { - ReadonlyBucket::new(PREFIX_ORDER, storage).load(&order_id.to_be_bytes()) -} - -pub fn read_orders_with_bidder_indexer( - storage: &S, - bidder_addr: &CanonicalAddr, - start_after: Option, - limit: Option, - order_by: Option, -) -> StdResult> { - let position_indexer: ReadonlyBucket = - ReadonlyBucket::multilevel(&[PREFIX_ORDER_BY_BIDDER, bidder_addr.as_slice()], storage); - - let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - let (start, end, order_by) = match order_by { - Some(OrderBy::Asc) => (calc_range_start(start_after), None, OrderBy::Asc), - _ => (None, calc_range_end(start_after), OrderBy::Desc), - }; - - position_indexer - .range(start.as_deref(), end.as_deref(), order_by.into()) - .take(limit) - .map(|item| { - let (k, _) = item?; - read_order(storage, bytes_to_u64(&k)?) - }) - .collect() -} - -// settings for pagination -const MAX_LIMIT: u32 = 30; -const DEFAULT_LIMIT: u32 = 10; -pub fn read_orders( - storage: &S, - start_after: Option, - limit: Option, - order_by: Option, -) -> StdResult> { - let position_bucket: ReadonlyBucket = ReadonlyBucket::new(PREFIX_ORDER, storage); - - let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - let (start, end, order_by) = match order_by { - Some(OrderBy::Asc) => (calc_range_start(start_after), None, OrderBy::Asc), - _ => (None, calc_range_end(start_after), OrderBy::Desc), - }; - - position_bucket - .range(start.as_deref(), end.as_deref(), order_by.into()) - .take(limit) - .map(|item| { - let (_, v) = item?; - Ok(v) - }) - .collect() -} - -fn bytes_to_u64(data: &[u8]) -> StdResult { - match data[0..8].try_into() { - Ok(bytes) => Ok(u64::from_be_bytes(bytes)), - Err(_) => Err(StdError::generic_err( - "Corrupted data found. 8 byte expected.", - )), - } -} - -// this will set the first key after the provided key, by appending a 1 byte -fn calc_range_start(start_after: Option) -> Option> { - start_after.map(|id| { - let mut v = id.to_be_bytes().to_vec(); - v.push(1); - v - }) -} - -// this will set the first key after the provided key, by appending a 1 byte -fn calc_range_end(start_after: Option) -> Option> { - start_after.map(|id| id.to_be_bytes().to_vec()) -} diff --git a/contracts/mirror_limit_order/src/testing/mock_querier.rs b/contracts/mirror_limit_order/src/testing/mock_querier.rs deleted file mode 100644 index 9868a3198..000000000 --- a/contracts/mirror_limit_order/src/testing/mock_querier.rs +++ /dev/null @@ -1,127 +0,0 @@ -use cosmwasm_std::testing::{MockApi, MockQuerier, MockStorage, MOCK_CONTRACT_ADDR}; -use cosmwasm_std::{ - from_slice, to_binary, Api, Coin, Decimal, Extern, HumanAddr, Querier, QuerierResult, - QueryRequest, SystemError, Uint128, -}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; - -use terra_cosmwasm::{TaxCapResponse, TaxRateResponse, TerraQuery, TerraQueryWrapper, TerraRoute}; - -/// mock_dependencies is a drop-in replacement for cosmwasm_std::testing::mock_dependencies -/// this uses our CustomQuerier. -pub fn mock_dependencies( - canonical_length: usize, - contract_balance: &[Coin], -) -> Extern { - let contract_addr = HumanAddr::from(MOCK_CONTRACT_ADDR); - let custom_querier: WasmMockQuerier = WasmMockQuerier::new( - MockQuerier::new(&[(&contract_addr, contract_balance)]), - MockApi::new(canonical_length), - ); - - Extern { - storage: MockStorage::default(), - api: MockApi::new(canonical_length), - querier: custom_querier, - } -} - -pub struct WasmMockQuerier { - base: MockQuerier, - tax_querier: TaxQuerier, -} - -#[derive(Clone, Default)] -pub struct TaxQuerier { - rate: Decimal, - // this lets us iterate over all pairs that match the first string - caps: HashMap, -} - -impl TaxQuerier { - pub fn new(rate: Decimal, caps: &[(&String, &Uint128)]) -> Self { - TaxQuerier { - rate, - caps: caps_to_map(caps), - } - } -} - -pub(crate) fn caps_to_map(caps: &[(&String, &Uint128)]) -> HashMap { - let mut owner_map: HashMap = HashMap::new(); - for (denom, cap) in caps.iter() { - owner_map.insert(denom.to_string(), **cap); - } - owner_map -} - -impl Querier for WasmMockQuerier { - fn raw_query(&self, bin_request: &[u8]) -> QuerierResult { - // MockQuerier doesn't support Custom, so we ignore it completely here - let request: QueryRequest = match from_slice(bin_request) { - Ok(v) => v, - Err(e) => { - return Err(SystemError::InvalidRequest { - error: format!("Parsing query request: {}", e), - request: bin_request.into(), - }) - } - }; - self.handle_query(&request) - } -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum MockQueryMsg { - Price {}, -} - -impl WasmMockQuerier { - pub fn handle_query(&self, request: &QueryRequest) -> QuerierResult { - match &request { - QueryRequest::Custom(TerraQueryWrapper { route, query_data }) => { - if route == &TerraRoute::Treasury { - match query_data { - TerraQuery::TaxRate {} => { - let res = TaxRateResponse { - rate: self.tax_querier.rate, - }; - Ok(to_binary(&res)) - } - TerraQuery::TaxCap { denom } => { - let cap = self - .tax_querier - .caps - .get(denom) - .copied() - .unwrap_or_default(); - let res = TaxCapResponse { cap }; - Ok(to_binary(&res)) - } - _ => panic!("DO NOT ENTER HERE"), - } - } else { - panic!("DO NOT ENTER HERE") - } - } - _ => self.base.handle_query(request), - } - } -} - -impl WasmMockQuerier { - pub fn new(base: MockQuerier, _api: A) -> Self { - WasmMockQuerier { - base, - tax_querier: TaxQuerier::default(), - } - } - - // configure the token owner mock querier - pub fn with_tax(&mut self, rate: Decimal, caps: &[(&String, &Uint128)]) { - self.tax_querier = TaxQuerier::new(rate, caps); - } -} diff --git a/contracts/mirror_limit_order/src/testing/mod.rs b/contracts/mirror_limit_order/src/testing/mod.rs deleted file mode 100644 index a1e507b68..000000000 --- a/contracts/mirror_limit_order/src/testing/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod mock_querier; -mod tests; diff --git a/contracts/mirror_limit_order/src/testing/tests.rs b/contracts/mirror_limit_order/src/testing/tests.rs deleted file mode 100644 index cb9290798..000000000 --- a/contracts/mirror_limit_order/src/testing/tests.rs +++ /dev/null @@ -1,811 +0,0 @@ -use cosmwasm_std::testing::{mock_env, MOCK_CONTRACT_ADDR}; -use cosmwasm_std::{ - from_binary, log, to_binary, BankMsg, Coin, CosmosMsg, Decimal, HumanAddr, StdError, Uint128, - WasmMsg, -}; - -use crate::contract::{handle, init, query}; -use crate::testing::mock_querier::mock_dependencies; - -use cw20::{Cw20HandleMsg, Cw20ReceiveMsg}; -use mirror_protocol::common::OrderBy; -use mirror_protocol::limit_order::{ - Cw20HookMsg, HandleMsg, InitMsg, LastOrderIdResponse, OrderResponse, OrdersResponse, QueryMsg, -}; -use terraswap::asset::{Asset, AssetInfo}; - -#[test] -fn proper_initialization() { - let mut deps = mock_dependencies(20, &[]); - - let msg = InitMsg {}; - let env = mock_env("addr0000", &[]); - - // we can just call .unwrap() to assert this was a success - let _res = init(&mut deps, env, msg).unwrap(); -} - -#[test] -fn submit_order() { - let mut deps = mock_dependencies(20, &[]); - - let msg = InitMsg {}; - let env = mock_env("addr0000", &[]); - - // we can just call .unwrap() to assert this was a success - let _res = init(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::SubmitOrder { - offer_asset: Asset { - amount: Uint128::from(1000000u128), - info: AssetInfo::Token { - contract_addr: HumanAddr::from("mAAPL"), - }, - }, - ask_asset: Asset { - amount: Uint128::from(1000000u128), - info: AssetInfo::Token { - contract_addr: HumanAddr::from("mAAPL"), - }, - }, - }; - - let env = mock_env("addr0000", &[]); - let res = handle(&mut deps, env, msg); - match res { - Err(StdError::GenericErr { msg, .. }) => assert_eq!(msg, "must provide native token"), - _ => panic!("DO NOT ENTER HERE"), - } - - let msg = HandleMsg::SubmitOrder { - offer_asset: Asset { - amount: Uint128::from(1000000u128), - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - }, - ask_asset: Asset { - amount: Uint128::from(1000000u128), - info: AssetInfo::Token { - contract_addr: HumanAddr::from("mAAPL"), - }, - }, - }; - - let env = mock_env("addr0000", &[]); - let res = handle(&mut deps, env, msg); - match res { - Err(StdError::GenericErr { msg, .. }) => assert_eq!( - msg, - "Native token balance missmatch between the argument and the transferred" - ), - _ => panic!("DO NOT ENTER HERE"), - } - - let msg = HandleMsg::SubmitOrder { - offer_asset: Asset { - amount: Uint128::from(1000000u128), - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - }, - ask_asset: Asset { - amount: Uint128::from(1000000u128), - info: AssetInfo::Token { - contract_addr: HumanAddr::from("mAAPL"), - }, - }, - }; - - let env = mock_env( - "addr0000", - &[Coin { - denom: "uusd".to_string(), - amount: Uint128::from(1000000u128), - }], - ); - let res = handle(&mut deps, env.clone(), msg).unwrap(); - assert_eq!( - res.log, - vec![ - log("action", "submit_order"), - log("order_id", 1), - log("bidder_addr", "addr0000"), - log("offer_asset", "1000000uusd"), - log("ask_asset", "1000000mAAPL"), - ] - ); - - assert_eq!( - from_binary::(&query(&deps, QueryMsg::LastOrderId {}).unwrap()) - .unwrap(), - LastOrderIdResponse { last_order_id: 1 } - ); - - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from("addr0000"), - amount: Uint128(1000000u128), - msg: Some( - to_binary(&Cw20HookMsg::SubmitOrder { - ask_asset: Asset { - amount: Uint128::from(1000000u128), - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - }, - }) - .unwrap(), - ), - }); - - let env = mock_env("mAAPL", &[]); - let res = handle(&mut deps, env.clone(), msg).unwrap(); - assert_eq!( - res.log, - vec![ - log("action", "submit_order"), - log("order_id", 2), - log("bidder_addr", "addr0000"), - log("offer_asset", "1000000mAAPL"), - log("ask_asset", "1000000uusd"), - ] - ); - assert_eq!( - from_binary::(&query(&deps, QueryMsg::LastOrderId {}).unwrap()) - .unwrap(), - LastOrderIdResponse { last_order_id: 2 } - ); -} - -#[test] -fn cancel_order_native_token() { - let mut deps = mock_dependencies(20, &[]); - deps.querier.with_tax( - Decimal::percent(1), - &[(&"uusd".to_string(), &Uint128(1000000u128))], - ); - - let msg = InitMsg {}; - let env = mock_env("addr0000", &[]); - - // we can just call .unwrap() to assert this was a success - let _res = init(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::SubmitOrder { - offer_asset: Asset { - amount: Uint128::from(1000000u128), - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - }, - ask_asset: Asset { - amount: Uint128::from(1000000u128), - info: AssetInfo::Token { - contract_addr: HumanAddr::from("mAAPL"), - }, - }, - }; - - let env = mock_env( - "addr0000", - &[Coin { - denom: "uusd".to_string(), - amount: Uint128::from(1000000u128), - }], - ); - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - - let msg = HandleMsg::CancelOrder { order_id: 1 }; - - // failed verfication failed - let wrong_env = mock_env("addr0001", &[]); - let res = handle(&mut deps, wrong_env, msg.clone()); - match res { - Err(StdError::Unauthorized { .. }) => {} - _ => panic!("DO NOT ENTER HERE"), - } - - let res = handle(&mut deps, env.clone(), msg.clone()).unwrap(); - assert_eq!( - res.log, - vec![ - log("action", "cancel_order"), - log("order_id", 1), - log("bidder_refund", "1000000uusd") - ] - ); - assert_eq!( - res.messages, - vec![CosmosMsg::Bank(BankMsg::Send { - from_address: HumanAddr::from(MOCK_CONTRACT_ADDR), - to_address: HumanAddr::from("addr0000"), - amount: vec![Coin { - denom: "uusd".to_string(), - amount: Uint128(990099u128), - }] - })] - ); - - // failed no order exists - let res = handle(&mut deps, env.clone(), msg.clone()); - assert_eq!(true, res.is_err()); -} - -#[test] -fn cancel_order_token() { - let mut deps = mock_dependencies(20, &[]); - deps.querier.with_tax( - Decimal::percent(1), - &[(&"uusd".to_string(), &Uint128(1000000u128))], - ); - - let msg = InitMsg {}; - let env = mock_env("addr0000", &[]); - - // we can just call .unwrap() to assert this was a success - let _res = init(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from("addr0000"), - amount: Uint128(1000000u128), - msg: Some( - to_binary(&Cw20HookMsg::SubmitOrder { - ask_asset: Asset { - amount: Uint128::from(1000000u128), - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - }, - }) - .unwrap(), - ), - }); - - let env = mock_env("mAAPL", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::CancelOrder { order_id: 1 }; - - // failed verfication failed - let wrong_env = mock_env("addr0001", &[]); - let res = handle(&mut deps, wrong_env, msg.clone()); - match res { - Err(StdError::Unauthorized { .. }) => {} - _ => panic!("DO NOT ENTER HERE"), - } - - let env = mock_env("addr0000", &[]); - let res = handle(&mut deps, env.clone(), msg.clone()).unwrap(); - assert_eq!( - res.log, - vec![ - log("action", "cancel_order"), - log("order_id", 1), - log("bidder_refund", "1000000mAAPL") - ] - ); - assert_eq!( - res.messages, - vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("mAAPL"), - send: vec![], - msg: to_binary(&Cw20HandleMsg::Transfer { - amount: Uint128(1000000u128), - recipient: HumanAddr::from("addr0000"), - }) - .unwrap(), - })] - ); - - // failed no order exists - let res = handle(&mut deps, env.clone(), msg.clone()); - assert_eq!(true, res.is_err()); -} - -#[test] -fn execute_order_native_token() { - let mut deps = mock_dependencies(20, &[]); - deps.querier.with_tax( - Decimal::percent(1), - &[ - (&"uusd".to_string(), &Uint128(1000000u128)), - (&"ukrw".to_string(), &Uint128(1000000u128)), - ], - ); - - let msg = InitMsg {}; - let env = mock_env("addr0000", &[]); - - // we can just call .unwrap() to assert this was a success - let _res = init(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::SubmitOrder { - offer_asset: Asset { - amount: Uint128::from(1000000u128), - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - }, - ask_asset: Asset { - amount: Uint128::from(1000000u128), - info: AssetInfo::NativeToken { - denom: "ukrw".to_string(), - }, - }, - }; - - let env = mock_env( - "addr0000", - &[Coin { - denom: "uusd".to_string(), - amount: Uint128::from(1000000u128), - }], - ); - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - - // assertion; native asset balance - let msg = HandleMsg::ExecuteOrder { - execute_asset: Asset { - amount: Uint128(500000u128), - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - }, - order_id: 1u64, - }; - let res = handle(&mut deps, env, msg.clone()); - match res { - Err(StdError::GenericErr { msg, .. }) => assert_eq!( - msg, - "Native token balance missmatch between the argument and the transferred" - ), - _ => panic!("DO NOT ENTER HERE"), - } - - // cannot execute order with other asset - let env = mock_env( - "addr0001", - &[Coin { - denom: "uusd".to_string(), - amount: Uint128::from(500000u128), - }], - ); - let res = handle(&mut deps, env.clone(), msg); - match res { - Err(StdError::GenericErr { msg, .. }) => assert_eq!(msg, "invalid asset given"), - _ => panic!("DO NOT ENTER HERE"), - } - - // partial execute - let env = mock_env( - "addr0001", - &[Coin { - denom: "ukrw".to_string(), - amount: Uint128::from(500000u128), - }], - ); - let msg = HandleMsg::ExecuteOrder { - execute_asset: Asset { - amount: Uint128(500000u128), - info: AssetInfo::NativeToken { - denom: "ukrw".to_string(), - }, - }, - order_id: 1u64, - }; - let res = handle(&mut deps, env.clone(), msg.clone()).unwrap(); - assert_eq!( - res.log, - vec![ - log("action", "execute_order"), - log("order_id", 1), - log("executor_receive", "500000uusd"), - log("bidder_receive", "500000ukrw"), - ] - ); - assert_eq!( - res.messages, - vec![ - CosmosMsg::Bank(BankMsg::Send { - from_address: HumanAddr::from(MOCK_CONTRACT_ADDR), - to_address: HumanAddr::from("addr0001"), - amount: vec![Coin { - denom: "uusd".to_string(), - amount: Uint128::from(495049u128) - }] - }), - CosmosMsg::Bank(BankMsg::Send { - from_address: HumanAddr::from(MOCK_CONTRACT_ADDR), - to_address: HumanAddr::from("addr0000"), - amount: vec![Coin { - denom: "ukrw".to_string(), - amount: Uint128::from(495049u128) - }] - }), - ] - ); - - let resp: OrderResponse = - from_binary(&query(&deps, QueryMsg::Order { order_id: 1 }).unwrap()).unwrap(); - assert_eq!(resp.filled_ask_amount, Uint128(500000u128)); - assert_eq!(resp.filled_offer_amount, Uint128(500000u128)); - - // fill left amount - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!( - res.log, - vec![ - log("action", "execute_order"), - log("order_id", 1), - log("executor_receive", "500000uusd"), - log("bidder_receive", "500000ukrw"), - ] - ); - assert_eq!( - res.messages, - vec![ - CosmosMsg::Bank(BankMsg::Send { - from_address: HumanAddr::from(MOCK_CONTRACT_ADDR), - to_address: HumanAddr::from("addr0001"), - amount: vec![Coin { - denom: "uusd".to_string(), - amount: Uint128::from(495049u128) - }] - }), - CosmosMsg::Bank(BankMsg::Send { - from_address: HumanAddr::from(MOCK_CONTRACT_ADDR), - to_address: HumanAddr::from("addr0000"), - amount: vec![Coin { - denom: "ukrw".to_string(), - amount: Uint128::from(495049u128) - }] - }), - ] - ); - - assert_eq!(true, query(&deps, QueryMsg::Order { order_id: 1 }).is_err()); -} - -#[test] -fn execute_order_token() { - let mut deps = mock_dependencies(20, &[]); - deps.querier.with_tax( - Decimal::percent(1), - &[ - (&"uusd".to_string(), &Uint128(1000000u128)), - (&"ukrw".to_string(), &Uint128(1000000u128)), - ], - ); - - let msg = InitMsg {}; - let env = mock_env("addr0000", &[]); - - // we can just call .unwrap() to assert this was a success - let _res = init(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from("addr0000"), - amount: Uint128::from(1000000u128), - msg: Some( - to_binary(&Cw20HookMsg::SubmitOrder { - ask_asset: Asset { - amount: Uint128::from(1000000u128), - info: AssetInfo::Token { - contract_addr: HumanAddr::from("token0001"), - }, - }, - }) - .unwrap(), - ), - }); - - let env = mock_env("token0000", &[]); - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - - // cannot execute order with other asset - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from("addr0001"), - amount: Uint128::from(500000u128), - msg: Some(to_binary(&Cw20HookMsg::ExecuteOrder { order_id: 1u64 }).unwrap()), - }); - - let env = mock_env("token0000", &[]); - let res = handle(&mut deps, env, msg.clone()); - match res { - Err(StdError::GenericErr { msg, .. }) => assert_eq!(msg, "invalid asset given"), - _ => panic!("DO NOT ENTER HERE"), - } - - // partial execute - let env = mock_env("token0001", &[]); - let res = handle(&mut deps, env.clone(), msg.clone()).unwrap(); - assert_eq!( - res.log, - vec![ - log("action", "execute_order"), - log("order_id", 1), - log("executor_receive", "500000token0000"), - log("bidder_receive", "500000token0001"), - ] - ); - assert_eq!( - res.messages, - vec![ - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("token0000"), - send: vec![], - msg: to_binary(&Cw20HandleMsg::Transfer { - recipient: HumanAddr::from("addr0001"), - amount: Uint128::from(500000u128) - }) - .unwrap(), - }), - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("token0001"), - send: vec![], - msg: to_binary(&Cw20HandleMsg::Transfer { - recipient: HumanAddr::from("addr0000"), - amount: Uint128::from(500000u128) - }) - .unwrap(), - }), - ] - ); - - let resp: OrderResponse = - from_binary(&query(&deps, QueryMsg::Order { order_id: 1 }).unwrap()).unwrap(); - assert_eq!(resp.filled_ask_amount, Uint128(500000u128)); - assert_eq!(resp.filled_offer_amount, Uint128(500000u128)); - - // fill left amount - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!( - res.log, - vec![ - log("action", "execute_order"), - log("order_id", 1), - log("executor_receive", "500000token0000"), - log("bidder_receive", "500000token0001"), - ] - ); - assert_eq!( - res.messages, - vec![ - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("token0000"), - send: vec![], - msg: to_binary(&Cw20HandleMsg::Transfer { - recipient: HumanAddr::from("addr0001"), - amount: Uint128::from(500000u128) - }) - .unwrap(), - }), - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("token0001"), - send: vec![], - msg: to_binary(&Cw20HandleMsg::Transfer { - recipient: HumanAddr::from("addr0000"), - amount: Uint128::from(500000u128) - }) - .unwrap(), - }), - ] - ); - - assert_eq!(true, query(&deps, QueryMsg::Order { order_id: 1 }).is_err()); -} - -#[test] -fn orders_querier() { - let mut deps = mock_dependencies(20, &[]); - deps.querier.with_tax( - Decimal::percent(1), - &[ - (&"uusd".to_string(), &Uint128(1000000u128)), - (&"ukrw".to_string(), &Uint128(1000000u128)), - ], - ); - - let msg = InitMsg {}; - let env = mock_env("addr0000", &[]); - - // we can just call .unwrap() to assert this was a success - let _res = init(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::SubmitOrder { - offer_asset: Asset { - amount: Uint128::from(1000000u128), - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - }, - ask_asset: Asset { - amount: Uint128::from(1000000u128), - info: AssetInfo::NativeToken { - denom: "ukrw".to_string(), - }, - }, - }; - - let env = mock_env( - "addr0000", - &[Coin { - denom: "uusd".to_string(), - amount: Uint128::from(1000000u128), - }], - ); - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from("addr0000"), - amount: Uint128::from(1000000u128), - msg: Some( - to_binary(&Cw20HookMsg::SubmitOrder { - ask_asset: Asset { - amount: Uint128::from(1000000u128), - info: AssetInfo::Token { - contract_addr: HumanAddr::from("token0001"), - }, - }, - }) - .unwrap(), - ), - }); - - let env = mock_env("token0000", &[]); - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - - let order_1 = OrderResponse { - order_id: 1u64, - bidder_addr: HumanAddr::from("addr0000"), - offer_asset: Asset { - amount: Uint128::from(1000000u128), - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - }, - ask_asset: Asset { - amount: Uint128::from(1000000u128), - info: AssetInfo::NativeToken { - denom: "ukrw".to_string(), - }, - }, - filled_offer_amount: Uint128::zero(), - filled_ask_amount: Uint128::zero(), - }; - - let order_2 = OrderResponse { - order_id: 2u64, - bidder_addr: HumanAddr::from("addr0000"), - offer_asset: Asset { - amount: Uint128::from(1000000u128), - info: AssetInfo::Token { - contract_addr: HumanAddr::from("token0000"), - }, - }, - ask_asset: Asset { - amount: Uint128::from(1000000u128), - info: AssetInfo::Token { - contract_addr: HumanAddr::from("token0001"), - }, - }, - filled_offer_amount: Uint128::zero(), - filled_ask_amount: Uint128::zero(), - }; - - assert_eq!( - OrdersResponse { - orders: vec![order_1.clone(), order_2.clone(),], - }, - from_binary::( - &query( - &deps, - QueryMsg::Orders { - bidder_addr: Some(HumanAddr::from("addr0000")), - start_after: None, - limit: None, - order_by: Some(OrderBy::Asc), - } - ) - .unwrap() - ) - .unwrap() - ); - - assert_eq!( - OrdersResponse { - orders: vec![order_1.clone(), order_2.clone(),], - }, - from_binary::( - &query( - &deps, - QueryMsg::Orders { - bidder_addr: None, - start_after: None, - limit: None, - order_by: Some(OrderBy::Asc), - } - ) - .unwrap() - ) - .unwrap() - ); - - // DESC test - assert_eq!( - OrdersResponse { - orders: vec![order_2.clone(), order_1.clone(),], - }, - from_binary::( - &query( - &deps, - QueryMsg::Orders { - bidder_addr: None, - start_after: None, - limit: None, - order_by: Some(OrderBy::Desc), - } - ) - .unwrap() - ) - .unwrap() - ); - - // different bidder - assert_eq!( - OrdersResponse { orders: vec![] }, - from_binary::( - &query( - &deps, - QueryMsg::Orders { - bidder_addr: Some(HumanAddr::from("addr0001")), - start_after: None, - limit: None, - order_by: None, - } - ) - .unwrap() - ) - .unwrap() - ); - - // start after DESC - assert_eq!( - OrdersResponse { - orders: vec![order_1.clone()], - }, - from_binary::( - &query( - &deps, - QueryMsg::Orders { - bidder_addr: None, - start_after: Some(2u64), - limit: None, - order_by: Some(OrderBy::Desc), - } - ) - .unwrap() - ) - .unwrap() - ); - - // start after ASC - assert_eq!( - OrdersResponse { - orders: vec![order_2.clone()], - }, - from_binary::( - &query( - &deps, - QueryMsg::Orders { - bidder_addr: None, - start_after: Some(1u64), - limit: None, - order_by: Some(OrderBy::Asc), - } - ) - .unwrap() - ) - .unwrap() - ); -} diff --git a/contracts/mirror_lock/.cargo/config b/contracts/mirror_lock/.cargo/config deleted file mode 100644 index 7c115322a..000000000 --- a/contracts/mirror_lock/.cargo/config +++ /dev/null @@ -1,6 +0,0 @@ -[alias] -wasm = "build --release --target wasm32-unknown-unknown" -wasm-debug = "build --target wasm32-unknown-unknown" -unit-test = "test --lib --features backtraces" -integration-test = "test --test integration" -schema = "run --example schema" diff --git a/contracts/mirror_lock/Cargo.toml b/contracts/mirror_lock/Cargo.toml deleted file mode 100644 index ccb833b7a..000000000 --- a/contracts/mirror_lock/Cargo.toml +++ /dev/null @@ -1,47 +0,0 @@ -[package] -name = "mirror-lock" -version = "1.1.0" -authors = ["Terraform Labs, PTE."] -edition = "2018" -description = "A Lock contract for Mirror Protocol - mint contract to lock funds for shorting positions" -license = "Apache-2.0" - -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] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces"] - -[dependencies] -cw20 = { version = "0.2" } -cosmwasm-std = { version = "0.10.1", features = ["iterator"] } -cosmwasm-storage = { version = "0.10.1", features = ["iterator"] } -mirror-protocol = { version = "1.1.0", path = "../../packages/mirror_protocol" } -terraswap = "1.1.0" -schemars = "0.7" -serde = { version = "1.0.103", default-features = false, features = ["derive"] } - -[dev-dependencies] -terra-cosmwasm = { version = "1.2.2" } -cosmwasm-schema = "0.10.1" \ No newline at end of file diff --git a/contracts/mirror_lock/README.md b/contracts/mirror_lock/README.md deleted file mode 100644 index dda6d923b..000000000 --- a/contracts/mirror_lock/README.md +++ /dev/null @@ -1 +0,0 @@ -# Mirror Lock diff --git a/contracts/mirror_lock/rustfmt.toml b/contracts/mirror_lock/rustfmt.toml deleted file mode 100644 index 11a85e6a9..000000000 --- a/contracts/mirror_lock/rustfmt.toml +++ /dev/null @@ -1,15 +0,0 @@ -# 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/mirror_lock/schema/config_response.json b/contracts/mirror_lock/schema/config_response.json deleted file mode 100644 index edfa758dc..000000000 --- a/contracts/mirror_lock/schema/config_response.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ConfigResponse", - "type": "object", - "required": [ - "base_denom", - "lockup_period", - "mint_contract", - "owner" - ], - "properties": { - "base_denom": { - "type": "string" - }, - "lockup_period": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "mint_contract": { - "$ref": "#/definitions/HumanAddr" - }, - "owner": { - "$ref": "#/definitions/HumanAddr" - } - }, - "definitions": { - "HumanAddr": { - "type": "string" - } - } -} diff --git a/contracts/mirror_lock/schema/handle_msg.json b/contracts/mirror_lock/schema/handle_msg.json deleted file mode 100644 index 6b0b9868c..000000000 --- a/contracts/mirror_lock/schema/handle_msg.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "HandleMsg", - "anyOf": [ - { - "type": "object", - "required": [ - "update_config" - ], - "properties": { - "update_config": { - "type": "object", - "properties": { - "base_denom": { - "type": [ - "string", - "null" - ] - }, - "lockup_period": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - }, - "mint_contract": { - "anyOf": [ - { - "$ref": "#/definitions/HumanAddr" - }, - { - "type": "null" - } - ] - }, - "owner": { - "anyOf": [ - { - "$ref": "#/definitions/HumanAddr" - }, - { - "type": "null" - } - ] - } - } - } - } - }, - { - "type": "object", - "required": [ - "lock_position_funds_hook" - ], - "properties": { - "lock_position_funds_hook": { - "type": "object", - "required": [ - "position_idx", - "receiver" - ], - "properties": { - "position_idx": { - "$ref": "#/definitions/Uint128" - }, - "receiver": { - "$ref": "#/definitions/HumanAddr" - } - } - } - } - }, - { - "type": "object", - "required": [ - "unlock_position_funds" - ], - "properties": { - "unlock_position_funds": { - "type": "object", - "required": [ - "position_idx" - ], - "properties": { - "position_idx": { - "$ref": "#/definitions/Uint128" - } - } - } - } - }, - { - "type": "object", - "required": [ - "release_position_funds" - ], - "properties": { - "release_position_funds": { - "type": "object", - "required": [ - "position_idx" - ], - "properties": { - "position_idx": { - "$ref": "#/definitions/Uint128" - } - } - } - } - } - ], - "definitions": { - "HumanAddr": { - "type": "string" - }, - "Uint128": { - "type": "string" - } - } -} diff --git a/contracts/mirror_lock/schema/position_lock_info_response.json b/contracts/mirror_lock/schema/position_lock_info_response.json deleted file mode 100644 index 16afa9aea..000000000 --- a/contracts/mirror_lock/schema/position_lock_info_response.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "PositionLockInfoResponse", - "type": "object", - "required": [ - "idx", - "locked_amount", - "receiver", - "unlock_time" - ], - "properties": { - "idx": { - "$ref": "#/definitions/Uint128" - }, - "locked_amount": { - "$ref": "#/definitions/Uint128" - }, - "receiver": { - "$ref": "#/definitions/HumanAddr" - }, - "unlock_time": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "definitions": { - "HumanAddr": { - "type": "string" - }, - "Uint128": { - "type": "string" - } - } -} diff --git a/contracts/mirror_lock/src/contract.rs b/contracts/mirror_lock/src/contract.rs deleted file mode 100644 index 7426fe73a..000000000 --- a/contracts/mirror_lock/src/contract.rs +++ /dev/null @@ -1,324 +0,0 @@ -use crate::state::{ - read_config, read_position_lock_info, remove_position_lock_info, store_config, - store_position_lock_info, total_locked_funds_read, total_locked_funds_store, Config, - PositionLockInfo, -}; -use cosmwasm_std::{ - log, to_binary, Api, Binary, CanonicalAddr, Env, Extern, HandleResponse, HumanAddr, - InitResponse, Querier, StdError, StdResult, Storage, Uint128, -}; -use mirror_protocol::lock::{ - ConfigResponse, HandleMsg, InitMsg, PositionLockInfoResponse, QueryMsg, -}; -use terraswap::{ - asset::{Asset, AssetInfo}, - querier::query_balance, -}; - -pub fn init( - deps: &mut Extern, - _env: Env, - msg: InitMsg, -) -> StdResult { - let config = Config { - owner: deps.api.canonical_address(&msg.owner)?, - mint_contract: deps.api.canonical_address(&msg.mint_contract)?, - base_denom: msg.base_denom, - lockup_period: msg.lockup_period, - }; - - store_config(&mut deps.storage, &config)?; - total_locked_funds_store(&mut deps.storage).save(&Uint128::zero())?; - Ok(InitResponse::default()) -} - -pub fn handle( - deps: &mut Extern, - env: Env, - msg: HandleMsg, -) -> StdResult { - match msg { - HandleMsg::UpdateConfig { - owner, - mint_contract, - base_denom, - lockup_period, - } => update_config(deps, env, owner, mint_contract, base_denom, lockup_period), - HandleMsg::LockPositionFundsHook { - position_idx, - receiver, - } => lock_position_funds_hook(deps, env, position_idx, receiver), - HandleMsg::UnlockPositionFunds { position_idx } => { - unlock_position_funds(deps, env, position_idx) - } - HandleMsg::ReleasePositionFunds { position_idx } => { - release_position_funds(deps, env, position_idx) - } - } -} - -pub fn update_config( - deps: &mut Extern, - env: Env, - owner: Option, - mint_contract: Option, - base_denom: Option, - lockup_period: Option, -) -> StdResult { - let mut config: Config = read_config(&deps.storage)?; - - if deps.api.canonical_address(&env.message.sender)? != config.owner { - return Err(StdError::unauthorized()); - } - - if let Some(owner) = owner { - config.owner = deps.api.canonical_address(&owner)?; - } - - if let Some(mint_contract) = mint_contract { - config.mint_contract = deps.api.canonical_address(&mint_contract)?; - } - - if let Some(base_denom) = base_denom { - config.base_denom = base_denom; - } - - if let Some(lockup_period) = lockup_period { - config.lockup_period = lockup_period; - } - - store_config(&mut deps.storage, &config)?; - Ok(HandleResponse { - messages: vec![], - log: vec![log("action", "update_config")], - data: None, - }) -} - -pub fn lock_position_funds_hook( - deps: &mut Extern, - env: Env, - position_idx: Uint128, - receiver: HumanAddr, -) -> StdResult { - let config: Config = read_config(&deps.storage)?; - let sender_addr_raw: CanonicalAddr = deps.api.canonical_address(&env.message.sender)?; - if sender_addr_raw != config.mint_contract { - return Err(StdError::unauthorized()); - } - - let current_balance: Uint128 = - query_balance(deps, &env.contract.address, config.base_denom.clone())?; - let locked_funds: Uint128 = total_locked_funds_read(&deps.storage).load()?; - let position_locked_amount: Uint128 = (current_balance - locked_funds)?; - - if position_locked_amount.is_zero() { - // nothing to lock - return Err(StdError::generic_err("Nothing to lock")); - } - - let unlock_time: u64 = env.block.time + config.lockup_period; - let receiver_raw: CanonicalAddr = deps.api.canonical_address(&receiver)?; - let lock_info: PositionLockInfo = - if let Ok(mut lock_info) = read_position_lock_info(&deps.storage, position_idx) { - // assert position receiver - if receiver_raw != lock_info.receiver { - // should never happen - return Err(StdError::generic_err( - "Receiver address do not match with existing record", - )); - } - // increase amount - lock_info.locked_amount += position_locked_amount; - lock_info.unlock_time = unlock_time; - lock_info - } else { - PositionLockInfo { - idx: position_idx, - receiver: receiver_raw, - locked_amount: position_locked_amount, - unlock_time: unlock_time, - } - }; - - store_position_lock_info(&mut deps.storage, &lock_info)?; - total_locked_funds_store(&mut deps.storage).save(¤t_balance)?; - - Ok(HandleResponse { - log: vec![ - log("action", "lock_position_funds_hook"), - log("position_idx", position_idx.to_string()), - log( - "locked_amount", - position_locked_amount.to_string() + &config.base_denom, - ), - log( - "total_locked_amount", - lock_info.locked_amount.to_string() + &config.base_denom, - ), - log("unlock_time", unlock_time), - ], - messages: vec![], - data: None, - }) -} - -pub fn unlock_position_funds( - deps: &mut Extern, - env: Env, - position_idx: Uint128, -) -> StdResult { - let config: Config = read_config(&deps.storage)?; - let sender_addr_raw: CanonicalAddr = deps.api.canonical_address(&env.message.sender)?; - let lock_info: PositionLockInfo = match read_position_lock_info(&deps.storage, position_idx) { - Ok(lock_info) => lock_info, - Err(_) => { - return Err(StdError::generic_err( - "There are no locked funds for this position idx", - )) - } - }; - - // only position owner can unlock funds - if sender_addr_raw != lock_info.receiver { - return Err(StdError::unauthorized()); - } - - if env.block.time < lock_info.unlock_time { - return Err(StdError::generic_err(format!( - "Lock period has not expired yet. Unlocks at {}", - lock_info.unlock_time - ))); - } - - let unlok_amount: Uint128 = lock_info.locked_amount; - let unlock_asset = Asset { - info: AssetInfo::NativeToken { - denom: config.base_denom.clone(), - }, - amount: unlok_amount, - }; - - // remove lock record - remove_position_lock_info(&mut deps.storage, position_idx); - - // decrease locked amount - total_locked_funds_store(&mut deps.storage).update(|current| { - let new_total = (current - unlok_amount)?; - Ok(new_total) - })?; - - let tax_amount: Uint128 = unlock_asset.compute_tax(&deps)?; - - Ok(HandleResponse { - log: vec![ - log("action", "unlock_shorting_funds"), - log("position_idx", position_idx.to_string()), - log("unlocked_amount", unlock_asset.to_string()), - log("tax_amount", tax_amount.to_string() + &config.base_denom), - ], - messages: vec![unlock_asset.into_msg( - &deps, - env.contract.address, - deps.api.human_address(&lock_info.receiver)?, - )?], - data: None, - }) -} - -pub fn release_position_funds( - deps: &mut Extern, - env: Env, - position_idx: Uint128, -) -> StdResult { - let config: Config = read_config(&deps.storage)?; - let sender_addr_raw: CanonicalAddr = deps.api.canonical_address(&env.message.sender)?; - // only mint contract can force claim all funds, without checking lock period - if sender_addr_raw != config.mint_contract { - return Err(StdError::unauthorized()); - } - - let lock_info: PositionLockInfo = match read_position_lock_info(&deps.storage, position_idx) { - Ok(lock_info) => lock_info, - Err(_) => { - return Ok(HandleResponse::default()); // user previously unlocked funds, graceful return - } - }; - - // ingnore lock period, and unlock funds - let unlock_amount: Uint128 = lock_info.locked_amount; - let unlock_asset = Asset { - info: AssetInfo::NativeToken { - denom: config.base_denom.clone(), - }, - amount: unlock_amount, - }; - - // remove position info - remove_position_lock_info(&mut deps.storage, position_idx); - - // decrease locked amount - total_locked_funds_store(&mut deps.storage).update(|current| { - let new_total = (current - unlock_amount)?; - Ok(new_total) - })?; - - let tax_amount: Uint128 = unlock_asset.compute_tax(&deps)?; - - Ok(HandleResponse { - log: vec![ - log("action", "release_shorting_funds"), - log("position_idx", position_idx.to_string()), - log("unlocked_amount", unlock_asset.to_string()), - log("tax_amount", tax_amount.to_string() + &config.base_denom), - ], - messages: vec![unlock_asset.into_msg( - &deps, - env.contract.address, - deps.api.human_address(&lock_info.receiver)?, - )?], - data: None, - }) -} - -pub fn query( - deps: &Extern, - msg: QueryMsg, -) -> StdResult { - match msg { - QueryMsg::Config {} => to_binary(&query_config(deps)?), - QueryMsg::PositionLockInfo { position_idx } => { - to_binary(&query_position_lock_info(deps, position_idx)?) - } - } -} - -pub fn query_config( - deps: &Extern, -) -> StdResult { - let state = read_config(&deps.storage)?; - let resp = ConfigResponse { - owner: deps.api.human_address(&state.owner)?, - mint_contract: deps.api.human_address(&state.mint_contract)?, - base_denom: state.base_denom, - lockup_period: state.lockup_period, - }; - - Ok(resp) -} - -pub fn query_position_lock_info( - deps: &Extern, - position_idx: Uint128, -) -> StdResult { - let lock_info: PositionLockInfo = read_position_lock_info(&deps.storage, position_idx)?; - - let resp = PositionLockInfoResponse { - idx: lock_info.idx, - receiver: deps.api.human_address(&lock_info.receiver)?, - locked_amount: lock_info.locked_amount, - unlock_time: lock_info.unlock_time, - }; - - Ok(resp) -} diff --git a/contracts/mirror_lock/src/lib.rs b/contracts/mirror_lock/src/lib.rs deleted file mode 100644 index 565496db4..000000000 --- a/contracts/mirror_lock/src/lib.rs +++ /dev/null @@ -1,10 +0,0 @@ -pub mod contract; -mod state; - -#[cfg(test)] -mod mock_querier; -#[cfg(test)] -mod tests; - -#[cfg(target_arch = "wasm32")] -cosmwasm_std::create_entry_points!(contract); diff --git a/contracts/mirror_lock/src/mock_querier.rs b/contracts/mirror_lock/src/mock_querier.rs deleted file mode 100644 index 670a25afa..000000000 --- a/contracts/mirror_lock/src/mock_querier.rs +++ /dev/null @@ -1,125 +0,0 @@ -use cosmwasm_std::testing::{MockApi, MockQuerier, MockStorage, MOCK_CONTRACT_ADDR}; -use cosmwasm_std::{ - from_slice, to_binary, Api, Coin, Decimal, Extern, HumanAddr, Querier, QuerierResult, - QueryRequest, SystemError, Uint128, -}; -use std::collections::HashMap; -use terra_cosmwasm::{TaxCapResponse, TaxRateResponse, TerraQuery, TerraQueryWrapper, TerraRoute}; - -pub fn mock_dependencies( - canonical_length: usize, - contract_balance: &[Coin], -) -> Extern { - let contract_addr = HumanAddr::from(MOCK_CONTRACT_ADDR); - let custom_querier: WasmMockQuerier = WasmMockQuerier::new( - MockQuerier::new(&[(&contract_addr, contract_balance)]), - MockApi::new(canonical_length), - canonical_length, - ); - - Extern { - storage: MockStorage::default(), - api: MockApi::new(canonical_length), - querier: custom_querier, - } -} - -pub struct WasmMockQuerier { - base: MockQuerier, - tax_querier: TaxQuerier, -} - -#[derive(Clone, Default)] -pub struct TaxQuerier { - rate: Decimal, - // this lets us iterate over all pairs that match the first string - caps: HashMap, -} - -impl TaxQuerier { - pub fn new(rate: Decimal, caps: &[(&String, &Uint128)]) -> Self { - TaxQuerier { - rate, - caps: caps_to_map(caps), - } - } -} - -pub(crate) fn caps_to_map(caps: &[(&String, &Uint128)]) -> HashMap { - let mut owner_map: HashMap = HashMap::new(); - for (denom, cap) in caps.iter() { - owner_map.insert(denom.to_string(), **cap); - } - owner_map -} - -impl Querier for WasmMockQuerier { - fn raw_query(&self, bin_request: &[u8]) -> QuerierResult { - // MockQuerier doesn't support Custom, so we ignore it completely here - let request: QueryRequest = match from_slice(bin_request) { - Ok(v) => v, - Err(e) => { - return Err(SystemError::InvalidRequest { - error: format!("Parsing query request: {}", e), - request: bin_request.into(), - }) - } - }; - self.handle_query(&request) - } -} - -impl WasmMockQuerier { - pub fn handle_query(&self, request: &QueryRequest) -> QuerierResult { - match &request { - QueryRequest::Custom(TerraQueryWrapper { route, query_data }) => { - if route == &TerraRoute::Treasury { - match query_data { - TerraQuery::TaxRate {} => { - let res = TaxRateResponse { - rate: self.tax_querier.rate, - }; - Ok(to_binary(&res)) - } - TerraQuery::TaxCap { denom } => { - let cap = self - .tax_querier - .caps - .get(denom) - .copied() - .unwrap_or_default(); - let res = TaxCapResponse { cap }; - Ok(to_binary(&res)) - } - _ => panic!("DO NOT ENTER HERE"), - } - } else { - panic!("DO NOT ENTER HERE") - } - } - _ => self.base.handle_query(request), - } - } -} - -impl WasmMockQuerier { - pub fn new( - base: MockQuerier, - _api: A, - _canonical_length: usize, - ) -> Self { - WasmMockQuerier { - base, - tax_querier: TaxQuerier::default(), - } - } - - pub fn with_bank_balance(&mut self, addr: &HumanAddr, balance: Vec) { - self.base.update_balance(addr, balance); - } - - // configure the token owner mock querier - pub fn with_tax(&mut self, rate: Decimal, caps: &[(&String, &Uint128)]) { - self.tax_querier = TaxQuerier::new(rate, caps); - } -} diff --git a/contracts/mirror_lock/src/state.rs b/contracts/mirror_lock/src/state.rs deleted file mode 100644 index e9e4f7680..000000000 --- a/contracts/mirror_lock/src/state.rs +++ /dev/null @@ -1,66 +0,0 @@ -use cosmwasm_std::{CanonicalAddr, StdResult, Storage, Uint128}; -use cosmwasm_storage::{ - singleton, singleton_read, Bucket, ReadonlyBucket, ReadonlySingleton, Singleton, -}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -static PREFIX_LOCK_INFOS: &[u8] = b"lock_infos"; -static KEY_CONFIG: &[u8] = b"config"; -static KEY_TOTAL_LOCKED_FUNDS: &[u8] = b"total_locked_funds"; - -pub fn total_locked_funds_store(storage: &mut S) -> Singleton { - singleton(storage, KEY_TOTAL_LOCKED_FUNDS) -} - -pub fn total_locked_funds_read(storage: &S) -> ReadonlySingleton { - singleton_read(storage, KEY_TOTAL_LOCKED_FUNDS) -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct Config { - pub owner: CanonicalAddr, - pub mint_contract: CanonicalAddr, - pub base_denom: String, - pub lockup_period: u64, -} - -pub fn store_config(storage: &mut S, config: &Config) -> StdResult<()> { - singleton(storage, KEY_CONFIG).save(config) -} - -pub fn read_config(storage: &S) -> StdResult { - singleton_read(storage, KEY_CONFIG).load() -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct PositionLockInfo { - pub idx: Uint128, - pub receiver: CanonicalAddr, - pub locked_amount: Uint128, - pub unlock_time: u64, -} - -pub fn store_position_lock_info( - storage: &mut S, - lock_info: &PositionLockInfo, -) -> StdResult<()> { - let mut lock_infos_bucket: Bucket = - Bucket::new(PREFIX_LOCK_INFOS, storage); - lock_infos_bucket.save(&lock_info.idx.u128().to_be_bytes(), &lock_info) -} - -pub fn read_position_lock_info( - storage: &S, - idx: Uint128, -) -> StdResult { - let lock_infos_bucket: ReadonlyBucket = - ReadonlyBucket::new(PREFIX_LOCK_INFOS, storage); - lock_infos_bucket.load(&idx.u128().to_be_bytes()) -} - -pub fn remove_position_lock_info(storage: &mut S, idx: Uint128) { - let mut lock_infos_bucket: Bucket = - Bucket::new(PREFIX_LOCK_INFOS, storage); - lock_infos_bucket.remove(&idx.u128().to_be_bytes()) -} diff --git a/contracts/mirror_lock/src/tests.rs b/contracts/mirror_lock/src/tests.rs deleted file mode 100644 index 4719e7db0..000000000 --- a/contracts/mirror_lock/src/tests.rs +++ /dev/null @@ -1,315 +0,0 @@ -use crate::contract::{handle, init, query}; -use crate::mock_querier::mock_dependencies; -use cosmwasm_std::testing::{mock_env, MOCK_CONTRACT_ADDR}; -use cosmwasm_std::{ - from_binary, log, BankMsg, BlockInfo, Coin, CosmosMsg, Decimal, Env, HumanAddr, StdError, - Uint128, -}; -use mirror_protocol::lock::{ - ConfigResponse, HandleMsg, InitMsg, PositionLockInfoResponse, QueryMsg, -}; - -fn mock_env_with_block_time>(sender: U, sent: &[Coin], time: u64) -> Env { - let env = mock_env(sender, sent); - // register time - return Env { - block: BlockInfo { - height: 1, - time, - chain_id: "columbus".to_string(), - }, - ..env - }; -} - -#[test] -fn proper_initialization() { - let mut deps = mock_dependencies(20, &[]); - let msg = InitMsg { - owner: HumanAddr::from("owner0000"), - mint_contract: HumanAddr::from("mint0000"), - base_denom: "uusd".to_string(), - lockup_period: 100u64, - }; - let env = mock_env("addr0000", &[]); - // we can just call .unwrap() to assert this was a success - let _res = init(&mut deps, env.clone(), msg).unwrap(); - // it worked, let's query the state - let res = query(&deps, QueryMsg::Config {}).unwrap(); - let config: ConfigResponse = from_binary(&res).unwrap(); - assert_eq!("owner0000", config.owner.as_str()); - assert_eq!("uusd", config.base_denom.to_string()); - assert_eq!("mint0000", config.mint_contract.as_str()); - assert_eq!(100u64, config.lockup_period); -} - -#[test] -fn update_config() { - let mut deps = mock_dependencies(20, &[]); - let msg = InitMsg { - owner: HumanAddr::from("owner0000"), - mint_contract: HumanAddr::from("mint0000"), - base_denom: "uusd".to_string(), - lockup_period: 100u64, - }; - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env.clone(), msg).unwrap(); - // update owner - let env = mock_env("owner0000", &[]); - let msg = HandleMsg::UpdateConfig { - owner: Some(HumanAddr("owner0001".to_string())), - mint_contract: None, - base_denom: None, - lockup_period: None, - }; - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!(0, res.messages.len()); - // it worked, let's query the state - let res = query(&deps, QueryMsg::Config {}).unwrap(); - let config: ConfigResponse = from_binary(&res).unwrap(); - assert_eq!("owner0001", config.owner.as_str()); - // Unauthorized err - let env = mock_env("owner0000", &[]); - let msg = HandleMsg::UpdateConfig { - owner: None, - mint_contract: None, - base_denom: None, - lockup_period: None, - }; - let res = handle(&mut deps, env, msg).unwrap_err(); - assert_eq!(res, StdError::unauthorized()); -} - -#[test] -fn lock_position_funds() { - let mut deps = mock_dependencies(20, &[]); - let msg = InitMsg { - owner: HumanAddr::from("owner0000"), - mint_contract: HumanAddr::from("mint0000"), - base_denom: "uusd".to_string(), - lockup_period: 100u64, - }; - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env.clone(), msg).unwrap(); - - deps.querier.with_bank_balance( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - vec![Coin { - denom: "uusd".to_string(), - amount: Uint128(100u128), // lock 100uusd - }], - ); - let msg = HandleMsg::LockPositionFundsHook { - position_idx: Uint128(1u128), - receiver: HumanAddr::from("addr0000"), - }; - - // unauthorized attempt - let env = mock_env("addr0000", &[]); - let res = handle(&mut deps, env.clone(), msg.clone()).unwrap_err(); - assert_eq!(res, StdError::unauthorized()); - - // successfull attempt - let env = mock_env_with_block_time("mint0000", &[], 20u64); - let res = handle(&mut deps, env.clone(), msg.clone()).unwrap(); - assert_eq!( - res.log, - vec![ - log("action", "lock_position_funds_hook"), - log("position_idx", "1"), - log("locked_amount", "100uusd"), - log("total_locked_amount", "100uusd"), - log("unlock_time", "120"), - ] - ); - - // query lock info - let res: PositionLockInfoResponse = from_binary( - &query( - &deps, - QueryMsg::PositionLockInfo { - position_idx: Uint128(1u128), - }, - ) - .unwrap(), - ) - .unwrap(); - assert_eq!( - res, - PositionLockInfoResponse { - idx: Uint128(1u128), - receiver: HumanAddr::from("addr0000"), - locked_amount: Uint128(100u128), - unlock_time: 120u64, - } - ); -} - -#[test] -fn unlock_position_funds() { - let mut deps = mock_dependencies(20, &[]); - deps.querier.with_tax( - Decimal::percent(1u64), - &[(&"uusd".to_string(), &Uint128(100000000u128))], - ); - let msg = InitMsg { - owner: HumanAddr::from("owner0000"), - mint_contract: HumanAddr::from("mint0000"), - base_denom: "uusd".to_string(), - lockup_period: 100u64, - }; - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env.clone(), msg).unwrap(); - - // lock 100 UST 3 times at different heights - let msg = HandleMsg::LockPositionFundsHook { - position_idx: Uint128(1u128), - receiver: HumanAddr::from("addr0000"), - }; - let env = mock_env_with_block_time("mint0000", &[], 1u64); - deps.querier.with_bank_balance( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - vec![Coin { - denom: "uusd".to_string(), - amount: Uint128(100u128), // lock 100uusd - }], - ); - let _res = handle(&mut deps, env, msg.clone()).unwrap(); - let env = mock_env_with_block_time("mint0000", &[], 10u64); - deps.querier.with_bank_balance( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - vec![Coin { - denom: "uusd".to_string(), - amount: Uint128(200u128), // lock 100uusd more - }], - ); - let _res = handle(&mut deps, env, msg.clone()).unwrap(); - - // query lock info - let res: PositionLockInfoResponse = from_binary( - &query( - &deps, - QueryMsg::PositionLockInfo { - position_idx: Uint128(1u128), - }, - ) - .unwrap(), - ) - .unwrap(); - assert_eq!( - res, - PositionLockInfoResponse { - idx: Uint128(1u128), - receiver: HumanAddr::from("addr0000"), - locked_amount: Uint128(200), - unlock_time: 10u64 + 100u64, // from last lock time - } - ); - - let msg = HandleMsg::UnlockPositionFunds { - position_idx: Uint128(1u128), - }; - - // unauthorized attempt - let env = mock_env("addr0001", &[]); - let res = handle(&mut deps, env.clone(), msg.clone()).unwrap_err(); - assert_eq!(res, StdError::unauthorized()); - - // nothing to unlock - let env = mock_env_with_block_time("addr0000", &[], 50u64); - let res = handle(&mut deps, env, msg.clone()).unwrap_err(); - assert_eq!( - res, - StdError::generic_err("Lock period has not expired yet. Unlocks at 110") - ); - - // unlock 100 UST - let env = mock_env_with_block_time("addr0000", &[], 120u64); - let res = handle(&mut deps, env, msg.clone()).unwrap(); - assert_eq!( - res.log, - vec![ - log("action", "unlock_shorting_funds"), - log("position_idx", "1"), - log("unlocked_amount", "200uusd"), - log("tax_amount", "2uusd"), - ] - ); - assert_eq!( - res.messages, - vec![CosmosMsg::Bank(BankMsg::Send { - from_address: HumanAddr::from(MOCK_CONTRACT_ADDR), - to_address: HumanAddr::from("addr0000"), - amount: vec![Coin { - denom: "uusd".to_string(), - amount: Uint128(198u128), // minus tax - }] - })] - ); - - // lock info does not exist anymore - let env = mock_env_with_block_time("addr0000", &[], 120u64); - let res = handle(&mut deps, env, msg.clone()).unwrap_err(); - assert_eq!( - res, - StdError::generic_err("There are no locked funds for this position idx") - ); -} - -#[test] -fn release_position_funds() { - let mut deps = mock_dependencies(20, &[]); - deps.querier.with_tax( - Decimal::percent(1u64), - &[(&"uusd".to_string(), &Uint128(100000000u128))], - ); - let msg = InitMsg { - owner: HumanAddr::from("owner0000"), - mint_contract: HumanAddr::from("mint0000"), - base_denom: "uusd".to_string(), - lockup_period: 100u64, - }; - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env.clone(), msg).unwrap(); - - // lock 100 UST - let msg = HandleMsg::LockPositionFundsHook { - position_idx: Uint128(1u128), - receiver: HumanAddr::from("addr0000"), - }; - let env = mock_env_with_block_time("mint0000", &[], 1u64); - deps.querier.with_bank_balance( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - vec![Coin { - denom: "uusd".to_string(), - amount: Uint128(100u128), // lock 100uusd - }], - ); - let _res = handle(&mut deps, env, msg.clone()).unwrap(); - - let msg = HandleMsg::ReleasePositionFunds { - position_idx: Uint128(1u128), - }; - - // unauthorized attempt - let env = mock_env_with_block_time("addr0000", &[], 1u64); - let res = handle(&mut deps, env, msg.clone()).unwrap_err(); - assert_eq!(res, StdError::unauthorized()); - - // only mint contract can unlock before lock period is over - let env = mock_env_with_block_time("mint0000", &[], 50u64); - let res = handle(&mut deps, env.clone(), msg.clone()).unwrap(); - assert_eq!( - res.log, - vec![ - log("action", "release_shorting_funds"), - log("position_idx", "1"), - log("unlocked_amount", "100uusd"), - log("tax_amount", "1uusd"), - ] - ); - - // lock info does not exist anymore, graceful return - let res = handle(&mut deps, env, msg.clone()).unwrap(); - assert_eq!(res.log.len(), 0); -} diff --git a/contracts/mirror_mint/.cargo/config b/contracts/mirror_mint/.cargo/config deleted file mode 100644 index 7c115322a..000000000 --- a/contracts/mirror_mint/.cargo/config +++ /dev/null @@ -1,6 +0,0 @@ -[alias] -wasm = "build --release --target wasm32-unknown-unknown" -wasm-debug = "build --target wasm32-unknown-unknown" -unit-test = "test --lib --features backtraces" -integration-test = "test --test integration" -schema = "run --example schema" diff --git a/contracts/mirror_mint/Cargo.toml b/contracts/mirror_mint/Cargo.toml deleted file mode 100644 index f26522db3..000000000 --- a/contracts/mirror_mint/Cargo.toml +++ /dev/null @@ -1,47 +0,0 @@ -[package] -name = "mirror-mint" -version = "1.1.0" -authors = ["Terraform Labs, PTE."] -edition = "2018" -description = "A Mint contract for Mirror Protocol - allows you to register and mint asset token" -license = "Apache-2.0" - -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] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces"] - -[dependencies] -cw20 = { version = "0.2" } -cosmwasm-std = { version = "0.10.1", features = ["iterator"] } -cosmwasm-storage = { version = "0.10.1", features = ["iterator"] } -mirror-protocol = { version = "1.1.0", path = "../../packages/mirror_protocol" } -terraswap = "1.1.0" -schemars = "0.7" -serde = { version = "1.0.103", default-features = false, features = ["derive"] } - -[dev-dependencies] -terra-cosmwasm = { version = "1.2.2" } -cosmwasm-schema = "0.10.1" \ No newline at end of file diff --git a/contracts/mirror_mint/README.md b/contracts/mirror_mint/README.md deleted file mode 100644 index ca3432df1..000000000 --- a/contracts/mirror_mint/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Mirror Mint - -**NOTE**: Reference documentation for this contract is available [here](https://docs.mirror.finance/contracts/mint). - -The Mint Contract implements the logic for Collateralized Debt Positions (CDPs), through which users can mint new mAsset tokens against their deposited collateral (UST or mAssets). Current prices of collateral and minted mAssets are read from the Oracle Contract determine the C-ratio of each CDP. The Mint Contract also contains the logic for liquidating CDPs with C-ratios below the minimum for their minted mAsset through auction. diff --git a/contracts/mirror_mint/examples/schema.rs b/contracts/mirror_mint/examples/schema.rs deleted file mode 100644 index b77e43946..000000000 --- a/contracts/mirror_mint/examples/schema.rs +++ /dev/null @@ -1,30 +0,0 @@ -use std::env::current_dir; -use std::fs::create_dir_all; - -use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; -use cosmwasm_std::HandleResponse; - -use mirror_protocol::mint::{ - AssetConfigResponse, ConfigResponse, Cw20HookMsg, HandleMsg, InitMsg, MigrateMsg, - NextPositionIdxResponse, PositionResponse, PositionsResponse, QueryMsg, ShortParams, -}; - -fn main() { - let mut out_dir = current_dir().unwrap(); - out_dir.push("schema"); - create_dir_all(&out_dir).unwrap(); - remove_schemas(&out_dir).unwrap(); - - export_schema(&schema_for!(InitMsg), &out_dir); - export_schema(&schema_for!(HandleMsg), &out_dir); - export_schema(&schema_for!(ShortParams), &out_dir); - export_schema(&schema_for!(Cw20HookMsg), &out_dir); - export_schema(&schema_for!(HandleResponse), &out_dir); - export_schema(&schema_for!(QueryMsg), &out_dir); - export_schema(&schema_for!(MigrateMsg), &out_dir); - export_schema(&schema_for!(ConfigResponse), &out_dir); - export_schema(&schema_for!(NextPositionIdxResponse), &out_dir); - export_schema(&schema_for!(AssetConfigResponse), &out_dir); - export_schema(&schema_for!(PositionResponse), &out_dir); - export_schema(&schema_for!(PositionsResponse), &out_dir); -} diff --git a/contracts/mirror_mint/rustfmt.toml b/contracts/mirror_mint/rustfmt.toml deleted file mode 100644 index 11a85e6a9..000000000 --- a/contracts/mirror_mint/rustfmt.toml +++ /dev/null @@ -1,15 +0,0 @@ -# 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/mirror_mint/schema/asset_config_response.json b/contracts/mirror_mint/schema/asset_config_response.json deleted file mode 100644 index 8c6f19a79..000000000 --- a/contracts/mirror_mint/schema/asset_config_response.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "AssetConfigResponse", - "type": "object", - "required": [ - "auction_discount", - "min_collateral_ratio", - "token" - ], - "properties": { - "auction_discount": { - "$ref": "#/definitions/Decimal" - }, - "end_price": { - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - }, - "ipo_params": { - "anyOf": [ - { - "$ref": "#/definitions/IPOParams" - }, - { - "type": "null" - } - ] - }, - "min_collateral_ratio": { - "$ref": "#/definitions/Decimal" - }, - "token": { - "$ref": "#/definitions/HumanAddr" - } - }, - "definitions": { - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "HumanAddr": { - "type": "string" - }, - "IPOParams": { - "type": "object", - "required": [ - "min_collateral_ratio_after_ipo", - "mint_end", - "pre_ipo_price" - ], - "properties": { - "min_collateral_ratio_after_ipo": { - "$ref": "#/definitions/Decimal" - }, - "mint_end": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "pre_ipo_price": { - "$ref": "#/definitions/Decimal" - } - } - } - } -} diff --git a/contracts/mirror_mint/schema/config_response.json b/contracts/mirror_mint/schema/config_response.json deleted file mode 100644 index 9bab3908e..000000000 --- a/contracts/mirror_mint/schema/config_response.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ConfigResponse", - "type": "object", - "required": [ - "base_denom", - "collateral_oracle", - "collector", - "lock", - "oracle", - "owner", - "protocol_fee_rate", - "staking", - "terraswap_factory", - "token_code_id" - ], - "properties": { - "base_denom": { - "type": "string" - }, - "collateral_oracle": { - "$ref": "#/definitions/HumanAddr" - }, - "collector": { - "$ref": "#/definitions/HumanAddr" - }, - "lock": { - "$ref": "#/definitions/HumanAddr" - }, - "oracle": { - "$ref": "#/definitions/HumanAddr" - }, - "owner": { - "$ref": "#/definitions/HumanAddr" - }, - "protocol_fee_rate": { - "$ref": "#/definitions/Decimal" - }, - "staking": { - "$ref": "#/definitions/HumanAddr" - }, - "terraswap_factory": { - "$ref": "#/definitions/HumanAddr" - }, - "token_code_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "definitions": { - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "HumanAddr": { - "type": "string" - } - } -} diff --git a/contracts/mirror_mint/schema/cw20_hook_msg.json b/contracts/mirror_mint/schema/cw20_hook_msg.json deleted file mode 100644 index c22267d87..000000000 --- a/contracts/mirror_mint/schema/cw20_hook_msg.json +++ /dev/null @@ -1,178 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Cw20HookMsg", - "anyOf": [ - { - "type": "object", - "required": [ - "open_position" - ], - "properties": { - "open_position": { - "type": "object", - "required": [ - "asset_info", - "collateral_ratio" - ], - "properties": { - "asset_info": { - "$ref": "#/definitions/AssetInfo" - }, - "collateral_ratio": { - "$ref": "#/definitions/Decimal" - }, - "short_params": { - "anyOf": [ - { - "$ref": "#/definitions/ShortParams" - }, - { - "type": "null" - } - ] - } - } - } - } - }, - { - "description": "Deposit more collateral", - "type": "object", - "required": [ - "deposit" - ], - "properties": { - "deposit": { - "type": "object", - "required": [ - "position_idx" - ], - "properties": { - "position_idx": { - "$ref": "#/definitions/Uint128" - } - } - } - } - }, - { - "description": "Convert specified asset amount and send back to user", - "type": "object", - "required": [ - "burn" - ], - "properties": { - "burn": { - "type": "object", - "required": [ - "position_idx" - ], - "properties": { - "position_idx": { - "$ref": "#/definitions/Uint128" - } - } - } - } - }, - { - "description": "Buy discounted collateral from the contract with their asset tokens", - "type": "object", - "required": [ - "auction" - ], - "properties": { - "auction": { - "type": "object", - "required": [ - "position_idx" - ], - "properties": { - "position_idx": { - "$ref": "#/definitions/Uint128" - } - } - } - } - } - ], - "definitions": { - "AssetInfo": { - "anyOf": [ - { - "type": "object", - "required": [ - "token" - ], - "properties": { - "token": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "$ref": "#/definitions/HumanAddr" - } - } - } - } - }, - { - "type": "object", - "required": [ - "native_token" - ], - "properties": { - "native_token": { - "type": "object", - "required": [ - "denom" - ], - "properties": { - "denom": { - "type": "string" - } - } - } - } - } - ] - }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "HumanAddr": { - "type": "string" - }, - "ShortParams": { - "type": "object", - "properties": { - "belief_price": { - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - }, - "max_spread": { - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - } - } - }, - "Uint128": { - "type": "string" - } - } -} diff --git a/contracts/mirror_mint/schema/handle_msg.json b/contracts/mirror_mint/schema/handle_msg.json deleted file mode 100644 index 60b6ba292..000000000 --- a/contracts/mirror_mint/schema/handle_msg.json +++ /dev/null @@ -1,505 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "HandleMsg", - "anyOf": [ - { - "type": "object", - "required": [ - "receive" - ], - "properties": { - "receive": { - "$ref": "#/definitions/Cw20ReceiveMsg" - } - } - }, - { - "description": "Owner Operations Update config; only owner is allowed to execute it", - "type": "object", - "required": [ - "update_config" - ], - "properties": { - "update_config": { - "type": "object", - "properties": { - "collateral_oracle": { - "anyOf": [ - { - "$ref": "#/definitions/HumanAddr" - }, - { - "type": "null" - } - ] - }, - "collector": { - "anyOf": [ - { - "$ref": "#/definitions/HumanAddr" - }, - { - "type": "null" - } - ] - }, - "lock": { - "anyOf": [ - { - "$ref": "#/definitions/HumanAddr" - }, - { - "type": "null" - } - ] - }, - "oracle": { - "anyOf": [ - { - "$ref": "#/definitions/HumanAddr" - }, - { - "type": "null" - } - ] - }, - "owner": { - "anyOf": [ - { - "$ref": "#/definitions/HumanAddr" - }, - { - "type": "null" - } - ] - }, - "protocol_fee_rate": { - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - }, - "terraswap_factory": { - "anyOf": [ - { - "$ref": "#/definitions/HumanAddr" - }, - { - "type": "null" - } - ] - }, - "token_code_id": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - } - } - } - } - }, - { - "description": "Update asset related parameters", - "type": "object", - "required": [ - "update_asset" - ], - "properties": { - "update_asset": { - "type": "object", - "required": [ - "asset_token" - ], - "properties": { - "asset_token": { - "$ref": "#/definitions/HumanAddr" - }, - "auction_discount": { - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - }, - "ipo_params": { - "anyOf": [ - { - "$ref": "#/definitions/IPOParams" - }, - { - "type": "null" - } - ] - }, - "min_collateral_ratio": { - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - } - } - } - } - }, - { - "description": "Generate asset token initialize msg and register required infos except token address", - "type": "object", - "required": [ - "register_asset" - ], - "properties": { - "register_asset": { - "type": "object", - "required": [ - "asset_token", - "auction_discount", - "min_collateral_ratio" - ], - "properties": { - "asset_token": { - "$ref": "#/definitions/HumanAddr" - }, - "auction_discount": { - "$ref": "#/definitions/Decimal" - }, - "ipo_params": { - "anyOf": [ - { - "$ref": "#/definitions/IPOParams" - }, - { - "type": "null" - } - ] - }, - "min_collateral_ratio": { - "$ref": "#/definitions/Decimal" - } - } - } - } - }, - { - "type": "object", - "required": [ - "register_migration" - ], - "properties": { - "register_migration": { - "type": "object", - "required": [ - "asset_token", - "end_price" - ], - "properties": { - "asset_token": { - "$ref": "#/definitions/HumanAddr" - }, - "end_price": { - "$ref": "#/definitions/Decimal" - } - } - } - } - }, - { - "description": "Asset feeder is allowed to trigger IPO event on preIPO assets", - "type": "object", - "required": [ - "trigger_i_p_o" - ], - "properties": { - "trigger_i_p_o": { - "type": "object", - "required": [ - "asset_token" - ], - "properties": { - "asset_token": { - "$ref": "#/definitions/HumanAddr" - } - } - } - } - }, - { - "description": "User Operations", - "type": "object", - "required": [ - "open_position" - ], - "properties": { - "open_position": { - "type": "object", - "required": [ - "asset_info", - "collateral", - "collateral_ratio" - ], - "properties": { - "asset_info": { - "$ref": "#/definitions/AssetInfo" - }, - "collateral": { - "$ref": "#/definitions/Asset" - }, - "collateral_ratio": { - "$ref": "#/definitions/Decimal" - }, - "short_params": { - "anyOf": [ - { - "$ref": "#/definitions/ShortParams" - }, - { - "type": "null" - } - ] - } - } - } - } - }, - { - "description": "Deposit more collateral", - "type": "object", - "required": [ - "deposit" - ], - "properties": { - "deposit": { - "type": "object", - "required": [ - "collateral", - "position_idx" - ], - "properties": { - "collateral": { - "$ref": "#/definitions/Asset" - }, - "position_idx": { - "$ref": "#/definitions/Uint128" - } - } - } - } - }, - { - "description": "Withdraw collateral", - "type": "object", - "required": [ - "withdraw" - ], - "properties": { - "withdraw": { - "type": "object", - "required": [ - "collateral", - "position_idx" - ], - "properties": { - "collateral": { - "$ref": "#/definitions/Asset" - }, - "position_idx": { - "$ref": "#/definitions/Uint128" - } - } - } - } - }, - { - "description": "Convert all deposit collateral to asset", - "type": "object", - "required": [ - "mint" - ], - "properties": { - "mint": { - "type": "object", - "required": [ - "asset", - "position_idx" - ], - "properties": { - "asset": { - "$ref": "#/definitions/Asset" - }, - "position_idx": { - "$ref": "#/definitions/Uint128" - }, - "short_params": { - "anyOf": [ - { - "$ref": "#/definitions/ShortParams" - }, - { - "type": "null" - } - ] - } - } - } - } - } - ], - "definitions": { - "Asset": { - "type": "object", - "required": [ - "amount", - "info" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "info": { - "$ref": "#/definitions/AssetInfo" - } - } - }, - "AssetInfo": { - "anyOf": [ - { - "type": "object", - "required": [ - "token" - ], - "properties": { - "token": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "$ref": "#/definitions/HumanAddr" - } - } - } - } - }, - { - "type": "object", - "required": [ - "native_token" - ], - "properties": { - "native_token": { - "type": "object", - "required": [ - "denom" - ], - "properties": { - "denom": { - "type": "string" - } - } - } - } - } - ] - }, - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", - "type": "string" - }, - "Cw20ReceiveMsg": { - "description": "Cw20ReceiveMsg should be de/serialized under `Receive()` variant in a HandleMsg", - "type": "object", - "required": [ - "amount", - "sender" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "msg": { - "anyOf": [ - { - "$ref": "#/definitions/Binary" - }, - { - "type": "null" - } - ] - }, - "sender": { - "$ref": "#/definitions/HumanAddr" - } - } - }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "HumanAddr": { - "type": "string" - }, - "IPOParams": { - "type": "object", - "required": [ - "min_collateral_ratio_after_ipo", - "mint_end", - "pre_ipo_price" - ], - "properties": { - "min_collateral_ratio_after_ipo": { - "$ref": "#/definitions/Decimal" - }, - "mint_end": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "pre_ipo_price": { - "$ref": "#/definitions/Decimal" - } - } - }, - "ShortParams": { - "type": "object", - "properties": { - "belief_price": { - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - }, - "max_spread": { - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - } - } - }, - "Uint128": { - "type": "string" - } - } -} diff --git a/contracts/mirror_mint/schema/init_msg.json b/contracts/mirror_mint/schema/init_msg.json deleted file mode 100644 index 762c8311c..000000000 --- a/contracts/mirror_mint/schema/init_msg.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InitMsg", - "type": "object", - "required": [ - "base_denom", - "collateral_oracle", - "collector", - "lock", - "oracle", - "owner", - "protocol_fee_rate", - "staking", - "terraswap_factory", - "token_code_id" - ], - "properties": { - "base_denom": { - "type": "string" - }, - "collateral_oracle": { - "$ref": "#/definitions/HumanAddr" - }, - "collector": { - "$ref": "#/definitions/HumanAddr" - }, - "lock": { - "$ref": "#/definitions/HumanAddr" - }, - "oracle": { - "$ref": "#/definitions/HumanAddr" - }, - "owner": { - "$ref": "#/definitions/HumanAddr" - }, - "protocol_fee_rate": { - "$ref": "#/definitions/Decimal" - }, - "staking": { - "$ref": "#/definitions/HumanAddr" - }, - "terraswap_factory": { - "$ref": "#/definitions/HumanAddr" - }, - "token_code_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "definitions": { - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "HumanAddr": { - "type": "string" - } - } -} diff --git a/contracts/mirror_mint/schema/migrate_msg.json b/contracts/mirror_mint/schema/migrate_msg.json deleted file mode 100644 index aa7740800..000000000 --- a/contracts/mirror_mint/schema/migrate_msg.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "MigrateMsg", - "type": "object", - "required": [ - "collateral_oracle", - "lock", - "staking", - "terraswap_factory" - ], - "properties": { - "collateral_oracle": { - "$ref": "#/definitions/HumanAddr" - }, - "lock": { - "$ref": "#/definitions/HumanAddr" - }, - "staking": { - "$ref": "#/definitions/HumanAddr" - }, - "terraswap_factory": { - "$ref": "#/definitions/HumanAddr" - } - }, - "definitions": { - "HumanAddr": { - "type": "string" - } - } -} diff --git a/contracts/mirror_mint/schema/next_position_idx_response.json b/contracts/mirror_mint/schema/next_position_idx_response.json deleted file mode 100644 index 1b28ccf93..000000000 --- a/contracts/mirror_mint/schema/next_position_idx_response.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "NextPositionIdxResponse", - "type": "object", - "required": [ - "next_position_idx" - ], - "properties": { - "next_position_idx": { - "$ref": "#/definitions/Uint128" - } - }, - "definitions": { - "Uint128": { - "type": "string" - } - } -} diff --git a/contracts/mirror_mint/schema/position_response.json b/contracts/mirror_mint/schema/position_response.json deleted file mode 100644 index a9937558b..000000000 --- a/contracts/mirror_mint/schema/position_response.json +++ /dev/null @@ -1,94 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "PositionResponse", - "type": "object", - "required": [ - "asset", - "collateral", - "idx", - "is_short", - "owner" - ], - "properties": { - "asset": { - "$ref": "#/definitions/Asset" - }, - "collateral": { - "$ref": "#/definitions/Asset" - }, - "idx": { - "$ref": "#/definitions/Uint128" - }, - "is_short": { - "type": "boolean" - }, - "owner": { - "$ref": "#/definitions/HumanAddr" - } - }, - "definitions": { - "Asset": { - "type": "object", - "required": [ - "amount", - "info" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "info": { - "$ref": "#/definitions/AssetInfo" - } - } - }, - "AssetInfo": { - "anyOf": [ - { - "type": "object", - "required": [ - "token" - ], - "properties": { - "token": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "$ref": "#/definitions/HumanAddr" - } - } - } - } - }, - { - "type": "object", - "required": [ - "native_token" - ], - "properties": { - "native_token": { - "type": "object", - "required": [ - "denom" - ], - "properties": { - "denom": { - "type": "string" - } - } - } - } - } - ] - }, - "HumanAddr": { - "type": "string" - }, - "Uint128": { - "type": "string" - } - } -} diff --git a/contracts/mirror_mint/schema/positions_response.json b/contracts/mirror_mint/schema/positions_response.json deleted file mode 100644 index c3d4ed8a5..000000000 --- a/contracts/mirror_mint/schema/positions_response.json +++ /dev/null @@ -1,108 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "PositionsResponse", - "type": "object", - "required": [ - "positions" - ], - "properties": { - "positions": { - "type": "array", - "items": { - "$ref": "#/definitions/PositionResponse" - } - } - }, - "definitions": { - "Asset": { - "type": "object", - "required": [ - "amount", - "info" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "info": { - "$ref": "#/definitions/AssetInfo" - } - } - }, - "AssetInfo": { - "anyOf": [ - { - "type": "object", - "required": [ - "token" - ], - "properties": { - "token": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "$ref": "#/definitions/HumanAddr" - } - } - } - } - }, - { - "type": "object", - "required": [ - "native_token" - ], - "properties": { - "native_token": { - "type": "object", - "required": [ - "denom" - ], - "properties": { - "denom": { - "type": "string" - } - } - } - } - } - ] - }, - "HumanAddr": { - "type": "string" - }, - "PositionResponse": { - "type": "object", - "required": [ - "asset", - "collateral", - "idx", - "is_short", - "owner" - ], - "properties": { - "asset": { - "$ref": "#/definitions/Asset" - }, - "collateral": { - "$ref": "#/definitions/Asset" - }, - "idx": { - "$ref": "#/definitions/Uint128" - }, - "is_short": { - "type": "boolean" - }, - "owner": { - "$ref": "#/definitions/HumanAddr" - } - } - }, - "Uint128": { - "type": "string" - } - } -} diff --git a/contracts/mirror_mint/schema/query_msg.json b/contracts/mirror_mint/schema/query_msg.json deleted file mode 100644 index 925543b98..000000000 --- a/contracts/mirror_mint/schema/query_msg.json +++ /dev/null @@ -1,142 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryMsg", - "anyOf": [ - { - "type": "object", - "required": [ - "config" - ], - "properties": { - "config": { - "type": "object" - } - } - }, - { - "type": "object", - "required": [ - "asset_config" - ], - "properties": { - "asset_config": { - "type": "object", - "required": [ - "asset_token" - ], - "properties": { - "asset_token": { - "$ref": "#/definitions/HumanAddr" - } - } - } - } - }, - { - "type": "object", - "required": [ - "position" - ], - "properties": { - "position": { - "type": "object", - "required": [ - "position_idx" - ], - "properties": { - "position_idx": { - "$ref": "#/definitions/Uint128" - } - } - } - } - }, - { - "type": "object", - "required": [ - "positions" - ], - "properties": { - "positions": { - "type": "object", - "properties": { - "asset_token": { - "anyOf": [ - { - "$ref": "#/definitions/HumanAddr" - }, - { - "type": "null" - } - ] - }, - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "order_by": { - "anyOf": [ - { - "$ref": "#/definitions/OrderBy" - }, - { - "type": "null" - } - ] - }, - "owner_addr": { - "anyOf": [ - { - "$ref": "#/definitions/HumanAddr" - }, - { - "type": "null" - } - ] - }, - "start_after": { - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - } - } - } - } - }, - { - "type": "object", - "required": [ - "next_position_idx" - ], - "properties": { - "next_position_idx": { - "type": "object" - } - } - } - ], - "definitions": { - "HumanAddr": { - "type": "string" - }, - "OrderBy": { - "type": "string", - "enum": [ - "asc", - "desc" - ] - }, - "Uint128": { - "type": "string" - } - } -} diff --git a/contracts/mirror_mint/schema/short_params.json b/contracts/mirror_mint/schema/short_params.json deleted file mode 100644 index 85c83076f..000000000 --- a/contracts/mirror_mint/schema/short_params.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ShortParams", - "type": "object", - "properties": { - "belief_price": { - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - }, - "max_spread": { - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - } - }, - "definitions": { - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - } - } -} diff --git a/contracts/mirror_mint/src/asserts.rs b/contracts/mirror_mint/src/asserts.rs deleted file mode 100644 index 48fbca4eb..000000000 --- a/contracts/mirror_mint/src/asserts.rs +++ /dev/null @@ -1,109 +0,0 @@ -use crate::state::{AssetConfig, Position}; -use cosmwasm_std::{Api, Decimal, Env, Extern, Querier, StdError, StdResult, Storage}; -use terraswap::asset::Asset; - -// Check zero balance & same collateral with position -pub fn assert_collateral( - deps: &Extern, - position: &Position, - collateral: &Asset, -) -> StdResult<()> { - if !collateral - .info - .equal(&position.collateral.info.to_normal(&deps)?) - || collateral.amount.is_zero() - { - return Err(StdError::generic_err("Wrong collateral")); - } - - Ok(()) -} - -// Check zero balance & same asset with position -pub fn assert_asset( - deps: &Extern, - position: &Position, - asset: &Asset, -) -> StdResult<()> { - if !asset.info.equal(&position.asset.info.to_normal(&deps)?) || asset.amount.is_zero() { - return Err(StdError::generic_err("Wrong asset")); - } - - Ok(()) -} - -pub fn assert_migrated_asset(asset_config: &AssetConfig) -> StdResult<()> { - if asset_config.end_price.is_some() { - return Err(StdError::generic_err( - "Operation is not allowed for the deprecated asset", - )); - } - - Ok(()) -} - -pub fn assert_revoked_collateral( - load_collateral_res: (Decimal, Decimal, bool), -) -> StdResult<(Decimal, Decimal)> { - if load_collateral_res.2 { - return Err(StdError::generic_err( - "The collateral asset provided is no longer valid", - )); - } - - Ok((load_collateral_res.0, load_collateral_res.1)) -} - -pub fn assert_auction_discount(auction_discount: Decimal) -> StdResult<()> { - if auction_discount > Decimal::one() { - Err(StdError::generic_err( - "auction_discount must be smaller than 1", - )) - } else { - Ok(()) - } -} - -pub fn assert_min_collateral_ratio(min_collateral_ratio: Decimal) -> StdResult<()> { - if min_collateral_ratio < Decimal::one() { - Err(StdError::generic_err( - "min_collateral_ratio must be bigger than 1", - )) - } else { - Ok(()) - } -} - -pub fn assert_protocol_fee(protocol_fee_rate: Decimal) -> StdResult<()> { - if protocol_fee_rate >= Decimal::one() { - Err(StdError::generic_err( - "protocol_fee_rate must be smaller than 1", - )) - } else { - Ok(()) - } -} - -pub fn assert_mint_period(env: &Env, asset_config: &AssetConfig) -> StdResult<()> { - if let Some(ipo_params) = asset_config.ipo_params.clone() { - if ipo_params.mint_end < env.block.time { - return Err(StdError::generic_err(format!( - "The minting period for this asset ended at time {}", - ipo_params.mint_end - ))); - } - } - Ok(()) -} - -pub fn assert_burn_period(env: &Env, asset_config: &AssetConfig) -> StdResult<()> { - if let Some(ipo_params) = asset_config.ipo_params.clone() { - if ipo_params.mint_end < env.block.time { - return Err(StdError::generic_err(format!( - "Burning is disabled for assets with limitied minting time. Mint period ended at time {}", - ipo_params.mint_end - ))); - } - } - Ok(()) -} diff --git a/contracts/mirror_mint/src/contract.rs b/contracts/mirror_mint/src/contract.rs deleted file mode 100644 index 33dc4c8c3..000000000 --- a/contracts/mirror_mint/src/contract.rs +++ /dev/null @@ -1,562 +0,0 @@ -use cosmwasm_std::{ - from_binary, log, to_binary, Api, Binary, CanonicalAddr, CosmosMsg, Decimal, Env, Extern, - HandleResponse, HandleResult, HumanAddr, InitResponse, MigrateResponse, MigrateResult, Querier, - StdError, StdResult, Storage, Uint128, WasmMsg, WasmQuery, -}; - -use crate::{ - asserts::{assert_auction_discount, assert_min_collateral_ratio, assert_protocol_fee}, - migration::{migrate_asset_configs, migrate_config}, - positions::{ - auction, burn, deposit, mint, open_position, query_next_position_idx, query_position, - query_positions, withdraw, - }, - querier::load_oracle_feeder, - state::{ - read_asset_config, read_config, store_asset_config, store_config, store_position_idx, - AssetConfig, Config, - }, -}; - -use cw20::Cw20ReceiveMsg; -use mirror_protocol::mint::{ - AssetConfigResponse, ConfigResponse, Cw20HookMsg, HandleMsg, InitMsg, MigrateMsg, QueryMsg, -}; -use mirror_protocol::oracle::QueryMsg as OracleQueryMsg; -use mirror_protocol::{ - collateral_oracle::{HandleMsg as CollateralOracleHandleMsg, SourceType}, - mint::IPOParams, -}; -use terraswap::asset::{Asset, AssetInfo}; - -pub fn init( - deps: &mut Extern, - _env: Env, - msg: InitMsg, -) -> StdResult { - let config = Config { - owner: deps.api.canonical_address(&msg.owner)?, - oracle: deps.api.canonical_address(&msg.oracle)?, - collector: deps.api.canonical_address(&msg.collector)?, - collateral_oracle: deps.api.canonical_address(&msg.collateral_oracle)?, - staking: deps.api.canonical_address(&msg.staking)?, - terraswap_factory: deps.api.canonical_address(&msg.terraswap_factory)?, - lock: deps.api.canonical_address(&msg.lock)?, - base_denom: msg.base_denom, - token_code_id: msg.token_code_id, - protocol_fee_rate: msg.protocol_fee_rate, - }; - - store_config(&mut deps.storage, &config)?; - store_position_idx(&mut deps.storage, Uint128(1u128))?; - Ok(InitResponse::default()) -} - -pub fn handle( - deps: &mut Extern, - env: Env, - msg: HandleMsg, -) -> StdResult { - match msg { - HandleMsg::Receive(msg) => receive_cw20(deps, env, msg), - HandleMsg::UpdateConfig { - owner, - oracle, - collector, - collateral_oracle, - terraswap_factory, - lock, - token_code_id, - protocol_fee_rate, - } => update_config( - deps, - env, - owner, - oracle, - collector, - collateral_oracle, - terraswap_factory, - lock, - token_code_id, - protocol_fee_rate, - ), - HandleMsg::UpdateAsset { - asset_token, - auction_discount, - min_collateral_ratio, - ipo_params, - } => update_asset( - deps, - env, - asset_token, - auction_discount, - min_collateral_ratio, - ipo_params, - ), - HandleMsg::RegisterAsset { - asset_token, - auction_discount, - min_collateral_ratio, - ipo_params, - } => register_asset( - deps, - env, - asset_token, - auction_discount, - min_collateral_ratio, - ipo_params, - ), - HandleMsg::RegisterMigration { - asset_token, - end_price, - } => register_migration(deps, env, asset_token, end_price), - HandleMsg::TriggerIPO { asset_token } => trigger_ipo(deps, env, asset_token), - HandleMsg::OpenPosition { - collateral, - asset_info, - collateral_ratio, - short_params, - } => { - // only native token can be deposited directly - if !collateral.is_native_token() { - return Err(StdError::unauthorized()); - } - - // Check the actual deposit happens - collateral.assert_sent_native_token_balance(&env)?; - - open_position( - deps, - env.clone(), - env.message.sender, - collateral, - asset_info, - collateral_ratio, - short_params, - ) - } - HandleMsg::Deposit { - position_idx, - collateral, - } => { - // only native token can be deposited directly - if !collateral.is_native_token() { - return Err(StdError::unauthorized()); - } - - // Check the actual deposit happens - collateral.assert_sent_native_token_balance(&env)?; - - deposit(deps, env.message.sender, position_idx, collateral) - } - HandleMsg::Withdraw { - position_idx, - collateral, - } => withdraw(deps, env, position_idx, collateral), - HandleMsg::Mint { - position_idx, - asset, - short_params, - } => mint(deps, env, position_idx, asset, short_params), - } -} - -pub fn receive_cw20( - deps: &mut Extern, - env: Env, - cw20_msg: Cw20ReceiveMsg, -) -> HandleResult { - let passed_asset: Asset = Asset { - info: AssetInfo::Token { - contract_addr: env.message.sender.clone(), - }, - amount: cw20_msg.amount, - }; - - if let Some(msg) = cw20_msg.msg { - match from_binary(&msg)? { - Cw20HookMsg::OpenPosition { - asset_info, - collateral_ratio, - short_params, - } => open_position( - deps, - env, - cw20_msg.sender, - passed_asset, - asset_info, - collateral_ratio, - short_params, - ), - Cw20HookMsg::Deposit { position_idx } => { - deposit(deps, cw20_msg.sender, position_idx, passed_asset) - } - Cw20HookMsg::Burn { position_idx } => { - burn(deps, env, cw20_msg.sender, position_idx, passed_asset) - } - Cw20HookMsg::Auction { position_idx } => { - auction(deps, env, cw20_msg.sender, position_idx, passed_asset) - } - } - } else { - Err(StdError::generic_err("data should be given")) - } -} - -pub fn update_config( - deps: &mut Extern, - env: Env, - owner: Option, - oracle: Option, - collector: Option, - collateral_oracle: Option, - terraswap_factory: Option, - lock: Option, - token_code_id: Option, - protocol_fee_rate: Option, -) -> StdResult { - let mut config: Config = read_config(&deps.storage)?; - - if deps.api.canonical_address(&env.message.sender)? != config.owner { - return Err(StdError::unauthorized()); - } - - if let Some(owner) = owner { - config.owner = deps.api.canonical_address(&owner)?; - } - - if let Some(oracle) = oracle { - config.oracle = deps.api.canonical_address(&oracle)?; - } - - if let Some(collector) = collector { - config.collector = deps.api.canonical_address(&collector)?; - } - - if let Some(collateral_oracle) = collateral_oracle { - config.collateral_oracle = deps.api.canonical_address(&collateral_oracle)?; - } - - if let Some(terraswap_factory) = terraswap_factory { - config.terraswap_factory = deps.api.canonical_address(&terraswap_factory)?; - } - - if let Some(lock) = lock { - config.lock = deps.api.canonical_address(&lock)?; - } - - if let Some(token_code_id) = token_code_id { - config.token_code_id = token_code_id; - } - - if let Some(protocol_fee_rate) = protocol_fee_rate { - assert_protocol_fee(protocol_fee_rate)?; - config.protocol_fee_rate = protocol_fee_rate; - } - - store_config(&mut deps.storage, &config)?; - Ok(HandleResponse { - messages: vec![], - log: vec![log("action", "update_config")], - data: None, - }) -} - -pub fn update_asset( - deps: &mut Extern, - env: Env, - asset_token: HumanAddr, - auction_discount: Option, - min_collateral_ratio: Option, - ipo_params: Option, -) -> StdResult { - let config: Config = read_config(&deps.storage)?; - let asset_token_raw = deps.api.canonical_address(&asset_token)?; - let mut asset: AssetConfig = read_asset_config(&deps.storage, &asset_token_raw)?; - - if deps.api.canonical_address(&env.message.sender)? != config.owner { - return Err(StdError::unauthorized()); - } - - if let Some(auction_discount) = auction_discount { - assert_auction_discount(auction_discount)?; - asset.auction_discount = auction_discount; - } - - if let Some(min_collateral_ratio) = min_collateral_ratio { - assert_min_collateral_ratio(min_collateral_ratio)?; - asset.min_collateral_ratio = min_collateral_ratio; - } - - if let Some(ipo_params) = ipo_params { - assert_min_collateral_ratio(ipo_params.min_collateral_ratio_after_ipo)?; - asset.ipo_params = Some(ipo_params); - } - - store_asset_config(&mut deps.storage, &asset_token_raw, &asset)?; - Ok(HandleResponse { - messages: vec![], - log: vec![log("action", "update_asset")], - data: None, - }) -} - -pub fn register_asset( - deps: &mut Extern, - env: Env, - asset_token: HumanAddr, - auction_discount: Decimal, - min_collateral_ratio: Decimal, - ipo_params: Option, -) -> StdResult { - assert_auction_discount(auction_discount)?; - assert_min_collateral_ratio(min_collateral_ratio)?; - - let config: Config = read_config(&deps.storage)?; - - // permission check - if deps.api.canonical_address(&env.message.sender)? != config.owner { - return Err(StdError::unauthorized()); - } - - let asset_token_raw = deps.api.canonical_address(&asset_token)?; - if read_asset_config(&deps.storage, &asset_token_raw).is_ok() { - return Err(StdError::generic_err("Asset was already registered")); - } - - let mut messages: Vec = vec![]; - - // check if it is a preIPO asset - if let Some(params) = ipo_params.clone() { - assert_min_collateral_ratio(params.min_collateral_ratio_after_ipo)?; - } else { - // only non-preIPO assets can be used as collateral - messages.push(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: deps.api.human_address(&config.collateral_oracle)?, - send: vec![], - msg: to_binary(&CollateralOracleHandleMsg::RegisterCollateralAsset { - asset: AssetInfo::Token { - contract_addr: asset_token.clone(), - }, - multiplier: Decimal::one(), // default collateral multiplier for new mAssets - price_source: SourceType::TerraOracle { - terra_oracle_query: to_binary(&WasmQuery::Smart { - contract_addr: deps.api.human_address(&config.oracle)?, - msg: to_binary(&OracleQueryMsg::Price { - base_asset: config.base_denom, - quote_asset: asset_token.to_string(), - })?, - })?, - }, - })?, - })); - } - - // Store temp info into base asset store - store_asset_config( - &mut deps.storage, - &asset_token_raw, - &AssetConfig { - token: deps.api.canonical_address(&asset_token)?, - auction_discount, - min_collateral_ratio, - end_price: None, - ipo_params, - }, - )?; - - Ok(HandleResponse { - messages, - log: vec![log("action", "register"), log("asset_token", asset_token)], - data: None, - }) -} - -pub fn register_migration( - deps: &mut Extern, - env: Env, - asset_token: HumanAddr, - end_price: Decimal, -) -> StdResult { - let config = read_config(&deps.storage)?; - if config.owner != deps.api.canonical_address(&env.message.sender)? { - return Err(StdError::unauthorized()); - } - - let asset_token_raw = deps.api.canonical_address(&asset_token)?; - let asset_config: AssetConfig = read_asset_config(&deps.storage, &asset_token_raw)?; - - // update asset config - store_asset_config( - &mut deps.storage, - &asset_token_raw, - &AssetConfig { - end_price: Some(end_price), - min_collateral_ratio: Decimal::percent(100), - ipo_params: None, - ..asset_config - }, - )?; - - // flag asset as revoked in the collateral oracle - Ok(HandleResponse { - messages: vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: deps.api.human_address(&config.collateral_oracle)?, - send: vec![], - msg: to_binary(&CollateralOracleHandleMsg::RevokeCollateralAsset { - asset: AssetInfo::Token { - contract_addr: asset_token.clone(), - }, - })?, - })], - log: vec![ - log("action", "migrate_asset"), - log("asset_token", asset_token.as_str()), - log("end_price", end_price.to_string()), - ], - data: None, - }) -} - -pub fn trigger_ipo( - deps: &mut Extern, - env: Env, - asset_token: HumanAddr, -) -> StdResult { - let config = read_config(&deps.storage)?; - let asset_token_raw: CanonicalAddr = deps.api.canonical_address(&&asset_token)?; - let oracle_feeder: HumanAddr = deps.api.human_address(&load_oracle_feeder( - &deps, - &deps.api.human_address(&config.oracle)?, - &asset_token_raw, - )?)?; - - // only asset feeder can trigger ipo - if oracle_feeder != env.message.sender { - return Err(StdError::unauthorized()); - } - - let mut asset_config: AssetConfig = read_asset_config(&deps.storage, &asset_token_raw)?; - - let ipo_params: IPOParams = match asset_config.ipo_params { - Some(v) => v, - None => return Err(StdError::generic_err("Asset does not have IPO params")), - }; - - asset_config.min_collateral_ratio = ipo_params.min_collateral_ratio_after_ipo; - asset_config.ipo_params = None; - - store_asset_config(&mut deps.storage, &asset_token_raw, &asset_config)?; - - // register asset in collateral oracle - Ok(HandleResponse { - messages: vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: deps.api.human_address(&config.collateral_oracle)?, - send: vec![], - msg: to_binary(&CollateralOracleHandleMsg::RegisterCollateralAsset { - asset: AssetInfo::Token { - contract_addr: asset_token.clone(), - }, - multiplier: Decimal::one(), // default collateral multiplier for new mAssets - price_source: SourceType::TerraOracle { - terra_oracle_query: to_binary(&WasmQuery::Smart { - contract_addr: deps.api.human_address(&config.oracle)?, - msg: to_binary(&OracleQueryMsg::Price { - base_asset: config.base_denom, - quote_asset: asset_token.to_string(), - })?, - })?, - }, - })?, - })], - log: vec![ - log("action", "trigger_ipo"), - log("asset_token", asset_token.as_str()), - ], - data: None, - }) -} - -pub fn query( - deps: &Extern, - msg: QueryMsg, -) -> StdResult { - match msg { - QueryMsg::Config {} => to_binary(&query_config(deps)?), - QueryMsg::AssetConfig { asset_token } => to_binary(&query_asset_config(deps, asset_token)?), - QueryMsg::Position { position_idx } => to_binary(&query_position(deps, position_idx)?), - QueryMsg::Positions { - owner_addr, - asset_token, - start_after, - limit, - order_by, - } => to_binary(&query_positions( - deps, - owner_addr, - asset_token, - start_after, - limit, - order_by, - )?), - QueryMsg::NextPositionIdx {} => to_binary(&query_next_position_idx(deps)?), - } -} - -pub fn query_config( - deps: &Extern, -) -> StdResult { - let state = read_config(&deps.storage)?; - let resp = ConfigResponse { - owner: deps.api.human_address(&state.owner)?, - oracle: deps.api.human_address(&state.oracle)?, - staking: deps.api.human_address(&state.staking)?, - collector: deps.api.human_address(&state.collector)?, - collateral_oracle: deps.api.human_address(&state.collateral_oracle)?, - terraswap_factory: deps.api.human_address(&state.terraswap_factory)?, - lock: deps.api.human_address(&state.lock)?, - base_denom: state.base_denom, - token_code_id: state.token_code_id, - protocol_fee_rate: Decimal::percent(1), - }; - - Ok(resp) -} - -pub fn query_asset_config( - deps: &Extern, - asset_token: HumanAddr, -) -> StdResult { - let asset_config: AssetConfig = - read_asset_config(&deps.storage, &deps.api.canonical_address(&asset_token)?)?; - - let resp = AssetConfigResponse { - token: deps.api.human_address(&asset_config.token).unwrap(), - auction_discount: asset_config.auction_discount, - min_collateral_ratio: asset_config.min_collateral_ratio, - end_price: asset_config.end_price, - ipo_params: asset_config.ipo_params, - }; - - Ok(resp) -} - -pub fn migrate( - deps: &mut Extern, - _env: Env, - msg: MigrateMsg, -) -> MigrateResult { - // migrate config - migrate_config( - &mut deps.storage, - deps.api.canonical_address(&msg.staking)?, - deps.api.canonical_address(&msg.terraswap_factory)?, - deps.api.canonical_address(&msg.collateral_oracle)?, - deps.api.canonical_address(&msg.lock)?, - )?; - - // migrate all asset configurations to use new add mint_end parameter - migrate_asset_configs(&mut deps.storage)?; - - Ok(MigrateResponse::default()) -} diff --git a/contracts/mirror_mint/src/contract_test.rs b/contracts/mirror_mint/src/contract_test.rs deleted file mode 100644 index bb379599c..000000000 --- a/contracts/mirror_mint/src/contract_test.rs +++ /dev/null @@ -1,331 +0,0 @@ -#[cfg(test)] -mod tests { - use crate::contract::{handle, init, query}; - use crate::mock_querier::mock_dependencies; - use cosmwasm_std::testing::mock_env; - use cosmwasm_std::{ - from_binary, to_binary, CosmosMsg, Decimal, HumanAddr, StdError, WasmMsg, WasmQuery, - }; - use mirror_protocol::collateral_oracle::{HandleMsg::RegisterCollateralAsset, SourceType}; - use mirror_protocol::mint::{ - AssetConfigResponse, ConfigResponse, HandleMsg, IPOParams, InitMsg, QueryMsg, - }; - use mirror_protocol::oracle::QueryMsg::Price; - use terraswap::asset::AssetInfo; - - static TOKEN_CODE_ID: u64 = 10u64; - - #[test] - fn proper_initialization() { - let mut deps = mock_dependencies(20, &[]); - let msg = InitMsg { - owner: HumanAddr::from("owner0000"), - oracle: HumanAddr::from("oracle0000"), - collector: HumanAddr::from("collector0000"), - collateral_oracle: HumanAddr::from("collateraloracle0000"), - staking: HumanAddr::from("staking0000"), - terraswap_factory: HumanAddr::from("terraswap_factory"), - lock: HumanAddr::from("lock0000"), - base_denom: "uusd".to_string(), - token_code_id: TOKEN_CODE_ID, - protocol_fee_rate: Decimal::percent(1), - }; - let env = mock_env("addr0000", &[]); - // we can just call .unwrap() to assert this was a success - let _res = init(&mut deps, env.clone(), msg).unwrap(); - // it worked, let's query the state - let res = query(&deps, QueryMsg::Config {}).unwrap(); - let config: ConfigResponse = from_binary(&res).unwrap(); - assert_eq!("owner0000", config.owner.as_str()); - assert_eq!("uusd", config.base_denom.to_string()); - assert_eq!("oracle0000", config.oracle.as_str()); - assert_eq!("staking0000", config.staking.as_str()); - assert_eq!("collector0000", config.collector.as_str()); - assert_eq!("terraswap_factory", config.terraswap_factory.as_str()); - assert_eq!("lock0000", config.lock.as_str()); - assert_eq!(TOKEN_CODE_ID, config.token_code_id); - assert_eq!(Decimal::percent(1), config.protocol_fee_rate); - } - #[test] - fn update_config() { - let mut deps = mock_dependencies(20, &[]); - let msg = InitMsg { - owner: HumanAddr::from("owner0000"), - oracle: HumanAddr::from("oracle0000"), - collector: HumanAddr::from("collector0000"), - collateral_oracle: HumanAddr::from("collateraloracle0000"), - staking: HumanAddr::from("staking0000"), - terraswap_factory: HumanAddr::from("terraswap_factory"), - lock: HumanAddr::from("lock0000"), - base_denom: "uusd".to_string(), - token_code_id: TOKEN_CODE_ID, - protocol_fee_rate: Decimal::percent(1), - }; - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env.clone(), msg).unwrap(); - // update owner - let env = mock_env("owner0000", &[]); - let msg = HandleMsg::UpdateConfig { - owner: Some(HumanAddr("owner0001".to_string())), - oracle: None, - collector: None, - terraswap_factory: None, - lock: None, - token_code_id: Some(100u64), - protocol_fee_rate: None, - collateral_oracle: None, - }; - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!(0, res.messages.len()); - // it worked, let's query the state - let res = query(&deps, QueryMsg::Config {}).unwrap(); - let config: ConfigResponse = from_binary(&res).unwrap(); - assert_eq!("owner0001", config.owner.as_str()); - assert_eq!(100u64, config.token_code_id); - // Unauthorized err - let env = mock_env("owner0000", &[]); - let msg = HandleMsg::UpdateConfig { - owner: None, - oracle: None, - collector: None, - terraswap_factory: None, - lock: None, - token_code_id: None, - protocol_fee_rate: None, - collateral_oracle: None, - }; - let res = handle(&mut deps, env, msg); - match res { - Err(StdError::Unauthorized { .. }) => {} - _ => panic!("Must return unauthorized error"), - } - } - #[test] - fn register_asset() { - let mut deps = mock_dependencies(20, &[]); - let base_denom = "uusd".to_string(); - let msg = InitMsg { - owner: HumanAddr::from("owner0000"), - oracle: HumanAddr::from("oracle0000"), - collector: HumanAddr::from("collector0000"), - collateral_oracle: HumanAddr::from("collateraloracle0000"), - staking: HumanAddr::from("staking0000"), - terraswap_factory: HumanAddr::from("terraswap_factory"), - lock: HumanAddr::from("lock0000"), - base_denom: base_denom.clone(), - token_code_id: TOKEN_CODE_ID, - protocol_fee_rate: Decimal::percent(1), - }; - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env, msg).unwrap(); - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset0000"), - auction_discount: Decimal::percent(20), - min_collateral_ratio: Decimal::percent(150), - ipo_params: None, - }; - let env = mock_env("owner0000", &[]); - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!( - res.messages, - vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("collateraloracle0000"), - send: vec![], - msg: to_binary(&RegisterCollateralAsset { - asset: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - multiplier: Decimal::one(), - price_source: SourceType::TerraOracle { - terra_oracle_query: to_binary(&WasmQuery::Smart { - contract_addr: HumanAddr::from("oracle0000"), - msg: to_binary(&Price { - base_asset: "uusd".to_string(), - quote_asset: "asset0000".to_string(), - }) - .unwrap() - }) - .unwrap(), - }, - }) - .unwrap(), - })] - ); - - let res = query( - &deps, - QueryMsg::AssetConfig { - asset_token: HumanAddr::from("asset0000"), - }, - ) - .unwrap(); - let asset_config: AssetConfigResponse = from_binary(&res).unwrap(); - assert_eq!( - asset_config, - AssetConfigResponse { - token: HumanAddr::from("asset0000"), - auction_discount: Decimal::percent(20), - min_collateral_ratio: Decimal::percent(150), - end_price: None, - ipo_params: None, - } - ); - // must be failed with the already registered token error - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset0000"), - auction_discount: Decimal::percent(20), - min_collateral_ratio: Decimal::percent(150), - ipo_params: None, - }; - let env = mock_env("owner0000", &[]); - let res = handle(&mut deps, env, msg).unwrap_err(); - match res { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Asset was already registered"), - _ => panic!("DO NOT ENTER HERE"), - } - // must be failed with unauthorized error - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset0000"), - auction_discount: Decimal::percent(20), - min_collateral_ratio: Decimal::percent(150), - ipo_params: None, - }; - let env = mock_env("owner0001", &[]); - let res = handle(&mut deps, env, msg).unwrap_err(); - match res { - StdError::Unauthorized { .. } => {} - _ => panic!("DO NOT ENTER HERE"), - } - // must be failed with unauthorized error - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset0000"), - auction_discount: Decimal::percent(150), - min_collateral_ratio: Decimal::percent(150), - ipo_params: None, - }; - let env = mock_env("owner0000", &[]); - let res = handle(&mut deps, env, msg).unwrap_err(); - match res { - StdError::GenericErr { msg, .. } => { - assert_eq!(msg, "auction_discount must be smaller than 1") - } - _ => panic!("DO NOT ENTER HERE"), - } - // must be failed with unauthorized error - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset0000"), - auction_discount: Decimal::percent(20), - min_collateral_ratio: Decimal::percent(50), - ipo_params: None, - }; - let env = mock_env("owner0000", &[]); - let res = handle(&mut deps, env, msg).unwrap_err(); - match res { - StdError::GenericErr { msg, .. } => { - assert_eq!(msg, "min_collateral_ratio must be bigger than 1") - } - _ => panic!("DO NOT ENTER HERE"), - } - } - #[test] - fn update_asset() { - let mut deps = mock_dependencies(20, &[]); - let base_denom = "uusd".to_string(); - let msg = InitMsg { - owner: HumanAddr::from("owner0000"), - oracle: HumanAddr::from("oracle0000"), - collector: HumanAddr::from("collector0000"), - collateral_oracle: HumanAddr::from("collateraloracle0000"), - staking: HumanAddr::from("staking0000"), - terraswap_factory: HumanAddr::from("terraswap_factory"), - lock: HumanAddr::from("lock0000"), - base_denom: base_denom.clone(), - token_code_id: TOKEN_CODE_ID, - protocol_fee_rate: Decimal::percent(1), - }; - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env, msg).unwrap(); - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset0000"), - auction_discount: Decimal::percent(20), - min_collateral_ratio: Decimal::percent(150), - ipo_params: None, - }; - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - let msg = HandleMsg::UpdateAsset { - asset_token: HumanAddr::from("asset0000"), - auction_discount: Some(Decimal::percent(30)), - min_collateral_ratio: Some(Decimal::percent(200)), - ipo_params: Some(IPOParams { - min_collateral_ratio_after_ipo: Decimal::percent(150), - mint_end: 10000u64, - pre_ipo_price: Decimal::percent(1), - }), - }; - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - let res = query( - &deps, - QueryMsg::AssetConfig { - asset_token: HumanAddr::from("asset0000"), - }, - ) - .unwrap(); - let asset_config: AssetConfigResponse = from_binary(&res).unwrap(); - assert_eq!( - asset_config, - AssetConfigResponse { - token: HumanAddr::from("asset0000"), - auction_discount: Decimal::percent(30), - min_collateral_ratio: Decimal::percent(200), - end_price: None, - ipo_params: Some(IPOParams { - min_collateral_ratio_after_ipo: Decimal::percent(150), - mint_end: 10000u64, - pre_ipo_price: Decimal::percent(1) - }), - } - ); - let msg = HandleMsg::UpdateAsset { - asset_token: HumanAddr::from("asset0000"), - auction_discount: Some(Decimal::percent(130)), - min_collateral_ratio: Some(Decimal::percent(150)), - ipo_params: None, - }; - let env = mock_env("owner0000", &[]); - let res = handle(&mut deps, env, msg).unwrap_err(); - match res { - StdError::GenericErr { msg, .. } => { - assert_eq!(msg, "auction_discount must be smaller than 1") - } - _ => panic!("Must return unauthorized error"), - } - let msg = HandleMsg::UpdateAsset { - asset_token: HumanAddr::from("asset0000"), - auction_discount: Some(Decimal::percent(30)), - min_collateral_ratio: Some(Decimal::percent(50)), - ipo_params: None, - }; - let env = mock_env("owner0000", &[]); - let res = handle(&mut deps, env, msg).unwrap_err(); - match res { - StdError::GenericErr { msg, .. } => { - assert_eq!(msg, "min_collateral_ratio must be bigger than 1") - } - _ => panic!("Must return unauthorized error"), - } - let msg = HandleMsg::UpdateAsset { - asset_token: HumanAddr::from("asset0000"), - auction_discount: Some(Decimal::percent(30)), - min_collateral_ratio: Some(Decimal::percent(200)), - ipo_params: None, - }; - let env = mock_env("owner0001", &[]); - let res = handle(&mut deps, env, msg).unwrap_err(); - match res { - StdError::Unauthorized { .. } => {} - _ => panic!("Must return unauthorized error"), - } - } -} diff --git a/contracts/mirror_mint/src/lib.rs b/contracts/mirror_mint/src/lib.rs deleted file mode 100644 index 75e4a50a9..000000000 --- a/contracts/mirror_mint/src/lib.rs +++ /dev/null @@ -1,21 +0,0 @@ -pub mod contract; - -mod asserts; -mod math; -mod migration; -mod positions; -mod querier; -mod state; - -// Testing files -mod contract_test; -mod migrated_asset_test; -mod positions_test; -mod pre_ipo_test; -mod short_test; - -#[cfg(test)] -mod mock_querier; - -#[cfg(target_arch = "wasm32")] -cosmwasm_std::create_entry_points_with_migration!(contract); diff --git a/contracts/mirror_mint/src/math.rs b/contracts/mirror_mint/src/math.rs deleted file mode 100644 index ad226558f..000000000 --- a/contracts/mirror_mint/src/math.rs +++ /dev/null @@ -1,23 +0,0 @@ -use cosmwasm_std::{Decimal, Uint128}; - -const DECIMAL_FRACTIONAL: Uint128 = Uint128(1_000_000_000u128); - -pub fn reverse_decimal(decimal: Decimal) -> Decimal { - Decimal::from_ratio(DECIMAL_FRACTIONAL, decimal * DECIMAL_FRACTIONAL) -} - -pub fn decimal_subtraction(a: Decimal, b: Decimal) -> Decimal { - Decimal::from_ratio( - (DECIMAL_FRACTIONAL * a - DECIMAL_FRACTIONAL * b).unwrap(), - DECIMAL_FRACTIONAL, - ) -} - -/// return a / b -pub fn decimal_division(a: Decimal, b: Decimal) -> Decimal { - Decimal::from_ratio(DECIMAL_FRACTIONAL * a, b * DECIMAL_FRACTIONAL) -} - -pub fn decimal_multiplication(a: Decimal, b: Decimal) -> Decimal { - Decimal::from_ratio(a * DECIMAL_FRACTIONAL * b, DECIMAL_FRACTIONAL) -} diff --git a/contracts/mirror_mint/src/migrated_asset_test.rs b/contracts/mirror_mint/src/migrated_asset_test.rs deleted file mode 100644 index f487594c2..000000000 --- a/contracts/mirror_mint/src/migrated_asset_test.rs +++ /dev/null @@ -1,919 +0,0 @@ -#[cfg(test)] -mod tests { - use crate::contract::{handle, init, query}; - use crate::mock_querier::mock_dependencies; - use cosmwasm_std::testing::{mock_env, MOCK_CONTRACT_ADDR}; - use cosmwasm_std::{ - from_binary, log, to_binary, BankMsg, BlockInfo, Coin, CosmosMsg, Decimal, Env, HumanAddr, - StdError, Uint128, WasmMsg, - }; - use cw20::{Cw20HandleMsg, Cw20ReceiveMsg}; - use mirror_protocol::collateral_oracle::HandleMsg::RevokeCollateralAsset; - use mirror_protocol::mint::{ - AssetConfigResponse, Cw20HookMsg, HandleMsg, InitMsg, PositionResponse, PositionsResponse, - QueryMsg, - }; - use terraswap::asset::{Asset, AssetInfo}; - - static TOKEN_CODE_ID: u64 = 10u64; - fn mock_env_with_block_time>(sender: U, sent: &[Coin], time: u64) -> Env { - let env = mock_env(sender, sent); - // register time - return Env { - block: BlockInfo { - height: 1, - time, - chain_id: "columbus".to_string(), - }, - ..env - }; - } - - #[test] - fn register_migration() { - let mut deps = mock_dependencies(20, &[]); - deps.querier.with_token_balances(&[( - &HumanAddr::from("asset0000"), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128::from(1000000u128), - )], - )]); - - let base_denom = "uusd".to_string(); - - let msg = InitMsg { - owner: HumanAddr::from("owner0000"), - oracle: HumanAddr::from("oracle0000"), - collector: HumanAddr::from("collector0000"), - collateral_oracle: HumanAddr::from("collateraloracle0000"), - staking: HumanAddr::from("staking0000"), - terraswap_factory: HumanAddr::from("terraswap_factory"), - lock: HumanAddr::from("lock0000"), - base_denom: base_denom.clone(), - token_code_id: TOKEN_CODE_ID, - protocol_fee_rate: Decimal::percent(1), - }; - - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset0000"), - auction_discount: Decimal::percent(20), - min_collateral_ratio: Decimal::percent(150), - ipo_params: None, - }; - - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterMigration { - asset_token: HumanAddr::from("asset0000"), - end_price: Decimal::from_ratio(2u128, 1u128), - }; - let env = mock_env("addr0000", &[]); - let res = handle(&mut deps, env, msg); - match res { - Err(StdError::Unauthorized { .. }) => {} - _ => panic!("DO NOT ENTER HERE"), - } - - let msg = HandleMsg::RegisterMigration { - asset_token: HumanAddr::from("asset0001"), - end_price: Decimal::from_ratio(2u128, 1u128), - }; - let env = mock_env("owner0000", &[]); - let res = handle(&mut deps, env, msg); - match res { - Err(StdError::GenericErr { msg, .. }) => assert_eq!(msg, "no asset data stored"), - _ => panic!("DO NOT ENTER HERE"), - } - - let msg = HandleMsg::RegisterMigration { - asset_token: HumanAddr::from("asset0000"), - end_price: Decimal::percent(50), - }; - let env = mock_env("owner0000", &[]); - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!( - res.log, - vec![ - log("action", "migrate_asset"), - log("asset_token", "asset0000"), - log("end_price", "0.5"), - ] - ); - assert_eq!( - res.messages, - vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("collateraloracle0000"), - send: vec![], - msg: to_binary(&RevokeCollateralAsset { - asset: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - }) - .unwrap(), - })] - ); - - let res = query( - &deps, - QueryMsg::AssetConfig { - asset_token: HumanAddr::from("asset0000"), - }, - ) - .unwrap(); - let asset_config_res: AssetConfigResponse = from_binary(&res).unwrap(); - assert_eq!( - asset_config_res, - AssetConfigResponse { - token: HumanAddr::from("asset0000"), - auction_discount: Decimal::percent(20), - min_collateral_ratio: Decimal::percent(100), - end_price: Some(Decimal::percent(50)), - ipo_params: None, - } - ); - } - #[test] - fn migrated_asset() { - let mut deps = mock_dependencies(20, &[]); - deps.querier.with_token_balances(&[( - &HumanAddr::from("asset0000"), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128::from(1000000u128), - )], - )]); - deps.querier.with_oracle_price(&[ - (&"uusd".to_string(), &Decimal::one()), - (&"asset0000".to_string(), &Decimal::percent(100)), - (&"asset0001".to_string(), &Decimal::percent(50)), - ]); - deps.querier.with_collateral_infos(&[( - &"asset0000".to_string(), - &Decimal::percent(100), - &Decimal::one(), - &false, - )]); - - let base_denom = "uusd".to_string(); - - let msg = InitMsg { - owner: HumanAddr::from("owner0000"), - oracle: HumanAddr::from("oracle0000"), - collector: HumanAddr::from("collector0000"), - collateral_oracle: HumanAddr::from("collateraloracle0000"), - staking: HumanAddr::from("staking0000"), - terraswap_factory: HumanAddr::from("terraswap_factory"), - lock: HumanAddr::from("lock0000"), - base_denom: base_denom.clone(), - token_code_id: TOKEN_CODE_ID, - protocol_fee_rate: Decimal::percent(1), - }; - - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset0000"), - auction_discount: Decimal::percent(20), - min_collateral_ratio: Decimal::percent(150), - ipo_params: None, - }; - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset0001"), - auction_discount: Decimal::percent(20), - min_collateral_ratio: Decimal::percent(150), - ipo_params: None, - }; - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // Open uusd:asset0000 position - let msg = HandleMsg::OpenPosition { - collateral: Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: Uint128(1000000u128), - }, - asset_info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - collateral_ratio: Decimal::percent(150), - short_params: None, - }; - let env = mock_env_with_block_time( - "addr0000", - &[Coin { - amount: Uint128(1000000u128), - denom: "uusd".to_string(), - }], - 1000, - ); - let _res = handle(&mut deps, env, msg).unwrap(); - - // Open asset0000:asset0001 position - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - msg: Some( - to_binary(&Cw20HookMsg::OpenPosition { - asset_info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0001"), - }, - collateral_ratio: Decimal::percent(150), - short_params: None, - }) - .unwrap(), - ), - sender: HumanAddr::from("addr0000"), - amount: Uint128(1000000u128), - }); - let env = mock_env_with_block_time("asset0000", &[], 1000); - let _res = handle(&mut deps, env, msg).unwrap(); - - // register migration - let msg = HandleMsg::RegisterMigration { - asset_token: HumanAddr::from("asset0000"), - end_price: Decimal::percent(100), - }; - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // cannot open a position with deprecated collateral - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - msg: Some( - to_binary(&Cw20HookMsg::OpenPosition { - asset_info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0001"), - }, - collateral_ratio: Decimal::percent(150), - short_params: None, - }) - .unwrap(), - ), - sender: HumanAddr::from("addr0000"), - amount: Uint128(1000000u128), - }); - let env = mock_env_with_block_time("asset0000", &[], 1000); - let res = handle(&mut deps, env, msg).unwrap_err(); - match res { - StdError::GenericErr { msg, .. } => { - assert_eq!(msg, "The collateral asset provided is no longer valid") - } - _ => panic!("DO NOT ENTER HERE"), - } - - // cannot open a deprecated asset position - let msg = HandleMsg::OpenPosition { - collateral: Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: Uint128(100u128), - }, - asset_info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - collateral_ratio: Decimal::percent(150), - short_params: None, - }; - let env = mock_env_with_block_time( - "addr0000", - &[Coin { - amount: Uint128(100u128), - denom: "uusd".to_string(), - }], - 1000, - ); - let res = handle(&mut deps, env, msg).unwrap_err(); - match res { - StdError::GenericErr { msg, .. } => { - assert_eq!(msg, "Operation is not allowed for the deprecated asset") - } - _ => panic!("DO NOT ENTER HERE"), - } - - // cannot do deposit more collateral to the deprecated asset position - let msg = HandleMsg::Deposit { - position_idx: Uint128(1u128), - collateral: Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: Uint128(1u128), - }, - }; - let env = mock_env( - "addr0000", - &[Coin { - denom: "uusd".to_string(), - amount: Uint128(1u128), - }], - ); - let res = handle(&mut deps, env, msg).unwrap_err(); - match res { - StdError::GenericErr { msg, .. } => { - assert_eq!(msg, "Operation is not allowed for the deprecated asset") - } - _ => panic!("DO NOT ENTER HERE"), - } - - // cannot deposit more collateral to the deprecated collateral position - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - msg: Some( - to_binary(&Cw20HookMsg::Deposit { - position_idx: Uint128(2u128), - }) - .unwrap(), - ), - sender: HumanAddr::from("addr0000"), - amount: Uint128(1000000u128), - }); - let env = mock_env("asset0000", &[]); - let res = handle(&mut deps, env, msg).unwrap_err(); - match res { - StdError::GenericErr { msg, .. } => { - assert_eq!(msg, "The collateral asset provided is no longer valid") - } - _ => panic!("DO NOT ENTER HERE"), - } - - // cannot mint more the deprecated asset - let msg = HandleMsg::Mint { - position_idx: Uint128(1u128), - asset: Asset { - info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - amount: Uint128(1u128), - }, - short_params: None, - }; - let env = mock_env("addr0000", &[]); - let res = handle(&mut deps, env, msg).unwrap_err(); - match res { - StdError::GenericErr { msg, .. } => { - assert_eq!(msg, "Operation is not allowed for the deprecated asset") - } - _ => panic!("DO NOT ENTER HERE"), - } - - // cannot mint more asset with deprecated collateral - let msg = HandleMsg::Mint { - position_idx: Uint128(2u128), - asset: Asset { - info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0001"), - }, - amount: Uint128(1u128), - }, - short_params: None, - }; - let env = mock_env_with_block_time("addr0000", &[], 1000); - let res = handle(&mut deps, env, msg).unwrap_err(); - match res { - StdError::GenericErr { msg, .. } => { - assert_eq!(msg, "The collateral asset provided is no longer valid") - } - _ => panic!("DO NOT ENTER HERE"), - } - - // withdraw 333334 uusd - let msg = HandleMsg::Withdraw { - position_idx: Uint128(1u128), - collateral: Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: Uint128(333334u128), - }, - }; - - // only owner can withdraw - let env = mock_env("addr0001", &[]); - let res = handle(&mut deps, env, msg.clone()).unwrap_err(); - match res { - StdError::Unauthorized { .. } => {} - _ => panic!("DO NOT ENTER HERE"), - } - - let env = mock_env("addr0000", &[]); - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!( - res.messages, - vec![ - CosmosMsg::Bank(BankMsg::Send { - from_address: HumanAddr::from(MOCK_CONTRACT_ADDR), - to_address: HumanAddr::from("addr0000"), - amount: vec![Coin { - denom: "uusd".to_string(), - amount: Uint128(330001u128), - }] - }), - CosmosMsg::Bank(BankMsg::Send { - from_address: HumanAddr::from(MOCK_CONTRACT_ADDR), - to_address: HumanAddr::from("collector0000"), - amount: vec![Coin { - denom: "uusd".to_string(), - amount: Uint128(3333u128), - }] - }) - ] - ); - - let res = query( - &deps, - QueryMsg::Position { - position_idx: Uint128(1u128), - }, - ) - .unwrap(); - - let position: PositionResponse = from_binary(&res).unwrap(); - assert_eq!( - position, - PositionResponse { - idx: Uint128(1u128), - owner: HumanAddr::from("addr0000"), - collateral: Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: Uint128::from(666666u128), - }, - asset: Asset { - info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - amount: Uint128::from(666666u128), - }, - is_short: false, - } - ); - - // anyone can burn deprecated asset to any position - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from("addr0001"), - amount: Uint128::from(666666u128), - msg: Some( - to_binary(&Cw20HookMsg::Burn { - position_idx: Uint128(1u128), - }) - .unwrap(), - ), - }); - let env = mock_env("asset0000", &[]); - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!( - res.messages, - vec![ - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("asset0000"), - send: vec![], - msg: to_binary(&Cw20HandleMsg::Burn { - amount: Uint128::from(666666u128), - }) - .unwrap(), - }), - CosmosMsg::Bank(BankMsg::Send { - from_address: HumanAddr::from(MOCK_CONTRACT_ADDR), - to_address: HumanAddr::from("addr0001"), - amount: vec![Coin { - denom: "uusd".to_string(), - amount: Uint128::from(666666u128), - }], - }), - ] - ); - - assert_eq!( - res.log, - vec![ - log("action", "burn"), - log("position_idx", "1"), - log("burn_amount", "666666asset0000"), - log("refund_collateral_amount", "666666uusd") - ] - ); - - let res = query( - &deps, - QueryMsg::Positions { - owner_addr: None, - asset_token: Some(HumanAddr::from("asset0000")), - start_after: None, - limit: None, - order_by: None, - }, - ) - .unwrap(); - let positions: PositionsResponse = from_binary(&res).unwrap(); - assert_eq!(positions, PositionsResponse { positions: vec![] }); - } - - #[test] - fn burn_migrated_asset_position() { - let mut deps = mock_dependencies(20, &[]); - deps.querier.with_token_balances(&[( - &HumanAddr::from("asset0000"), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128::from(1000000u128), - )], - )]); - deps.querier.with_oracle_price(&[ - (&"uusd".to_string(), &Decimal::one()), - (&"asset0000".to_string(), &Decimal::percent(100)), - (&"asset0001".to_string(), &Decimal::percent(50)), - ]); - deps.querier.with_collateral_infos(&[( - &"asset0000".to_string(), - &Decimal::percent(100), - &Decimal::one(), - &false, - )]); - - let base_denom = "uusd".to_string(); - - let msg = InitMsg { - owner: HumanAddr::from("owner0000"), - oracle: HumanAddr::from("oracle0000"), - collector: HumanAddr::from("collector0000"), - collateral_oracle: HumanAddr::from("collateraloracle0000"), - staking: HumanAddr::from("staking0000"), - terraswap_factory: HumanAddr::from("terraswap_factory"), - lock: HumanAddr::from("lock0000"), - base_denom: base_denom.clone(), - token_code_id: TOKEN_CODE_ID, - protocol_fee_rate: Decimal::percent(1), - }; - - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset0000"), - auction_discount: Decimal::percent(20), - min_collateral_ratio: Decimal::percent(150), - ipo_params: None, - }; - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset0001"), - auction_discount: Decimal::percent(20), - min_collateral_ratio: Decimal::percent(150), - ipo_params: None, - }; - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // Open asset0000:asset0001 position - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - msg: Some( - to_binary(&Cw20HookMsg::OpenPosition { - asset_info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0001"), - }, - collateral_ratio: Decimal::percent(150), - short_params: None, - }) - .unwrap(), - ), - sender: HumanAddr::from("addr0000"), - amount: Uint128(1000000u128), - }); - let env = mock_env_with_block_time("asset0000", &[], 1000); - let _res = handle(&mut deps, env, msg).unwrap(); - - // register migration - let msg = HandleMsg::RegisterMigration { - asset_token: HumanAddr::from("asset0001"), - end_price: Decimal::percent(50), - }; - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // withdraw 333334 uusd - let msg = HandleMsg::Withdraw { - position_idx: Uint128(1u128), - collateral: Asset { - info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - amount: Uint128(333334u128), - }, - }; - - // only owner can withdraw - let env = mock_env("addr0001", &[]); - let res = handle(&mut deps, env, msg.clone()).unwrap_err(); - match res { - StdError::Unauthorized { .. } => {} - _ => panic!("DO NOT ENTER HERE"), - } - - let env = mock_env_with_block_time("addr0000", &[], 1000); - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!( - res.messages, - vec![ - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("asset0000"), - send: vec![], - msg: to_binary(&Cw20HandleMsg::Transfer { - recipient: HumanAddr::from("addr0000"), - amount: Uint128::from(330001u128), - }) - .unwrap(), - }), - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("asset0000"), - send: vec![], - msg: to_binary(&Cw20HandleMsg::Transfer { - recipient: HumanAddr::from("collector0000"), - amount: Uint128::from(3333u128), - }) - .unwrap(), - }), - ] - ); - - let res = query( - &deps, - QueryMsg::Position { - position_idx: Uint128(1u128), - }, - ) - .unwrap(); - - let position: PositionResponse = from_binary(&res).unwrap(); - assert_eq!( - position, - PositionResponse { - idx: Uint128(1u128), - owner: HumanAddr::from("addr0000"), - collateral: Asset { - info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - amount: Uint128::from(666666u128), - }, - asset: Asset { - info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0001"), - }, - amount: Uint128::from(1333333u128), - }, - is_short: false, - } - ); - - // anyone can burn deprecated asset to any position - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from("addr0001"), - amount: Uint128::from(1333333u128), - msg: Some( - to_binary(&Cw20HookMsg::Burn { - position_idx: Uint128(1u128), - }) - .unwrap(), - ), - }); - let env = mock_env_with_block_time("asset0001", &[], 1000); - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!( - res.messages, - vec![ - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("asset0001"), - send: vec![], - msg: to_binary(&Cw20HandleMsg::Burn { - amount: Uint128::from(1333333u128), - }) - .unwrap(), - }), - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("asset0000"), - send: vec![], - msg: to_binary(&Cw20HandleMsg::Transfer { - recipient: HumanAddr::from("addr0001"), - amount: Uint128::from(666665u128), // rounding - }) - .unwrap(), - }), - ] - ); - - assert_eq!( - res.log, - vec![ - log("action", "burn"), - log("position_idx", "1"), - log("burn_amount", "1333333asset0001"), - log("refund_collateral_amount", "666665asset0000") // rounding - ] - ); - - let res = query( - &deps, - QueryMsg::Positions { - owner_addr: None, - asset_token: Some(HumanAddr::from("asset0000")), - start_after: None, - limit: None, - order_by: None, - }, - ) - .unwrap(); - let positions: PositionsResponse = from_binary(&res).unwrap(); - assert_eq!(positions, PositionsResponse { positions: vec![] }); - } - - #[test] - fn revoked_collateral() { - let mut deps = mock_dependencies(20, &[]); - deps.querier.with_token_balances(&[( - &HumanAddr::from("asset000"), - &[( - &HumanAddr::from(MOCK_CONTRACT_ADDR), - &Uint128::from(1000000u128), - )], - )]); - deps.querier.with_oracle_price(&[ - (&"uusd".to_string(), &Decimal::one()), - (&"asset0000".to_string(), &Decimal::percent(100)), - ]); - deps.querier.with_collateral_infos(&[( - &"uluna".to_string(), - &Decimal::percent(10), - &Decimal::percent(50), - &false, - )]); - - let base_denom = "uusd".to_string(); - - let msg = InitMsg { - owner: HumanAddr::from("owner0000"), - oracle: HumanAddr::from("oracle0000"), - collector: HumanAddr::from("collector0000"), - collateral_oracle: HumanAddr::from("collateraloracle0000"), - staking: HumanAddr::from("staking0000"), - terraswap_factory: HumanAddr::from("terraswap_factory"), - lock: HumanAddr::from("lock0000"), - base_denom: base_denom.clone(), - token_code_id: TOKEN_CODE_ID, - protocol_fee_rate: Decimal::percent(1), - }; - - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset0000"), - auction_discount: Decimal::percent(20), - min_collateral_ratio: Decimal::percent(150), - ipo_params: None, - }; - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // Open uluna:asset0000 position - let msg = HandleMsg::OpenPosition { - collateral: Asset { - info: AssetInfo::NativeToken { - denom: "uluna".to_string(), - }, - amount: Uint128(1000u128), - }, - asset_info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - collateral_ratio: Decimal::percent(200), - short_params: None, - }; - let env = mock_env_with_block_time( - "addr0000", - &[Coin { - amount: Uint128(1000u128), - denom: "uluna".to_string(), - }], - 1000, - ); - let _res = handle(&mut deps, env, msg.clone()).unwrap(); - - // collateral is revoked - deps.querier.with_collateral_infos(&[( - &"uluna".to_string(), - &Decimal::percent(100), - &Decimal::percent(50), - &true, - )]); - - // open position fails - let env = mock_env_with_block_time( - "addr0000", - &[Coin { - amount: Uint128(1000u128), - denom: "uluna".to_string(), - }], - 1000, - ); - let res = handle(&mut deps, env, msg.clone()).unwrap_err(); - assert_eq!( - res, - StdError::generic_err("The collateral asset provided is no longer valid") - ); - - // minting to previously open position fails - let msg = HandleMsg::Mint { - position_idx: Uint128(1u128), - asset: Asset { - info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - amount: Uint128(1u128), - }, - short_params: None, - }; - let env = mock_env_with_block_time("addr0000", &[], 1000); - let res = handle(&mut deps, env, msg.clone()).unwrap_err(); - assert_eq!( - res, - StdError::generic_err("The collateral asset provided is no longer valid") - ); - - // deposit fails - let msg = HandleMsg::Deposit { - position_idx: Uint128(1u128), - collateral: Asset { - info: AssetInfo::NativeToken { - denom: "uluna".to_string(), - }, - amount: Uint128(2u128), - }, - }; - let env = mock_env_with_block_time( - "addr0000", - &[Coin { - amount: Uint128(2u128), - denom: "uluna".to_string(), - }], - 1000, - ); - let res = handle(&mut deps, env, msg.clone()).unwrap_err(); - assert_eq!( - res, - StdError::generic_err("The collateral asset provided is no longer valid") - ); - - // burn against revoked collateral is enabled - // fail attempt, only owner can burn - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from("addr0001"), - amount: Uint128::from(10u128), - msg: Some( - to_binary(&Cw20HookMsg::Burn { - position_idx: Uint128(1u128), - }) - .unwrap(), - ), - }); - let env = mock_env("asset0000", &[]); - let res = handle(&mut deps, env, msg).unwrap_err(); - assert_eq!(res, StdError::unauthorized()); - // sucessful attempt - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from("addr0000"), - amount: Uint128::from(10u128), - msg: Some( - to_binary(&Cw20HookMsg::Burn { - position_idx: Uint128(1u128), - }) - .unwrap(), - ), - }); - let env = mock_env("asset0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // owner can withdraw revoked collateral - let msg = HandleMsg::Withdraw { - position_idx: Uint128(1u128), - collateral: Asset { - info: AssetInfo::NativeToken { - denom: "uluna".to_string(), - }, - amount: Uint128(2u128), - }, - }; - let env = mock_env_with_block_time("addr0000", &[], 1000); - let _res = handle(&mut deps, env, msg.clone()).unwrap(); - } -} diff --git a/contracts/mirror_mint/src/migration.rs b/contracts/mirror_mint/src/migration.rs deleted file mode 100644 index afe421404..000000000 --- a/contracts/mirror_mint/src/migration.rs +++ /dev/null @@ -1,238 +0,0 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use cosmwasm_std::{CanonicalAddr, Decimal, Order, StdResult, Storage}; -use cosmwasm_storage::{singleton_read, ReadonlyBucket}; - -use crate::state::{store_asset_config, store_config, AssetConfig, Config}; - -static PREFIX_ASSET_CONFIG: &[u8] = b"asset_config"; -static KEY_CONFIG: &[u8] = b"config"; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct LegacyConfig { - pub owner: CanonicalAddr, - pub oracle: CanonicalAddr, - pub collector: CanonicalAddr, - pub base_denom: String, - pub token_code_id: u64, - pub protocol_fee_rate: Decimal, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct LegacyAssetConfig { - pub token: CanonicalAddr, - pub auction_discount: Decimal, - pub min_collateral_ratio: Decimal, - pub end_price: Option, -} - -fn read_legacy_config(storage: &S) -> StdResult { - singleton_read(storage, KEY_CONFIG).load() -} - -pub fn migrate_config( - storage: &mut S, - staking: CanonicalAddr, - terraswap_factory: CanonicalAddr, - collateral_oracle: CanonicalAddr, - lock: CanonicalAddr, -) -> StdResult<()> { - let legacy_config: LegacyConfig = read_legacy_config(storage)?; - store_config( - storage, - &Config { - staking, - terraswap_factory, - collateral_oracle, - lock, - owner: legacy_config.owner, - oracle: legacy_config.oracle, - collector: legacy_config.collector, - base_denom: legacy_config.base_denom, - token_code_id: legacy_config.token_code_id, - protocol_fee_rate: legacy_config.protocol_fee_rate, - }, - )?; - - Ok(()) -} - -fn read_legacy_asset_configs(storage: &S) -> StdResult> { - let asset_config_bucket: ReadonlyBucket = - ReadonlyBucket::new(PREFIX_ASSET_CONFIG, storage); - asset_config_bucket - .range(None, None, Order::Ascending) - .map(|item| { - let (_, v) = item?; - Ok(v) - }) - .collect() -} - -pub fn migrate_asset_configs(storage: &mut S) -> StdResult<()> { - let legacy_asset_configs: Vec = read_legacy_asset_configs(storage)?; - - for legacy_config in legacy_asset_configs { - store_asset_config( - storage, - &legacy_config.token, - &AssetConfig { - token: legacy_config.token.clone(), - auction_discount: legacy_config.auction_discount, - min_collateral_ratio: legacy_config.min_collateral_ratio, - end_price: legacy_config.end_price, - ipo_params: None, - }, - )? - } - Ok(()) -} - -#[cfg(test)] -mod migrate_tests { - use super::*; - use crate::state::{read_asset_config, read_config}; - use cosmwasm_std::testing::mock_dependencies; - use cosmwasm_std::{Api, HumanAddr}; - use cosmwasm_storage::{singleton, Bucket}; - - pub fn asset_config_old_store<'a, S: Storage>( - storage: &'a mut S, - ) -> Bucket<'a, S, LegacyAssetConfig> { - Bucket::new(PREFIX_ASSET_CONFIG, storage) - } - - pub fn store_legacy_config( - storage: &mut S, - config: &LegacyConfig, - ) -> StdResult<()> { - singleton(storage, KEY_CONFIG).save(config) - } - - #[test] - fn test_config_migration() { - let mut deps = mock_dependencies(20, &[]); - - let owner = deps - .api - .canonical_address(&HumanAddr::from("owner0000")) - .unwrap(); - let oracle = deps - .api - .canonical_address(&HumanAddr::from("oracle0000")) - .unwrap(); - let collector = deps - .api - .canonical_address(&HumanAddr::from("collector0000")) - .unwrap(); - let staking = deps - .api - .canonical_address(&HumanAddr::from("staking0000")) - .unwrap(); - let terraswap_factory = deps - .api - .canonical_address(&HumanAddr::from("terraswap_factory")) - .unwrap(); - let collateral_oracle = deps - .api - .canonical_address(&HumanAddr::from("collateral_oracle")) - .unwrap(); - let lock = deps - .api - .canonical_address(&HumanAddr::from("lock0000")) - .unwrap(); - store_legacy_config( - &mut deps.storage, - &LegacyConfig { - owner: owner.clone(), - oracle: oracle.clone(), - collector: collector.clone(), - base_denom: "uusd".to_string(), - token_code_id: 10, - protocol_fee_rate: Decimal::percent(1), - }, - ) - .unwrap(); - - migrate_config( - &mut deps.storage, - staking.clone(), - terraswap_factory.clone(), - collateral_oracle.clone(), - lock.clone(), - ) - .unwrap(); - assert_eq!( - read_config(&deps.storage).unwrap(), - Config { - owner, - oracle, - staking, - collector, - terraswap_factory, - lock, - collateral_oracle, - base_denom: "uusd".to_string(), - token_code_id: 10, - protocol_fee_rate: Decimal::percent(1), - } - ); - } - - #[test] - fn test_asset_config_migration() { - let mut deps = mock_dependencies(20, &[]); - - let asset_token = deps - .api - .canonical_address(&HumanAddr::from("token0001")) - .unwrap(); - let legacy_asset_config = LegacyAssetConfig { - token: asset_token.clone(), - auction_discount: Decimal::percent(10), - min_collateral_ratio: Decimal::percent(150), - end_price: None, - }; - let asset_token_2 = deps - .api - .canonical_address(&HumanAddr::from("token0002")) - .unwrap(); - let legacy_asset_config_2 = LegacyAssetConfig { - token: asset_token_2.clone(), - auction_discount: Decimal::percent(20), - min_collateral_ratio: Decimal::percent(200), - end_price: Some(Decimal::percent(1)), - }; - - asset_config_old_store(&mut deps.storage) - .save(asset_token.as_slice(), &legacy_asset_config) - .unwrap(); - asset_config_old_store(&mut deps.storage) - .save(asset_token_2.as_slice(), &legacy_asset_config_2) - .unwrap(); - - migrate_asset_configs(&mut deps.storage).unwrap(); - - assert_eq!( - read_asset_config(&mut deps.storage, &asset_token).unwrap(), - AssetConfig { - token: legacy_asset_config.token.clone(), - auction_discount: legacy_asset_config.auction_discount, - min_collateral_ratio: legacy_asset_config.min_collateral_ratio, - end_price: legacy_asset_config.end_price, - ipo_params: None, - } - ); - assert_eq!( - read_asset_config(&mut deps.storage, &asset_token_2).unwrap(), - AssetConfig { - token: legacy_asset_config_2.token.clone(), - auction_discount: legacy_asset_config_2.auction_discount, - min_collateral_ratio: legacy_asset_config_2.min_collateral_ratio, - end_price: legacy_asset_config_2.end_price, - ipo_params: None, - } - ); - } -} diff --git a/contracts/mirror_mint/src/mock_querier.rs b/contracts/mirror_mint/src/mock_querier.rs deleted file mode 100644 index b66e5ec1c..000000000 --- a/contracts/mirror_mint/src/mock_querier.rs +++ /dev/null @@ -1,457 +0,0 @@ -use cosmwasm_std::testing::{MockApi, MockQuerier, MockStorage, MOCK_CONTRACT_ADDR}; -use cosmwasm_std::{ - from_binary, from_slice, to_binary, Api, CanonicalAddr, Coin, Decimal, Extern, HumanAddr, - Querier, QuerierResult, QueryRequest, SystemError, Uint128, WasmQuery, -}; -use cosmwasm_storage::to_length_prefixed; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; - -use crate::math::decimal_division; -use mirror_protocol::collateral_oracle::CollateralPriceResponse; -use mirror_protocol::oracle::PriceResponse; -use terra_cosmwasm::{TaxCapResponse, TaxRateResponse, TerraQuery, TerraQueryWrapper, TerraRoute}; -use terraswap::{asset::AssetInfo, asset::PairInfo}; - -/// mock_dependencies is a drop-in replacement for cosmwasm_std::testing::mock_dependencies -/// this uses our CustomQuerier. -pub fn mock_dependencies( - canonical_length: usize, - contract_balance: &[Coin], -) -> Extern { - let contract_addr = HumanAddr::from(MOCK_CONTRACT_ADDR); - let custom_querier: WasmMockQuerier = WasmMockQuerier::new( - MockQuerier::new(&[(&contract_addr, contract_balance)]), - MockApi::new(canonical_length), - canonical_length, - ); - - Extern { - storage: MockStorage::default(), - api: MockApi::new(canonical_length), - querier: custom_querier, - } -} - -pub struct WasmMockQuerier { - base: MockQuerier, - token_querier: TokenQuerier, - tax_querier: TaxQuerier, - oracle_price_querier: OraclePriceQuerier, - collateral_oracle_querier: CollateralOracleQuerier, - terraswap_pair_querier: TerraswapPairQuerier, - oracle_querier: OracleQuerier, - canonical_length: usize, -} - -#[derive(Clone, Default)] -pub struct TokenQuerier { - // this lets us iterate over all pairs that match the first string - balances: HashMap>, -} - -impl TokenQuerier { - pub fn new(balances: &[(&HumanAddr, &[(&HumanAddr, &Uint128)])]) -> Self { - TokenQuerier { - balances: balances_to_map(balances), - } - } -} - -pub(crate) fn balances_to_map( - balances: &[(&HumanAddr, &[(&HumanAddr, &Uint128)])], -) -> HashMap> { - let mut balances_map: HashMap> = HashMap::new(); - for (contract_addr, balances) in balances.iter() { - let mut contract_balances_map: HashMap = HashMap::new(); - for (addr, balance) in balances.iter() { - contract_balances_map.insert(HumanAddr::from(addr), **balance); - } - - balances_map.insert(HumanAddr::from(contract_addr), contract_balances_map); - } - balances_map -} - -#[derive(Clone, Default)] -pub struct TaxQuerier { - rate: Decimal, - // this lets us iterate over all pairs that match the first string - caps: HashMap, -} - -impl TaxQuerier { - pub fn new(rate: Decimal, caps: &[(&String, &Uint128)]) -> Self { - TaxQuerier { - rate, - caps: caps_to_map(caps), - } - } -} - -pub(crate) fn caps_to_map(caps: &[(&String, &Uint128)]) -> HashMap { - let mut owner_map: HashMap = HashMap::new(); - for (denom, cap) in caps.iter() { - owner_map.insert(denom.to_string(), **cap); - } - owner_map -} - -#[derive(Clone, Default)] -pub struct OraclePriceQuerier { - // this lets us iterate over all pairs that match the first string - oracle_price: HashMap, -} - -impl OraclePriceQuerier { - pub fn new(oracle_price: &[(&String, &Decimal)]) -> Self { - OraclePriceQuerier { - oracle_price: oracle_price_to_map(oracle_price), - } - } -} - -pub(crate) fn oracle_price_to_map( - oracle_price: &[(&String, &Decimal)], -) -> HashMap { - let mut oracle_price_map: HashMap = HashMap::new(); - for (base_quote, oracle_price) in oracle_price.iter() { - oracle_price_map.insert((*base_quote).clone(), **oracle_price); - } - - oracle_price_map -} - -#[derive(Clone, Default)] -pub struct CollateralOracleQuerier { - // this lets us iterate over all pairs that match the first string - collateral_infos: HashMap, -} - -impl CollateralOracleQuerier { - pub fn new(collateral_infos: &[(&String, &Decimal, &Decimal, &bool)]) -> Self { - CollateralOracleQuerier { - collateral_infos: collateral_infos_to_map(collateral_infos), - } - } -} - -pub(crate) fn collateral_infos_to_map( - collateral_infos: &[(&String, &Decimal, &Decimal, &bool)], -) -> HashMap { - let mut collateral_infos_map: HashMap = HashMap::new(); - for (collateral, collateral_price, collateral_multiplier, is_revoked) in collateral_infos.iter() - { - collateral_infos_map.insert( - (*collateral).clone(), - (**collateral_price, **collateral_multiplier, **is_revoked), - ); - } - - collateral_infos_map -} - -#[derive(Clone, Default)] -pub struct TerraswapPairQuerier { - // this lets us iterate over all pairs that match the first string - pairs: HashMap, -} - -impl TerraswapPairQuerier { - pub fn new(pairs: &[(&String, &String, &HumanAddr)]) -> Self { - TerraswapPairQuerier { - pairs: paris_to_map(pairs), - } - } -} - -pub(crate) fn paris_to_map(pairs: &[(&String, &String, &HumanAddr)]) -> HashMap { - let mut pairs_map: HashMap = HashMap::new(); - for (asset1, asset2, pair) in pairs.iter() { - pairs_map.insert( - (asset1.to_string() + &asset2.to_string()).clone(), - HumanAddr::from(pair), - ); - } - - pairs_map -} - -#[derive(Clone, Default)] -pub struct OracleQuerier { - feeders: HashMap, -} - -impl OracleQuerier { - pub fn new(feeders: &[(&HumanAddr, &HumanAddr)]) -> Self { - OracleQuerier { - feeders: address_pair_to_map(feeders), - } - } -} - -pub(crate) fn address_pair_to_map( - address_pair: &[(&HumanAddr, &HumanAddr)], -) -> HashMap { - let mut address_pair_map: HashMap = HashMap::new(); - for (addr1, addr2) in address_pair.iter() { - address_pair_map.insert(HumanAddr::from(addr1), HumanAddr::from(addr2)); - } - address_pair_map -} - -impl Querier for WasmMockQuerier { - fn raw_query(&self, bin_request: &[u8]) -> QuerierResult { - // MockQuerier doesn't support Custom, so we ignore it completely here - let request: QueryRequest = match from_slice(bin_request) { - Ok(v) => v, - Err(e) => { - return Err(SystemError::InvalidRequest { - error: format!("Parsing query request: {}", e), - request: bin_request.into(), - }) - } - }; - self.handle_query(&request) - } -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum MockQueryMsg { - Price { - base_asset: String, - quote_asset: String, - }, - CollateralPrice { - asset: String, - }, - Pair { - asset_infos: [AssetInfo; 2], - }, -} - -impl WasmMockQuerier { - pub fn handle_query(&self, request: &QueryRequest) -> QuerierResult { - match &request { - QueryRequest::Custom(TerraQueryWrapper { route, query_data }) => { - if route == &TerraRoute::Treasury { - match query_data { - TerraQuery::TaxRate {} => { - let res = TaxRateResponse { - rate: self.tax_querier.rate, - }; - Ok(to_binary(&res)) - } - TerraQuery::TaxCap { denom } => { - let cap = self - .tax_querier - .caps - .get(denom) - .copied() - .unwrap_or_default(); - let res = TaxCapResponse { cap }; - Ok(to_binary(&res)) - } - _ => panic!("DO NOT ENTER HERE"), - } - } else { - panic!("DO NOT ENTER HERE") - } - } - QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: _, - msg, - }) => match from_binary(&msg).unwrap() { - MockQueryMsg::Price { - base_asset, - quote_asset, - } => match self.oracle_price_querier.oracle_price.get(&base_asset) { - Some(base_price) => { - match self.oracle_price_querier.oracle_price.get("e_asset) { - Some(quote_price) => Ok(to_binary(&PriceResponse { - rate: decimal_division(*base_price, *quote_price), - last_updated_base: 1000u64, - last_updated_quote: 1000u64, - })), - None => Err(SystemError::InvalidRequest { - error: "No oracle price exists".to_string(), - request: msg.as_slice().into(), - }), - } - } - None => Err(SystemError::InvalidRequest { - error: "No oracle price exists".to_string(), - request: msg.as_slice().into(), - }), - }, - MockQueryMsg::CollateralPrice { asset } => { - match self.collateral_oracle_querier.collateral_infos.get(&asset) { - Some(collateral_info) => Ok(to_binary(&CollateralPriceResponse { - asset, - rate: collateral_info.0, - last_updated: 1000u64, - multiplier: collateral_info.1, - is_revoked: collateral_info.2, - })), - None => Err(SystemError::InvalidRequest { - error: "Collateral info does not exist".to_string(), - request: msg.as_slice().into(), - }), - } - } - MockQueryMsg::Pair { asset_infos } => { - match self - .terraswap_pair_querier - .pairs - .get(&(asset_infos[0].to_string() + &asset_infos[1].to_string())) - { - Some(pair) => Ok(to_binary(&PairInfo { - asset_infos, - contract_addr: pair.clone(), - liquidity_token: HumanAddr::from("liquidity"), - })), - None => Err(SystemError::InvalidRequest { - error: "No pair exists".to_string(), - request: msg.as_slice().into(), - }), - } - } - }, - QueryRequest::Wasm(WasmQuery::Raw { contract_addr, key }) => { - let key: &[u8] = key.as_slice(); - let prefix_balance = to_length_prefixed(b"balance").to_vec(); - let prefix_feeder = to_length_prefixed(b"feeder").to_vec(); - - if key.len() > prefix_balance.len() - && key[..prefix_balance.len()].to_vec() == prefix_balance - { - let balances: &HashMap = - match self.token_querier.balances.get(contract_addr) { - Some(balances) => balances, - None => { - return Err(SystemError::InvalidRequest { - error: format!( - "No balance info exists for the contract {}", - contract_addr - ), - request: key.into(), - }) - } - }; - - let key_address: &[u8] = &key[prefix_balance.len()..]; - let address_raw: CanonicalAddr = CanonicalAddr::from(key_address); - - let api: MockApi = MockApi::new(self.canonical_length); - let address: HumanAddr = match api.human_address(&address_raw) { - Ok(v) => v, - Err(e) => { - return Err(SystemError::InvalidRequest { - error: format!("Parsing query request: {}", e), - request: key.into(), - }) - } - }; - - let balance = match balances.get(&address) { - Some(v) => v, - None => { - return Err(SystemError::InvalidRequest { - error: "Balance not found".to_string(), - request: key.into(), - }) - } - }; - - Ok(to_binary(&to_binary(&balance).unwrap())) - } else if key.len() > prefix_feeder.len() - && key[..prefix_feeder.len()].to_vec() == prefix_feeder - { - let api: MockApi = MockApi::new(self.canonical_length); - let rest_key: &[u8] = &key[prefix_feeder.len()..]; - - if contract_addr == &HumanAddr::from("oracle0000") { - let asset_token: HumanAddr = api - .human_address(&(CanonicalAddr::from(rest_key.to_vec()))) - .unwrap(); - - let feeder = match self.oracle_querier.feeders.get(&asset_token) { - Some(v) => v, - None => { - return Err(SystemError::InvalidRequest { - error: format!( - "Oracle Feeder is not found for {}", - asset_token - ), - request: key.into(), - }) - } - }; - - Ok(to_binary( - &to_binary(&api.canonical_address(&feeder).unwrap()).unwrap(), - )) - } else { - panic!("DO NOT ENTER HERE") - } - } else { - panic!("DO NOT ENTER HERE") - } - } - _ => self.base.handle_query(request), - } - } -} - -impl WasmMockQuerier { - pub fn new( - base: MockQuerier, - _api: A, - canonical_length: usize, - ) -> Self { - WasmMockQuerier { - base, - token_querier: TokenQuerier::default(), - tax_querier: TaxQuerier::default(), - oracle_price_querier: OraclePriceQuerier::default(), - collateral_oracle_querier: CollateralOracleQuerier::default(), - terraswap_pair_querier: TerraswapPairQuerier::default(), - oracle_querier: OracleQuerier::default(), - canonical_length, - } - } - - // configure the mint whitelist mock querier - pub fn with_token_balances(&mut self, balances: &[(&HumanAddr, &[(&HumanAddr, &Uint128)])]) { - self.token_querier = TokenQuerier::new(balances); - } - - // configure the token owner mock querier - pub fn with_tax(&mut self, rate: Decimal, caps: &[(&String, &Uint128)]) { - self.tax_querier = TaxQuerier::new(rate, caps); - } - - // configure the oracle price mock querier - pub fn with_oracle_price(&mut self, oracle_price: &[(&String, &Decimal)]) { - self.oracle_price_querier = OraclePriceQuerier::new(oracle_price); - } - - // configure the collateral oracle mock querier - pub fn with_collateral_infos( - &mut self, - collateral_infos: &[(&String, &Decimal, &Decimal, &bool)], - ) { - self.collateral_oracle_querier = CollateralOracleQuerier::new(collateral_infos); - } - - // configure the terraswap factory pair mock querier - pub fn with_terraswap_pair(&mut self, pairs: &[(&String, &String, &HumanAddr)]) { - self.terraswap_pair_querier = TerraswapPairQuerier::new(pairs); - } - - pub fn with_oracle_feeders(&mut self, feeders: &[(&HumanAddr, &HumanAddr)]) { - self.oracle_querier = OracleQuerier::new(feeders); - } -} diff --git a/contracts/mirror_mint/src/positions.rs b/contracts/mirror_mint/src/positions.rs deleted file mode 100644 index 5d8edf0cc..000000000 --- a/contracts/mirror_mint/src/positions.rs +++ /dev/null @@ -1,971 +0,0 @@ -use cosmwasm_std::{ - log, to_binary, Api, CosmosMsg, Decimal, Env, Extern, HandleResponse, HandleResult, HumanAddr, - LogAttribute, Querier, StdError, StdResult, Storage, Uint128, WasmMsg, -}; - -use crate::{ - asserts::{ - assert_asset, assert_burn_period, assert_collateral, assert_migrated_asset, - assert_mint_period, assert_revoked_collateral, - }, - math::{decimal_division, decimal_multiplication, decimal_subtraction, reverse_decimal}, - querier::{load_asset_price, load_collateral_info}, - state::{ - create_position, is_short_position, read_asset_config, read_config, read_position, - read_position_idx, read_positions, read_positions_with_asset_indexer, - read_positions_with_user_indexer, remove_position, store_position, store_position_idx, - store_short_position, AssetConfig, Config, Position, - }, -}; - -use cw20::Cw20HandleMsg; -use mirror_protocol::{ - common::OrderBy, - lock::HandleMsg as LockHandleMsg, - mint::{NextPositionIdxResponse, PositionResponse, PositionsResponse, ShortParams}, - staking::HandleMsg as StakingHandleMsg, -}; -use terraswap::{ - asset::{Asset, AssetInfo, AssetInfoRaw, AssetRaw}, - pair::Cw20HookMsg as PairCw20HookMsg, - querier::query_pair_info, -}; - -pub fn open_position( - deps: &mut Extern, - env: Env, - sender: HumanAddr, - collateral: Asset, - asset_info: AssetInfo, - collateral_ratio: Decimal, - short_params: Option, -) -> StdResult { - let config: Config = read_config(&deps.storage)?; - if collateral.amount.is_zero() { - return Err(StdError::generic_err("Wrong collateral")); - } - - // assert the collateral is listed and has not been migrated/revoked - let collateral_info_raw: AssetInfoRaw = collateral.info.to_raw(&deps)?; - let collateral_oracle: HumanAddr = deps.api.human_address(&config.collateral_oracle)?; - let (collateral_price, collateral_multiplier) = - assert_revoked_collateral(load_collateral_info( - &deps, - &collateral_oracle, - &collateral_info_raw, - Some(env.block.time), - )?)?; - - // assert asset migrated - let asset_info_raw: AssetInfoRaw = asset_info.to_raw(&deps)?; - let asset_token_raw = match asset_info_raw.clone() { - AssetInfoRaw::Token { contract_addr } => contract_addr, - _ => panic!("DO NOT ENTER HERE"), - }; - - let asset_config: AssetConfig = read_asset_config(&deps.storage, &asset_token_raw)?; - assert_migrated_asset(&asset_config)?; - - // for assets with limited minting period (preIPO assets), assert minting phase - assert_mint_period(&env, &asset_config)?; - - if collateral_ratio < asset_config.min_collateral_ratio { - return Err(StdError::generic_err( - "Can not open a position with low collateral ratio than minimum", - )); - } - - let oracle: HumanAddr = deps.api.human_address(&config.oracle)?; - let asset_price: Decimal = - load_asset_price(&deps, &oracle, &asset_info_raw, Some(env.block.time))?; - - let asset_price_in_collateral_asset = decimal_division(collateral_price, asset_price); - - // Calculate effective cr - let effective_cr = decimal_multiplication(collateral_ratio, collateral_multiplier); - - // Convert collateral to mint amount - let mint_amount = - collateral.amount * asset_price_in_collateral_asset * reverse_decimal(effective_cr); - if mint_amount.is_zero() { - return Err(StdError::generic_err("collateral is too small")); - } - - let position_idx = read_position_idx(&deps.storage)?; - let asset_info_raw = asset_info.to_raw(&deps)?; - - create_position( - &mut deps.storage, - position_idx, - &Position { - idx: position_idx, - owner: deps.api.canonical_address(&sender)?, - collateral: AssetRaw { - amount: collateral.amount, - info: collateral_info_raw, - }, - asset: AssetRaw { - amount: mint_amount, - info: asset_info_raw, - }, - }, - )?; - - // If the short_params exists, the position is - // flagged as short position. so if want to make short position, - // the one must pass at least empty {} as short_params - let is_short: bool; - let asset_token = deps.api.human_address(&asset_config.token)?; - let messages: Vec = if let Some(short_params) = short_params { - is_short = true; - store_short_position(&mut deps.storage, position_idx)?; - - // need to sell the tokens to terraswap - // load pair contract address - let pair_info = query_pair_info( - &deps, - &deps.api.human_address(&config.terraswap_factory)?, - &[ - AssetInfo::NativeToken { - denom: config.base_denom, - }, - asset_info.clone(), - ], - )?; - - // 1. Mint token to itself - // 2. Swap token and send to lock contract - // 3. Call lock hook on lock contract - // 4. Increase short token in staking contract - let lock_contract = deps.api.human_address(&config.lock)?; - vec![ - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: asset_token.clone(), - send: vec![], - msg: to_binary(&Cw20HandleMsg::Mint { - recipient: env.contract.address, - amount: mint_amount, - })?, - }), - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: asset_token.clone(), - send: vec![], - msg: to_binary(&Cw20HandleMsg::Send { - contract: pair_info.contract_addr, - amount: mint_amount, - msg: Some(to_binary(&PairCw20HookMsg::Swap { - belief_price: short_params.belief_price, - max_spread: short_params.max_spread, - to: Some(lock_contract.clone()), - })?), - })?, - }), - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: lock_contract, - send: vec![], - msg: to_binary(&LockHandleMsg::LockPositionFundsHook { - position_idx, - receiver: sender.clone(), - })?, - }), - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: deps.api.human_address(&config.staking)?, - send: vec![], - msg: to_binary(&StakingHandleMsg::IncreaseShortToken { - asset_token, - staker_addr: sender, - amount: mint_amount, - })?, - }), - ] - } else { - is_short = false; - vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: asset_token, - send: vec![], - msg: to_binary(&Cw20HandleMsg::Mint { - recipient: sender, - amount: mint_amount, - })?, - })] - }; - - store_position_idx(&mut deps.storage, position_idx + Uint128(1u128))?; - Ok(HandleResponse { - messages, - log: vec![ - log("action", "open_position"), - log("position_idx", position_idx.to_string()), - log( - "mint_amount", - mint_amount.to_string() + &asset_info.to_string(), - ), - log("collateral_amount", collateral.to_string()), - log("is_short", is_short), - ], - data: None, - }) -} - -pub fn deposit( - deps: &mut Extern, - sender: HumanAddr, - position_idx: Uint128, - collateral: Asset, -) -> HandleResult { - let config: Config = read_config(&deps.storage)?; - let mut position: Position = read_position(&deps.storage, position_idx)?; - let position_owner = deps.api.human_address(&position.owner)?; - if sender != position_owner { - return Err(StdError::unauthorized()); - } - - // Check the given collateral has same asset info - // with position's collateral token - // also Check the collateral amount is non-zero - assert_collateral(deps, &position, &collateral)?; - - // assert the collateral is listed and has not been migrated/revoked - let collateral_oracle: HumanAddr = deps.api.human_address(&config.collateral_oracle)?; - assert_revoked_collateral(load_collateral_info( - &deps, - &collateral_oracle, - &position.collateral.info, - None, - )?)?; - - // assert asset migrated - match position.asset.info.clone() { - AssetInfoRaw::Token { contract_addr } => { - assert_migrated_asset(&read_asset_config(&deps.storage, &contract_addr)?)? - } - _ => panic!("DO NOT ENTER HERE"), - }; - - // Increase collateral amount - position.collateral.amount += collateral.amount; - store_position(&mut deps.storage, position_idx, &position)?; - - Ok(HandleResponse { - messages: vec![], - log: vec![ - log("action", "deposit"), - log("position_idx", position_idx.to_string()), - log("deposit_amount", collateral.to_string()), - ], - data: None, - }) -} - -pub fn withdraw( - deps: &mut Extern, - env: Env, - position_idx: Uint128, - collateral: Asset, -) -> HandleResult { - let config: Config = read_config(&deps.storage)?; - let mut position: Position = read_position(&deps.storage, position_idx)?; - let position_owner = deps.api.human_address(&position.owner)?; - if env.message.sender != position_owner { - return Err(StdError::unauthorized()); - } - - // Check the given collateral has same asset info - // with position's collateral token - // also Check the collateral amount is non-zero - assert_collateral(deps, &position, &collateral)?; - - if position.collateral.amount < collateral.amount { - return Err(StdError::generic_err( - "Cannot withdraw more than you provide", - )); - } - - let asset_token_raw = match position.asset.info.clone() { - AssetInfoRaw::Token { contract_addr } => contract_addr, - _ => panic!("DO NOT ENTER HERE"), - }; - - let asset_config: AssetConfig = read_asset_config(&deps.storage, &asset_token_raw)?; - let oracle: HumanAddr = deps.api.human_address(&config.oracle)?; - let asset_price: Decimal = - load_asset_price(&deps, &oracle, &position.asset.info, Some(env.block.time))?; - - // Fetch collateral info from collateral oracle - let collateral_oracle: HumanAddr = deps.api.human_address(&config.collateral_oracle)?; - let (collateral_price, collateral_multiplier, _collateral_is_revoked) = load_collateral_info( - &deps, - &collateral_oracle, - &position.collateral.info, - Some(env.block.time), - )?; - - // Compute new collateral amount - let collateral_amount: Uint128 = (position.collateral.amount - collateral.amount)?; - - // Convert asset to collateral unit - let asset_value_in_collateral_asset: Uint128 = - position.asset.amount * decimal_division(asset_price, collateral_price); - - // Check minimum collateral ratio is satisfied - if asset_value_in_collateral_asset * asset_config.min_collateral_ratio * collateral_multiplier - > collateral_amount - { - return Err(StdError::generic_err( - "Cannot withdraw collateral over than minimum collateral ratio", - )); - } - - let mut messages: Vec = vec![]; - - position.collateral.amount = collateral_amount; - if position.collateral.amount == Uint128::zero() && position.asset.amount == Uint128::zero() { - // if it is a short position, release locked funds - if is_short_position(&deps.storage, position_idx)? { - messages.push(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: deps.api.human_address(&config.lock)?, - send: vec![], - msg: to_binary(&LockHandleMsg::ReleasePositionFunds { position_idx })?, - })); - } - remove_position(&mut deps.storage, position_idx)?; - } else { - store_position(&mut deps.storage, position_idx, &position)?; - } - - let mut collateral = collateral; - - // Deduct protocol fee - let protocol_fee = Asset { - info: collateral.info.clone(), - amount: collateral.amount * config.protocol_fee_rate, - }; - - collateral.amount = (collateral.amount - protocol_fee.amount)?; - - // Compute tax amount - let tax_amount = collateral.compute_tax(&deps)?; - - if !protocol_fee.amount.is_zero() { - messages.push(protocol_fee.clone().into_msg( - &deps, - env.contract.address.clone(), - deps.api.human_address(&config.collector)?, - )?); - } - - Ok(HandleResponse { - messages: vec![ - vec![collateral - .clone() - .into_msg(&deps, env.contract.address, position_owner)?], - messages, - ] - .concat(), - log: vec![ - log("action", "withdraw"), - log("position_idx", position_idx.to_string()), - log("withdraw_amount", collateral.to_string()), - log( - "tax_amount", - tax_amount.to_string() + &collateral.info.to_string(), - ), - log("protocol_fee", protocol_fee.to_string()), - ], - data: None, - }) -} - -pub fn mint( - deps: &mut Extern, - env: Env, - position_idx: Uint128, - asset: Asset, - short_params: Option, -) -> HandleResult { - let config: Config = read_config(&deps.storage)?; - let mint_amount = asset.amount; - let sender = env.message.sender.clone(); - - let mut position: Position = read_position(&deps.storage, position_idx)?; - let position_owner = deps.api.human_address(&position.owner)?; - if sender != position_owner { - return Err(StdError::unauthorized()); - } - - assert_asset(&deps, &position, &asset)?; - - let asset_token_raw = match position.asset.info.clone() { - AssetInfoRaw::Token { contract_addr } => contract_addr, - _ => panic!("DO NOT ENTER HERE"), - }; - - // assert the asset migrated - let asset_config: AssetConfig = read_asset_config(&deps.storage, &asset_token_raw)?; - assert_migrated_asset(&asset_config)?; - - // assert the collateral is listed and has not been migrated/revoked - let collateral_oracle: HumanAddr = deps.api.human_address(&config.collateral_oracle)?; - let (collateral_price, collateral_multiplier) = - assert_revoked_collateral(load_collateral_info( - &deps, - &collateral_oracle, - &position.collateral.info, - Some(env.block.time), - )?)?; - - // for assets with limited minting period (preIPO assets), assert minting phase - assert_mint_period(&env, &asset_config)?; - - let oracle: HumanAddr = deps.api.human_address(&config.oracle)?; - let asset_price: Decimal = - load_asset_price(&deps, &oracle, &position.asset.info, Some(env.block.time))?; - - // Compute new asset amount - let asset_amount: Uint128 = mint_amount + position.asset.amount; - - // Convert asset to collateral unit - let asset_value_in_collateral_asset: Uint128 = - asset_amount * decimal_division(asset_price, collateral_price); - - // Check minimum collateral ratio is satisfied - if asset_value_in_collateral_asset * asset_config.min_collateral_ratio * collateral_multiplier - > position.collateral.amount - { - return Err(StdError::generic_err( - "Cannot mint asset over than min collateral ratio", - )); - } - - position.asset.amount += mint_amount; - store_position(&mut deps.storage, position_idx, &position)?; - - let asset_token = deps.api.human_address(&asset_config.token)?; - - // If the position is flagged as short position. - // immediately sell the token to terraswap - // and execute staking short token - let messages: Vec = if is_short_position(&deps.storage, position_idx)? { - // need to sell the tokens to terraswap - // load pair contract address - let pair_info = query_pair_info( - &deps, - &deps.api.human_address(&config.terraswap_factory)?, - &[ - AssetInfo::NativeToken { - denom: config.base_denom, - }, - asset.info.clone(), - ], - )?; - - // 1. Mint token to itself - // 2. Swap token and send to lock contract - // 3. Call lock hook on lock contract - // 4. Increase short token in staking contract - let lock_contract = deps.api.human_address(&config.lock)?; - vec![ - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: asset_token.clone(), - send: vec![], - msg: to_binary(&Cw20HandleMsg::Mint { - recipient: env.contract.address, - amount: mint_amount, - })?, - }), - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: asset_token.clone(), - send: vec![], - msg: to_binary(&Cw20HandleMsg::Send { - contract: pair_info.contract_addr, - amount: mint_amount, - msg: Some(to_binary( - &(if let Some(short_params) = short_params { - PairCw20HookMsg::Swap { - belief_price: short_params.belief_price, - max_spread: short_params.max_spread, - to: Some(lock_contract.clone()), - } - } else { - PairCw20HookMsg::Swap { - belief_price: None, - max_spread: None, - to: Some(lock_contract.clone()), - } - }), - )?), - })?, - }), - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: lock_contract, - send: vec![], - msg: to_binary(&LockHandleMsg::LockPositionFundsHook { - position_idx, - receiver: position_owner.clone(), - })?, - }), - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: deps.api.human_address(&config.staking)?, - send: vec![], - msg: to_binary(&StakingHandleMsg::IncreaseShortToken { - asset_token, - staker_addr: position_owner, - amount: mint_amount, - })?, - }), - ] - } else { - vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: deps.api.human_address(&asset_config.token)?, - msg: to_binary(&Cw20HandleMsg::Mint { - amount: mint_amount, - recipient: position_owner, - })?, - send: vec![], - })] - }; - - Ok(HandleResponse { - messages, - log: vec![ - log("action", "mint"), - log("position_idx", position_idx.to_string()), - log("mint_amount", asset.to_string()), - ], - data: None, - }) -} - -pub fn burn( - deps: &mut Extern, - env: Env, - sender: HumanAddr, - position_idx: Uint128, - asset: Asset, -) -> StdResult { - let burn_amount = asset.amount; - - let config: Config = read_config(&deps.storage)?; - let mut position: Position = read_position(&deps.storage, position_idx)?; - let position_owner = deps.api.human_address(&position.owner)?; - let collateral_info: AssetInfo = position.collateral.info.to_normal(&deps)?; - - // Check the asset has same token with position asset - // also Check burn amount is non-zero - assert_asset(deps, &position, &asset)?; - - let asset_token_raw = match position.asset.info.clone() { - AssetInfoRaw::Token { contract_addr } => contract_addr, - _ => panic!("DO NOT ENTER HERE"), - }; - - let asset_config: AssetConfig = read_asset_config(&deps.storage, &asset_token_raw)?; - if position.asset.amount < burn_amount { - return Err(StdError::generic_err( - "Cannot burn asset more than you mint", - )); - } - - let mut messages: Vec = vec![]; - let mut logs: Vec = vec![]; - - // For preIPO assets, burning should be disabled after the minting period is over. - // Burning is enabled again after IPO event is triggered, when ipo_params are set to None - assert_burn_period(&env, &asset_config)?; - - // Check if it is a short position - let is_short_position: bool = is_short_position(&deps.storage, position_idx)?; - - // If the collateral is default denom asset and the asset is deprecated, - // anyone can execute burn the asset to any position without permission - let mut close_position: bool = false; - if let Some(end_price) = asset_config.end_price { - let asset_price: Decimal = end_price; - - // fetch collateral info from collateral oracle - let collateral_oracle: HumanAddr = deps.api.human_address(&config.collateral_oracle)?; - let (collateral_price, _collateral_multiplier, _collateral_is_revoked) = - load_collateral_info( - &deps, - &collateral_oracle, - &position.collateral.info, - Some(env.block.time), - )?; - - let collateral_price_in_asset = decimal_division(asset_price, collateral_price); - - // Burn deprecated asset to receive collaterals back - let conversion_rate = - Decimal::from_ratio(position.collateral.amount, position.asset.amount); - let refund_collateral = Asset { - info: collateral_info, - amount: std::cmp::min( - burn_amount * collateral_price_in_asset, - burn_amount * conversion_rate, - ), - }; - - position.asset.amount = (position.asset.amount - burn_amount).unwrap(); - position.collateral.amount = - (position.collateral.amount - refund_collateral.amount).unwrap(); - - // due to rounding, include 1 - if position.collateral.amount <= Uint128(1u128) && position.asset.amount == Uint128::zero() - { - close_position = true; - remove_position(&mut deps.storage, position_idx)?; - } else { - store_position(&mut deps.storage, position_idx, &position)?; - } - - // Refund collateral msg - messages.push( - refund_collateral - .clone() - .into_msg(&deps, env.contract.address, sender)?, - ); - - logs.push(log( - "refund_collateral_amount", - refund_collateral.to_string(), - )); - } else { - if sender != position_owner { - return Err(StdError::unauthorized()); - } - - // Update asset amount - position.asset.amount = (position.asset.amount - burn_amount).unwrap(); - store_position(&mut deps.storage, position_idx, &position)?; - } - - // If the position is flagged as short position. - // decrease short token amount from the staking contract - let asset_token = deps.api.human_address(&asset_config.token)?; - if is_short_position { - messages.push(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: deps.api.human_address(&config.staking)?, - msg: to_binary(&StakingHandleMsg::DecreaseShortToken { - asset_token: asset_token.clone(), - staker_addr: position_owner, - amount: burn_amount, - })?, - send: vec![], - })); - if close_position { - messages.push(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: deps.api.human_address(&config.lock)?, - send: vec![], - msg: to_binary(&LockHandleMsg::ReleasePositionFunds { position_idx })?, - })); - } - } - - Ok(HandleResponse { - messages: vec![ - vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: asset_token, - msg: to_binary(&Cw20HandleMsg::Burn { - amount: burn_amount, - })?, - send: vec![], - })], - messages, - ] - .concat(), - log: vec![ - vec![ - log("action", "burn"), - log("position_idx", position_idx.to_string()), - log("burn_amount", asset.to_string()), - ], - logs, - ] - .concat(), - data: None, - }) -} - -pub fn auction( - deps: &mut Extern, - env: Env, - sender: HumanAddr, - position_idx: Uint128, - asset: Asset, -) -> StdResult { - let config: Config = read_config(&deps.storage)?; - let mut position: Position = read_position(&deps.storage, position_idx)?; - let position_owner = deps.api.human_address(&position.owner)?; - assert_asset(&deps, &position, &asset)?; - - let asset_token_raw = match position.asset.info.clone() { - AssetInfoRaw::Token { contract_addr } => contract_addr, - _ => panic!("DO NOT ENTER HERE"), - }; - - let asset_config: AssetConfig = read_asset_config(&deps.storage, &asset_token_raw)?; - assert_migrated_asset(&asset_config)?; - - let asset_token = deps.api.human_address(&asset_config.token)?; - let collateral_info = position.collateral.info.to_normal(&deps)?; - - if asset.amount > position.asset.amount { - return Err(StdError::generic_err( - "Cannot liquidate more than the position amount".to_string(), - )); - } - - let oracle: HumanAddr = deps.api.human_address(&config.oracle)?; - let asset_price: Decimal = - load_asset_price(&deps, &oracle, &position.asset.info, Some(env.block.time))?; - - // fetch collateral info from collateral oracle - let collateral_oracle: HumanAddr = deps.api.human_address(&config.collateral_oracle)?; - let (collateral_price, collateral_multiplier, _collateral_is_revoked) = load_collateral_info( - &deps, - &collateral_oracle, - &position.collateral.info, - Some(env.block.time), - )?; - - // Compute collateral price in asset unit - let collateral_price_in_asset: Decimal = decimal_division(asset_price, collateral_price); - - // Check the position is in auction state - // asset_amount * price_to_collateral * auction_threshold > collateral_amount - let asset_value_in_collateral_asset: Uint128 = - position.asset.amount * collateral_price_in_asset; - if asset_value_in_collateral_asset * asset_config.min_collateral_ratio * collateral_multiplier - < position.collateral.amount - { - return Err(StdError::generic_err( - "Cannot liquidate a safely collateralized position", - )); - } - - // Compute discounted price - let discounted_price: Decimal = decimal_division( - collateral_price_in_asset, - decimal_subtraction(Decimal::one(), asset_config.auction_discount), - ); - - // Convert asset value in discounted collateral unit - let asset_value_in_collateral_asset: Uint128 = asset.amount * discounted_price; - - let mut messages: Vec = vec![]; - - // Cap return collateral amount to position collateral amount - // If the given asset amount exceeds the amount required to liquidate position, - // left asset amount will be refunds to executor. - let (return_collateral_amount, refund_asset_amount) = - if asset_value_in_collateral_asset > position.collateral.amount { - // refunds left asset to position liquidator - let refund_asset_amount = - (asset_value_in_collateral_asset - position.collateral.amount).unwrap() - * reverse_decimal(discounted_price); - - let refund_asset: Asset = Asset { - info: asset.info.clone(), - amount: refund_asset_amount, - }; - - messages.push(refund_asset.into_msg( - &deps, - env.contract.address.clone(), - sender.clone(), - )?); - - (position.collateral.amount, refund_asset_amount) - } else { - (asset_value_in_collateral_asset, Uint128::zero()) - }; - - let liquidated_asset_amount = (asset.amount - refund_asset_amount).unwrap(); - let left_asset_amount = (position.asset.amount - liquidated_asset_amount).unwrap(); - let left_collateral_amount = (position.collateral.amount - return_collateral_amount).unwrap(); - - // Check if it is a short position - let is_short_position: bool = is_short_position(&deps.storage, position_idx)?; - - let mut close_position: bool = false; - if left_collateral_amount.is_zero() { - // all collaterals are sold out - close_position = true; - remove_position(&mut deps.storage, position_idx)?; - } else if left_asset_amount.is_zero() { - // all assets are paid - close_position = true; - remove_position(&mut deps.storage, position_idx)?; - - // refunds left collaterals to position owner - let refund_collateral: Asset = Asset { - info: collateral_info.clone(), - amount: left_collateral_amount, - }; - - messages.push(refund_collateral.into_msg( - &deps, - env.contract.address.clone(), - position_owner.clone(), - )?); - } else { - position.collateral.amount = left_collateral_amount; - position.asset.amount = left_asset_amount; - - store_position(&mut deps.storage, position_idx, &position)?; - } - - // token burn message - messages.push(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: asset_token.clone(), - msg: to_binary(&Cw20HandleMsg::Burn { - amount: liquidated_asset_amount, - })?, - send: vec![], - })); - - // Deduct protocol fee - let protocol_fee = return_collateral_amount * config.protocol_fee_rate; - let return_collateral_amount = (return_collateral_amount - protocol_fee).unwrap(); - - // return collateral to liquidation initiator(sender) - let return_collateral_asset = Asset { - info: collateral_info.clone(), - amount: return_collateral_amount, - }; - let tax_amount = return_collateral_asset.compute_tax(&deps)?; - messages.push(return_collateral_asset.into_msg(&deps, env.contract.address.clone(), sender)?); - - // protocol fee sent to collector - let protocol_fee_asset = Asset { - info: collateral_info.clone(), - amount: protocol_fee, - }; - - if !protocol_fee_asset.amount.is_zero() { - messages.push(protocol_fee_asset.into_msg( - &deps, - env.contract.address, - deps.api.human_address(&config.collector)?, - )?); - } - - // If the position is flagged as short position. - // decrease short token amount from the staking contract - if is_short_position { - messages.push(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: deps.api.human_address(&config.staking)?, - msg: to_binary(&StakingHandleMsg::DecreaseShortToken { - asset_token: asset_token, - staker_addr: position_owner.clone(), - amount: liquidated_asset_amount, - })?, - send: vec![], - })); - if close_position { - messages.push(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: deps.api.human_address(&config.lock)?, - send: vec![], - msg: to_binary(&LockHandleMsg::ReleasePositionFunds { position_idx })?, - })); - } - } - - let collateral_info_str = collateral_info.to_string(); - let asset_info_str = asset.info.to_string(); - Ok(HandleResponse { - log: vec![ - log("action", "auction"), - log("position_idx", position_idx.to_string()), - log("owner", position_owner.as_str()), - log( - "return_collateral_amount", - return_collateral_amount.to_string() + &collateral_info_str, - ), - log( - "liquidated_amount", - liquidated_asset_amount.to_string() + &asset_info_str, - ), - log("tax_amount", tax_amount.to_string() + &collateral_info_str), - log( - "protocol_fee", - protocol_fee.to_string() + &collateral_info_str, - ), - ], - messages, - data: None, - }) -} - -pub fn query_position( - deps: &Extern, - position_idx: Uint128, -) -> StdResult { - let position: Position = read_position(&deps.storage, position_idx)?; - let resp = PositionResponse { - idx: position.idx, - owner: deps.api.human_address(&position.owner)?, - collateral: position.collateral.to_normal(&deps)?, - asset: position.asset.to_normal(&deps)?, - is_short: is_short_position(&deps.storage, position.idx)?, - }; - - Ok(resp) -} - -pub fn query_positions( - deps: &Extern, - owner_addr: Option, - asset_token: Option, - start_after: Option, - limit: Option, - order_by: Option, -) -> StdResult { - let positions: Vec = if let Some(owner_addr) = owner_addr { - read_positions_with_user_indexer( - &deps.storage, - &deps.api.canonical_address(&owner_addr)?, - start_after, - limit, - order_by, - )? - } else if let Some(asset_token) = asset_token { - read_positions_with_asset_indexer( - &deps.storage, - &deps.api.canonical_address(&asset_token)?, - start_after, - limit, - order_by, - )? - } else { - read_positions(&deps.storage, start_after, limit, order_by)? - }; - - let position_responses: StdResult> = positions - .iter() - .map(|position| { - Ok(PositionResponse { - idx: position.idx, - owner: deps.api.human_address(&position.owner)?, - collateral: position.collateral.to_normal(&deps)?, - asset: position.asset.to_normal(&deps)?, - is_short: is_short_position(&deps.storage, position.idx)?, - }) - }) - .collect(); - - Ok(PositionsResponse { - positions: position_responses?, - }) -} - -pub fn query_next_position_idx( - deps: &Extern, -) -> StdResult { - let idx = read_position_idx(&deps.storage)?; - let resp = NextPositionIdxResponse { - next_position_idx: idx, - }; - - Ok(resp) -} diff --git a/contracts/mirror_mint/src/positions_test.rs b/contracts/mirror_mint/src/positions_test.rs deleted file mode 100644 index ce5869e84..000000000 --- a/contracts/mirror_mint/src/positions_test.rs +++ /dev/null @@ -1,1720 +0,0 @@ -#[cfg(test)] -mod tests { - - use crate::contract::{handle, init, query}; - use crate::mock_querier::mock_dependencies; - use cosmwasm_std::testing::{mock_env, MOCK_CONTRACT_ADDR}; - use cosmwasm_std::{ - from_binary, log, to_binary, BankMsg, BlockInfo, Coin, CosmosMsg, Decimal, Env, HumanAddr, - StdError, Uint128, WasmMsg, - }; - use cw20::{Cw20HandleMsg, Cw20ReceiveMsg}; - use mirror_protocol::common::OrderBy; - use mirror_protocol::mint::{ - Cw20HookMsg, HandleMsg, InitMsg, PositionResponse, PositionsResponse, QueryMsg, - }; - use terraswap::asset::{Asset, AssetInfo}; - - static TOKEN_CODE_ID: u64 = 10u64; - fn mock_env_with_block_time>(sender: U, sent: &[Coin], time: u64) -> Env { - let env = mock_env(sender, sent); - // register time - return Env { - block: BlockInfo { - height: 1, - time, - chain_id: "columbus".to_string(), - }, - ..env - }; - } - - #[test] - fn open_position() { - let mut deps = mock_dependencies(20, &[]); - deps.querier.with_oracle_price(&[ - (&"uusd".to_string(), &Decimal::one()), - (&"asset0000".to_string(), &Decimal::percent(100)), - (&"asset0001".to_string(), &Decimal::percent(50)), - ]); - deps.querier.with_collateral_infos(&[( - &"asset0001".to_string(), - &Decimal::percent(50), - &Decimal::percent(200), // 2 collateral_multiplier - &false, - )]); - - let base_denom = "uusd".to_string(); - - let msg = InitMsg { - owner: HumanAddr::from("owner0000"), - oracle: HumanAddr::from("oracle0000"), - collector: HumanAddr::from("collector0000"), - collateral_oracle: HumanAddr::from("collateraloracle0000"), - staking: HumanAddr::from("staking0000"), - terraswap_factory: HumanAddr::from("terraswap_factory"), - lock: HumanAddr::from("lock0000"), - base_denom: base_denom.clone(), - token_code_id: TOKEN_CODE_ID, - protocol_fee_rate: Decimal::percent(1), - }; - - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset0000"), - auction_discount: Decimal::percent(20), - min_collateral_ratio: Decimal::percent(150), - ipo_params: None, - }; - - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset0001"), - auction_discount: Decimal::percent(20), - min_collateral_ratio: Decimal::percent(150), - ipo_params: None, - }; - - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // open position with unknown collateral - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - msg: Some( - to_binary(&Cw20HookMsg::OpenPosition { - asset_info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset9999"), - }, - collateral_ratio: Decimal::percent(150), - short_params: None, - }) - .unwrap(), - ), - sender: HumanAddr::from("addr0000"), - amount: Uint128(1000000u128), - }); - let env = mock_env_with_block_time("asset9999", &[], 1000); - let _res = handle(&mut deps, env, msg).unwrap_err(); // expect error - - // must fail; collateral ratio is too low - let msg = HandleMsg::OpenPosition { - collateral: Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: Uint128(1000000u128), - }, - asset_info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - collateral_ratio: Decimal::percent(140), - short_params: None, - }; - let env = mock_env_with_block_time( - "addr0000", - &[Coin { - denom: "uusd".to_string(), - amount: Uint128(1000000u128), - }], - 1000, - ); - let res = handle(&mut deps, env.clone(), msg).unwrap_err(); - match res { - StdError::GenericErr { msg, .. } => assert_eq!( - msg, - "Can not open a position with low collateral ratio than minimum" - ), - _ => panic!("DO NOT ENTER ERROR"), - } - - // successful attempt - let msg = HandleMsg::OpenPosition { - collateral: Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: Uint128(1000000u128), - }, - asset_info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - collateral_ratio: Decimal::percent(150), - short_params: None, - }; - let res = handle(&mut deps, env.clone(), msg).unwrap(); - - assert_eq!( - res.log, - vec![ - log("action", "open_position"), - log("position_idx", "1"), - log("mint_amount", "666666asset0000"), - log("collateral_amount", "1000000uusd"), - log("is_short", "false"), - ] - ); - - assert_eq!( - res.messages, - vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("asset0000"), - send: vec![], - msg: to_binary(&Cw20HandleMsg::Mint { - recipient: HumanAddr::from("addr0000"), - amount: Uint128(666666u128), - }) - .unwrap(), - })] - ); - - let res = query( - &deps, - QueryMsg::Position { - position_idx: Uint128(1u128), - }, - ) - .unwrap(); - let position: PositionResponse = from_binary(&res).unwrap(); - assert_eq!( - position, - PositionResponse { - idx: Uint128(1u128), - owner: HumanAddr::from("addr0000"), - asset: Asset { - info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - amount: Uint128(666666u128), - }, - collateral: Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: Uint128(1000000u128), - }, - is_short: false, - } - ); - - // can query positions - let res = query( - &deps, - QueryMsg::Positions { - owner_addr: Some(HumanAddr::from("addr0000")), - asset_token: None, - limit: None, - start_after: None, - order_by: Some(OrderBy::Asc), - }, - ) - .unwrap(); - let positions: PositionsResponse = from_binary(&res).unwrap(); - assert_eq!( - positions, - PositionsResponse { - positions: vec![PositionResponse { - idx: Uint128(1u128), - owner: HumanAddr::from("addr0000"), - asset: Asset { - info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - amount: Uint128(666666u128), - }, - collateral: Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: Uint128(1000000u128), - }, - is_short: false, - }], - } - ); - - // Cannot directly deposit token - let msg = HandleMsg::OpenPosition { - collateral: Asset { - info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0001"), - }, - amount: Uint128(1000000u128), - }, - asset_info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - collateral_ratio: Decimal::percent(150), - short_params: None, - }; - let res = handle(&mut deps, env, msg).unwrap_err(); - match res { - StdError::Unauthorized { .. } => (), - _ => panic!("DO NOT ENTER HERE"), - } - - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - msg: Some( - to_binary(&Cw20HookMsg::OpenPosition { - asset_info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - collateral_ratio: Decimal::percent(150), - short_params: None, - }) - .unwrap(), - ), - sender: HumanAddr::from("addr0000"), - amount: Uint128(1000000u128), - }); - let env = mock_env_with_block_time("asset0001", &[], 1000); - let res = handle(&mut deps, env, msg).unwrap(); - - assert_eq!( - res.log, - vec![ - log("action", "open_position"), - log("position_idx", "2"), - log("mint_amount", "166666asset0000"), // 1000000 * 0.5 (price to asset) * 0.5 multiplier / 1.5 (mcr) - log("collateral_amount", "1000000asset0001"), - log("is_short", "false"), - ] - ); - - assert_eq!( - res.messages, - vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("asset0000"), - send: vec![], - msg: to_binary(&Cw20HandleMsg::Mint { - recipient: HumanAddr::from("addr0000"), - amount: Uint128(166666u128), - }) - .unwrap(), - })] - ); - - let res = query( - &deps, - QueryMsg::Position { - position_idx: Uint128(2u128), - }, - ) - .unwrap(); - let position: PositionResponse = from_binary(&res).unwrap(); - assert_eq!( - position, - PositionResponse { - idx: Uint128(2u128), - owner: HumanAddr::from("addr0000"), - asset: Asset { - info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - amount: Uint128(166666u128), - }, - collateral: Asset { - info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0001"), - }, - amount: Uint128(1000000u128), - }, - is_short: false, - } - ); - - // can query positions - let res = query( - &deps, - QueryMsg::Positions { - owner_addr: Some(HumanAddr::from("addr0000")), - asset_token: None, - limit: None, - start_after: None, - order_by: Some(OrderBy::Desc), - }, - ) - .unwrap(); - let positions: PositionsResponse = from_binary(&res).unwrap(); - assert_eq!( - positions, - PositionsResponse { - positions: vec![ - PositionResponse { - idx: Uint128(2u128), - owner: HumanAddr::from("addr0000"), - asset: Asset { - info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - amount: Uint128(166666u128), - }, - collateral: Asset { - info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0001"), - }, - amount: Uint128(1000000u128), - }, - is_short: false, - }, - PositionResponse { - idx: Uint128(1u128), - owner: HumanAddr::from("addr0000"), - asset: Asset { - info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - amount: Uint128(666666u128), - }, - collateral: Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: Uint128(1000000u128), - }, - is_short: false, - } - ], - } - ); - - let res = query( - &deps, - QueryMsg::Positions { - owner_addr: Some(HumanAddr::from("addr0000")), - asset_token: None, - limit: None, - start_after: Some(Uint128(2u128)), - order_by: Some(OrderBy::Desc), - }, - ) - .unwrap(); - let positions: PositionsResponse = from_binary(&res).unwrap(); - assert_eq!( - positions, - PositionsResponse { - positions: vec![PositionResponse { - idx: Uint128(1u128), - owner: HumanAddr::from("addr0000"), - asset: Asset { - info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - amount: Uint128(666666u128), - }, - collateral: Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: Uint128(1000000u128), - }, - is_short: false, - }], - } - ); - } - - #[test] - fn deposit() { - let mut deps = mock_dependencies(20, &[]); - deps.querier.with_oracle_price(&[ - (&"uusd".to_string(), &Decimal::one()), - (&"asset0000".to_string(), &Decimal::percent(100)), - (&"asset0001".to_string(), &Decimal::percent(50)), - ]); - deps.querier.with_collateral_infos(&[( - &"asset0001".to_string(), - &Decimal::percent(50), - &Decimal::one(), - &false, - )]); - - let base_denom = "uusd".to_string(); - - let msg = InitMsg { - owner: HumanAddr::from("owner0000"), - oracle: HumanAddr::from("oracle0000"), - collector: HumanAddr::from("collector0000"), - collateral_oracle: HumanAddr::from("collateraloracle0000"), - staking: HumanAddr::from("staking0000"), - terraswap_factory: HumanAddr::from("terraswap_factory"), - lock: HumanAddr::from("lock0000"), - base_denom: base_denom.clone(), - token_code_id: TOKEN_CODE_ID, - protocol_fee_rate: Decimal::percent(1), - }; - - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset0000"), - auction_discount: Decimal::percent(20), - min_collateral_ratio: Decimal::percent(150), - ipo_params: None, - }; - - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset0001"), - auction_discount: Decimal::percent(20), - min_collateral_ratio: Decimal::percent(150), - ipo_params: None, - }; - - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // open uusd-asset0000 position - let msg = HandleMsg::OpenPosition { - collateral: Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: Uint128(1000000u128), - }, - asset_info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - collateral_ratio: Decimal::percent(150), - short_params: None, - }; - let env = mock_env_with_block_time( - "addr0000", - &[Coin { - denom: "uusd".to_string(), - amount: Uint128(1000000u128), - }], - 1000, - ); - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - - // open asset0001-asset0000 position - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - msg: Some( - to_binary(&Cw20HookMsg::OpenPosition { - asset_info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - collateral_ratio: Decimal::percent(150), - short_params: None, - }) - .unwrap(), - ), - sender: HumanAddr::from("addr0000"), - amount: Uint128(1000000u128), - }); - let env = mock_env_with_block_time("asset0001", &[], 1000); - let _res = handle(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::Deposit { - position_idx: Uint128(1u128), - collateral: Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: Uint128(1000000u128), - }, - }; - let env = mock_env( - "addr0000", - &[Coin { - denom: "uusd".to_string(), - amount: Uint128(1000000u128), - }], - ); - let _res = handle(&mut deps, env, msg).unwrap(); - let res = query( - &deps, - QueryMsg::Position { - position_idx: Uint128(1u128), - }, - ) - .unwrap(); - - let position: PositionResponse = from_binary(&res).unwrap(); - assert_eq!( - position, - PositionResponse { - idx: Uint128(1u128), - owner: HumanAddr::from("addr0000"), - asset: Asset { - info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - amount: Uint128(666666u128), - }, - collateral: Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: Uint128(2000000u128), - }, - is_short: false, - } - ); - - // unauthorized failed; must be executed from token contract - let msg = HandleMsg::Deposit { - position_idx: Uint128(2u128), - collateral: Asset { - info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0001"), - }, - amount: Uint128(1000000u128), - }, - }; - let env = mock_env("addr0000", &[]); - let res = handle(&mut deps, env, msg).unwrap_err(); - match res { - StdError::Unauthorized { .. } => {} - _ => panic!("Must return unauthorized error"), - } - - // deposit other token asset - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from("addr0000"), - amount: Uint128(1000000u128), - msg: Some( - to_binary(&Cw20HookMsg::Deposit { - position_idx: Uint128(2u128), - }) - .unwrap(), - ), - }); - - let env = mock_env("asset0001", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - let res = query( - &deps, - QueryMsg::Position { - position_idx: Uint128(2u128), - }, - ) - .unwrap(); - - let position: PositionResponse = from_binary(&res).unwrap(); - assert_eq!( - position, - PositionResponse { - idx: Uint128(2u128), - owner: HumanAddr::from("addr0000"), - asset: Asset { - info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - amount: Uint128(333333u128), - }, - collateral: Asset { - info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0001"), - }, - amount: Uint128(2000000u128), - }, - is_short: false, - } - ); - } - - #[test] - fn mint() { - let mut deps = mock_dependencies(20, &[]); - deps.querier.with_oracle_price(&[ - (&"uusd".to_string(), &Decimal::one()), - ( - &"asset0000".to_string(), - &Decimal::from_ratio(100u128, 1u128), - ), - ( - &"asset0001".to_string(), - &Decimal::from_ratio(50u128, 1u128), - ), - ]); - deps.querier.with_collateral_infos(&[( - &"asset0001".to_string(), - &Decimal::from_ratio(50u128, 1u128), - &Decimal::one(), - &false, - )]); - - let base_denom = "uusd".to_string(); - - let msg = InitMsg { - owner: HumanAddr::from("owner0000"), - oracle: HumanAddr::from("oracle0000"), - collector: HumanAddr::from("collector0000"), - collateral_oracle: HumanAddr::from("collateraloracle0000"), - staking: HumanAddr::from("staking0000"), - terraswap_factory: HumanAddr::from("terraswap_factory"), - lock: HumanAddr::from("lock0000"), - base_denom: base_denom.clone(), - token_code_id: TOKEN_CODE_ID, - protocol_fee_rate: Decimal::percent(1), - }; - - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset0000"), - auction_discount: Decimal::percent(20), - min_collateral_ratio: Decimal::percent(150), - ipo_params: None, - }; - - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset0001"), - auction_discount: Decimal::percent(20), - min_collateral_ratio: Decimal::percent(150), - ipo_params: None, - }; - - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // open uusd-asset0000 position - let msg = HandleMsg::OpenPosition { - collateral: Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: Uint128(1000000u128), - }, - asset_info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - collateral_ratio: Decimal::percent(150), - short_params: None, - }; - let env = mock_env_with_block_time( - "addr0000", - &[Coin { - denom: "uusd".to_string(), - amount: Uint128(1000000u128), - }], - 1000, - ); - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - - // open asset0001-asset0000 position - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - msg: Some( - to_binary(&Cw20HookMsg::OpenPosition { - asset_info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - collateral_ratio: Decimal::percent(150), - short_params: None, - }) - .unwrap(), - ), - sender: HumanAddr::from("addr0000"), - amount: Uint128(1000000u128), - }); - let env = mock_env_with_block_time("asset0001", &[], 1000); - let _res = handle(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::Deposit { - position_idx: Uint128(1u128), - collateral: Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: Uint128(1000000u128), - }, - }; - let env = mock_env( - "addr0000", - &[Coin { - denom: "uusd".to_string(), - amount: Uint128(1000000u128), - }], - ); - let _res = handle(&mut deps, env, msg).unwrap(); - - // deposit other token asset - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from("addr0000"), - amount: Uint128(1000000u128), - msg: Some( - to_binary(&Cw20HookMsg::Deposit { - position_idx: Uint128(2u128), - }) - .unwrap(), - ), - }); - - let env = mock_env("asset0001", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // failed to mint; due to min_collateral_ratio - // price 100, collateral 1000000, min_collateral_ratio 150% - // x * price * min_collateral_ratio < collateral - // x < collateral/(price*min_collateral_ratio) = 10000 / 1.5 - let msg = HandleMsg::Mint { - position_idx: Uint128(1u128), - asset: Asset { - info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - amount: Uint128(6668u128), - }, - short_params: None, - }; - let env = mock_env_with_block_time("addr0000", &[], 1000u64); - let res = handle(&mut deps, env, msg).unwrap_err(); - match res { - StdError::GenericErr { msg, .. } => { - assert_eq!(msg, "Cannot mint asset over than min collateral ratio") - } - _ => panic!("DO NOT ENTER HERE"), - } - - // successfully mint within the min_collateral_ratio - let msg = HandleMsg::Mint { - position_idx: Uint128(1u128), - asset: Asset { - info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - amount: Uint128(6667u128), - }, - short_params: None, - }; - let env = mock_env_with_block_time("addr0000", &[], 1000u64); - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!( - res.messages, - vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("asset0000"), - msg: to_binary(&Cw20HandleMsg::Mint { - amount: Uint128(6667u128), - recipient: HumanAddr::from("addr0000"), - }) - .unwrap(), - send: vec![], - })] - ); - assert_eq!( - res.log, - vec![ - log("action", "mint"), - log("position_idx", "1"), - log("mint_amount", "6667asset0000") - ] - ); - - // mint with other token; failed due to min collateral ratio - let msg = HandleMsg::Mint { - position_idx: Uint128(2u128), - asset: Asset { - info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - amount: Uint128(333334u128), - }, - short_params: None, - }; - let env = mock_env_with_block_time("addr0000", &[], 1000u64); - let res = handle(&mut deps, env, msg).unwrap_err(); - match res { - StdError::GenericErr { msg, .. } => { - assert_eq!(msg, "Cannot mint asset over than min collateral ratio") - } - _ => panic!("DO NOT ENTER HERE"), - } - - // mint with other token; - let msg = HandleMsg::Mint { - position_idx: Uint128(2u128), - asset: Asset { - info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - amount: Uint128(333333u128), - }, - short_params: None, - }; - let env = mock_env_with_block_time("addr0000", &[], 1000u64); - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!( - res.messages, - vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("asset0000"), - msg: to_binary(&Cw20HandleMsg::Mint { - amount: Uint128(333333u128), - recipient: HumanAddr::from("addr0000"), - }) - .unwrap(), - send: vec![], - })] - ); - assert_eq!( - res.log, - vec![ - log("action", "mint"), - log("position_idx", "2"), - log("mint_amount", "333333asset0000") - ] - ); - } - - #[test] - fn burn() { - let mut deps = mock_dependencies(20, &[]); - deps.querier.with_oracle_price(&[ - (&"uusd".to_string(), &Decimal::one()), - ( - &"asset0000".to_string(), - &Decimal::from_ratio(100u128, 1u128), - ), - ( - &"asset0001".to_string(), - &Decimal::from_ratio(50u128, 1u128), - ), - ]); - deps.querier.with_collateral_infos(&[( - &"asset0001".to_string(), - &Decimal::from_ratio(50u128, 1u128), - &Decimal::one(), - &false, - )]); - - let base_denom = "uusd".to_string(); - - let msg = InitMsg { - owner: HumanAddr::from("owner0000"), - oracle: HumanAddr::from("oracle0000"), - collector: HumanAddr::from("collector0000"), - collateral_oracle: HumanAddr::from("collateraloracle0000"), - staking: HumanAddr::from("staking0000"), - terraswap_factory: HumanAddr::from("terraswap_factory"), - lock: HumanAddr::from("lock0000"), - base_denom: base_denom.clone(), - token_code_id: TOKEN_CODE_ID, - protocol_fee_rate: Decimal::percent(1), - }; - - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset0000"), - auction_discount: Decimal::percent(20), - min_collateral_ratio: Decimal::percent(150), - ipo_params: None, - }; - - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset0001"), - auction_discount: Decimal::percent(20), - min_collateral_ratio: Decimal::percent(150), - ipo_params: None, - }; - - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // open uusd-asset0000 position - let msg = HandleMsg::OpenPosition { - collateral: Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: Uint128(1000000u128), - }, - asset_info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - collateral_ratio: Decimal::percent(150), - short_params: None, - }; - let env = mock_env_with_block_time( - "addr0000", - &[Coin { - denom: "uusd".to_string(), - amount: Uint128(1000000u128), - }], - 1000, - ); - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - - // open asset0001-asset0000 position - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - msg: Some( - to_binary(&Cw20HookMsg::OpenPosition { - asset_info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - collateral_ratio: Decimal::percent(150), - short_params: None, - }) - .unwrap(), - ), - sender: HumanAddr::from("addr0000"), - amount: Uint128(1000000u128), - }); - let env = mock_env_with_block_time("asset0001", &[], 1000); - let _res = handle(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::Deposit { - position_idx: Uint128(1u128), - collateral: Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: Uint128(1000000u128), - }, - }; - let env = mock_env( - "addr0000", - &[Coin { - denom: "uusd".to_string(), - amount: Uint128(1000000u128), - }], - ); - let _res = handle(&mut deps, env, msg).unwrap(); - - // deposit other token asset - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from("addr0000"), - amount: Uint128(1000000u128), - msg: Some( - to_binary(&Cw20HookMsg::Deposit { - position_idx: Uint128(2u128), - }) - .unwrap(), - ), - }); - - let env = mock_env("asset0001", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::Mint { - position_idx: Uint128(1u128), - asset: Asset { - info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - amount: Uint128(6667u128), - }, - short_params: None, - }; - let env = mock_env_with_block_time("addr0000", &[], 1000u64); - let _res = handle(&mut deps, env, msg).unwrap(); - - // failed to burn more than the position amount - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from("addr0000"), - amount: Uint128(13334u128), - msg: Some( - to_binary(&Cw20HookMsg::Burn { - position_idx: Uint128(1u128), - }) - .unwrap(), - ), - }); - let env = mock_env("asset0000", &[]); - let res = handle(&mut deps, env, msg).unwrap_err(); - match res { - StdError::GenericErr { msg, .. } => { - assert_eq!(msg, "Cannot burn asset more than you mint") - } - _ => panic!("DO NOT ENTER HERE"), - } - - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from("addr0000"), - amount: Uint128(13333u128), - msg: Some( - to_binary(&Cw20HookMsg::Burn { - position_idx: Uint128(1u128), - }) - .unwrap(), - ), - }); - let env = mock_env("asset0000", &[]); - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!( - res.log, - vec![ - log("action", "burn"), - log("position_idx", "1"), - log("burn_amount", "13333asset0000"), - ] - ); - assert_eq!( - res.messages, - vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("asset0000"), - msg: to_binary(&Cw20HandleMsg::Burn { - amount: Uint128(13333u128), - }) - .unwrap(), - send: vec![], - })] - ); - - // mint other asset - let msg = HandleMsg::Mint { - position_idx: Uint128(2u128), - asset: Asset { - info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - amount: Uint128(333333u128), - }, - short_params: None, - }; - let env = mock_env_with_block_time("addr0000", &[], 1000u64); - let _res = handle(&mut deps, env, msg).unwrap(); - - // failed to burn more than the position amount - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from("addr0000"), - amount: Uint128(666667u128), - msg: Some( - to_binary(&Cw20HookMsg::Burn { - position_idx: Uint128(2u128), - }) - .unwrap(), - ), - }); - let env = mock_env("asset0000", &[]); - let res = handle(&mut deps, env, msg).unwrap_err(); - match res { - StdError::GenericErr { msg, .. } => { - assert_eq!(msg, "Cannot burn asset more than you mint") - } - _ => panic!("DO NOT ENTER HERE"), - } - - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from("addr0000"), - amount: Uint128(666666u128), - msg: Some( - to_binary(&Cw20HookMsg::Burn { - position_idx: Uint128(2u128), - }) - .unwrap(), - ), - }); - let env = mock_env("asset0000", &[]); - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!( - res.log, - vec![ - log("action", "burn"), - log("position_idx", "2"), - log("burn_amount", "666666asset0000"), - ] - ); - assert_eq!( - res.messages, - vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("asset0000"), - msg: to_binary(&Cw20HandleMsg::Burn { - amount: Uint128(666666u128), - }) - .unwrap(), - send: vec![], - })] - ); - } - - #[test] - fn withdraw() { - let mut deps = mock_dependencies(20, &[]); - deps.querier.with_tax( - Decimal::percent(1), - &[(&"uusd".to_string(), &Uint128(1000000u128))], - ); - deps.querier.with_oracle_price(&[ - (&"uusd".to_string(), &Decimal::one()), - ( - &"asset0000".to_string(), - &Decimal::from_ratio(100u128, 1u128), - ), - ( - &"asset0001".to_string(), - &Decimal::from_ratio(50u128, 1u128), - ), - ]); - deps.querier.with_collateral_infos(&[( - &"asset0001".to_string(), - &Decimal::from_ratio(50u128, 1u128), - &Decimal::one(), - &false, - )]); - - let base_denom = "uusd".to_string(); - - let msg = InitMsg { - owner: HumanAddr::from("owner0000"), - oracle: HumanAddr::from("oracle0000"), - collector: HumanAddr::from("collector0000"), - collateral_oracle: HumanAddr::from("collateraloracle0000"), - staking: HumanAddr::from("staking0000"), - terraswap_factory: HumanAddr::from("terraswap_factory"), - lock: HumanAddr::from("lock0000"), - base_denom: base_denom.clone(), - token_code_id: TOKEN_CODE_ID, - protocol_fee_rate: Decimal::percent(1), - }; - - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset0000"), - auction_discount: Decimal::percent(20), - min_collateral_ratio: Decimal::percent(150), - ipo_params: None, - }; - - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset0001"), - auction_discount: Decimal::percent(20), - min_collateral_ratio: Decimal::percent(150), - ipo_params: None, - }; - - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // open uusd-asset0000 position - let msg = HandleMsg::OpenPosition { - collateral: Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: Uint128(1000000u128), - }, - asset_info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - collateral_ratio: Decimal::percent(150), - short_params: None, - }; - let env = mock_env_with_block_time( - "addr0000", - &[Coin { - denom: "uusd".to_string(), - amount: Uint128(1000000u128), - }], - 1000, - ); - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - - // open asset0001-asset0000 position - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - msg: Some( - to_binary(&Cw20HookMsg::OpenPosition { - asset_info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - collateral_ratio: Decimal::percent(150), - short_params: None, - }) - .unwrap(), - ), - sender: HumanAddr::from("addr0000"), - amount: Uint128(1000000u128), - }); - let env = mock_env_with_block_time("asset0001", &[], 1000); - let _res = handle(&mut deps, env, msg).unwrap(); - - // cannot withdraw more than (100 collateral token == 1 token) - // due to min collateral ratio - let msg = HandleMsg::Withdraw { - position_idx: Uint128(1u128), - collateral: Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: Uint128(101u128), - }, - }; - let env = mock_env_with_block_time("addr0000", &[], 1000u64); - let res = handle(&mut deps, env, msg).unwrap_err(); - match res { - StdError::GenericErr { msg, .. } => assert_eq!( - msg, - "Cannot withdraw collateral over than minimum collateral ratio" - ), - _ => panic!("DO NOT ENTER HERE"), - } - - let msg = HandleMsg::Withdraw { - position_idx: Uint128(1u128), - collateral: Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: Uint128(100u128), - }, - }; - let env = mock_env_with_block_time("addr0000", &[], 1000u64); - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!( - res.log, - vec![ - log("action", "withdraw"), - log("position_idx", "1"), - log("withdraw_amount", "99uusd"), - log("tax_amount", "1uusd"), - log("protocol_fee", "1uusd"), - ] - ); - - // cannot withdraw more than (2 collateral token == 1 token) - // due to min collateral ratio - let msg = HandleMsg::Withdraw { - position_idx: Uint128(2u128), - collateral: Asset { - info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0001"), - }, - amount: Uint128(2u128), - }, - }; - let env = mock_env_with_block_time("addr0000", &[], 1000u64); - let res = handle(&mut deps, env, msg).unwrap_err(); - match res { - StdError::GenericErr { msg, .. } => assert_eq!( - msg, - "Cannot withdraw collateral over than minimum collateral ratio" - ), - _ => panic!("DO NOT ENTER HERE"), - } - - let msg = HandleMsg::Withdraw { - position_idx: Uint128(2u128), - collateral: Asset { - info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0001"), - }, - amount: Uint128(1u128), - }, - }; - let env = mock_env_with_block_time("addr0000", &[], 1000u64); - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!( - res.log, - vec![ - log("action", "withdraw"), - log("position_idx", "2"), - log("withdraw_amount", "1asset0001"), - log("tax_amount", "0asset0001"), - log("protocol_fee", "0asset0001"), - ] - ); - } - - #[test] - fn auction() { - let mut deps = mock_dependencies(20, &[]); - deps.querier.with_tax( - Decimal::percent(5u64), - &[(&"uusd".to_string(), &Uint128(1000000u128))], - ); - deps.querier.with_oracle_price(&[ - (&"uusd".to_string(), &Decimal::one()), - ( - &"asset0000".to_string(), - &Decimal::from_ratio(100u128, 1u128), - ), - ( - &"asset0001".to_string(), - &Decimal::from_ratio(50u128, 1u128), - ), - ]); - deps.querier.with_collateral_infos(&[ - ( - &"asset0000".to_string(), - &Decimal::from_ratio(100u128, 1u128), - &Decimal::one(), - &false, - ), - ( - &"asset0001".to_string(), - &Decimal::from_ratio(50u128, 1u128), - &Decimal::one(), - &false, - ), - ]); - - let base_denom = "uusd".to_string(); - - let msg = InitMsg { - owner: HumanAddr::from("owner0000"), - oracle: HumanAddr::from("oracle0000"), - collector: HumanAddr::from("collector0000"), - collateral_oracle: HumanAddr::from("collateraloracle0000"), - staking: HumanAddr::from("staking0000"), - terraswap_factory: HumanAddr::from("terraswap_factory"), - lock: HumanAddr::from("lock0000"), - base_denom: base_denom.clone(), - token_code_id: TOKEN_CODE_ID, - protocol_fee_rate: Decimal::percent(1), - }; - - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset0000"), - auction_discount: Decimal::percent(20), - min_collateral_ratio: Decimal::percent(130), - ipo_params: None, - }; - - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset0001"), - auction_discount: Decimal::percent(20), - min_collateral_ratio: Decimal::percent(150), - ipo_params: None, - }; - - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // open uusd-asset0000 position - let msg = HandleMsg::OpenPosition { - collateral: Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: Uint128(1000000u128), - }, - asset_info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - collateral_ratio: Decimal::percent(150), - short_params: None, - }; - let env = mock_env_with_block_time( - "addr0000", - &[Coin { - denom: "uusd".to_string(), - amount: Uint128(1000000u128), - }], - 1000, - ); - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - - // open asset0001-asset0000 position - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - msg: Some( - to_binary(&Cw20HookMsg::OpenPosition { - asset_info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - collateral_ratio: Decimal::percent(150), - short_params: None, - }) - .unwrap(), - ), - sender: HumanAddr::from("addr0000"), - amount: Uint128(1000000u128), - }); - let env = mock_env_with_block_time("asset0001", &[], 1000); - let _res = handle(&mut deps, env, msg).unwrap(); - - deps.querier.with_oracle_price(&[ - (&"uusd".to_string(), &Decimal::one()), - ( - &"asset0000".to_string(), - &Decimal::from_ratio(115u128, 1u128), - ), - ( - &"asset0001".to_string(), - &Decimal::from_ratio(50u128, 1u128), - ), - ]); - - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from("addr0001"), - amount: Uint128(1u128), - msg: Some( - to_binary(&Cw20HookMsg::Auction { - position_idx: Uint128(1u128), - }) - .unwrap(), - ), - }); - - let env = mock_env_with_block_time("asset0000", &[], 1000u64); - let res = handle(&mut deps, env, msg).unwrap_err(); - match res { - StdError::GenericErr { msg, .. } => { - assert_eq!(msg, "Cannot liquidate a safely collateralized position") - } - _ => panic!("DO NOT ENTER HERE"), - } - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from("addr0001"), - amount: Uint128(1u128), - msg: Some( - to_binary(&Cw20HookMsg::Auction { - position_idx: Uint128(1u128), - }) - .unwrap(), - ), - }); - - let env = mock_env_with_block_time("asset0000", &[], 1000u64); - let res = handle(&mut deps, env, msg).unwrap_err(); - match res { - StdError::GenericErr { msg, .. } => { - assert_eq!(msg, "Cannot liquidate a safely collateralized position") - } - _ => panic!("DO NOT ENTER HERE"), - } - - deps.querier.with_oracle_price(&[ - (&"uusd".to_string(), &Decimal::one()), - ( - &"asset0000".to_string(), - &Decimal::from_ratio(116u128, 1u128), - ), - ( - &"asset0001".to_string(), - &Decimal::from_ratio(50u128, 1u128), - ), - ]); - deps.querier.with_collateral_infos(&[ - ( - &"asset0000".to_string(), - &Decimal::from_ratio(116u128, 1u128), - &Decimal::one(), - &false, - ), - ( - &"asset0001".to_string(), - &Decimal::from_ratio(50u128, 1u128), - &Decimal::one(), - &false, - ), - ]); - - // auction failed; liquidation amount is bigger than position amount - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from("addr0001"), - amount: Uint128(6667u128), - msg: Some( - to_binary(&Cw20HookMsg::Auction { - position_idx: Uint128(1u128), - }) - .unwrap(), - ), - }); - - let env = mock_env_with_block_time("asset0000", &[], 1000u64); - let res = handle(&mut deps, env, msg).unwrap_err(); - match res { - StdError::GenericErr { msg, .. } => { - assert_eq!(msg, "Cannot liquidate more than the position amount") - } - _ => panic!("DO NOT ENTER HERE"), - } - - // auction failed; liquidation amount is bigger than position amount - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from("addr0001"), - amount: Uint128(333334u128), - msg: Some( - to_binary(&Cw20HookMsg::Auction { - position_idx: Uint128(2u128), - }) - .unwrap(), - ), - }); - - let env = mock_env_with_block_time("asset0000", &[], 1000u64); - let res = handle(&mut deps, env, msg).unwrap_err(); - match res { - StdError::GenericErr { msg, .. } => { - assert_eq!(msg, "Cannot liquidate more than the position amount") - } - _ => panic!("DO NOT ENTER HERE"), - } - - // auction success - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from("addr0001"), - amount: Uint128(6666u128), - msg: Some( - to_binary(&Cw20HookMsg::Auction { - position_idx: Uint128(1u128), - }) - .unwrap(), - ), - }); - - let env = mock_env_with_block_time("asset0000", &[], 1000u64); - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!( - res.messages, - vec![ - CosmosMsg::Bank(BankMsg::Send { - from_address: HumanAddr::from(MOCK_CONTRACT_ADDR), - to_address: HumanAddr::from("addr0000"), - amount: vec![Coin { - denom: "uusd".to_string(), - amount: Uint128(31838u128) // Tax (5%) 33430 * 1 / 1.05 -> 31838 - }], - }), - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("asset0000"), - msg: to_binary(&Cw20HandleMsg::Burn { - amount: Uint128(6666u128), - }) - .unwrap(), - send: vec![], - }), - CosmosMsg::Bank(BankMsg::Send { - from_address: HumanAddr::from(MOCK_CONTRACT_ADDR), - to_address: HumanAddr::from("addr0001"), - amount: vec![Coin { - denom: "uusd".to_string(), - // Origin: 966,570 - // ProtocolFee(1%): -9,665 - // Tax(5%): -45,567 - amount: Uint128(911338u128) - }], - }), - CosmosMsg::Bank(BankMsg::Send { - from_address: HumanAddr::from(MOCK_CONTRACT_ADDR), - to_address: HumanAddr::from("collector0000"), - amount: vec![Coin { - denom: "uusd".to_string(), - // Origin: 9,665 - // Tax(5%): -461 - amount: Uint128(9204u128) - }] - }) - ], - ); - assert_eq!( - res.log, - vec![ - log("action", "auction"), - log("position_idx", "1"), - log("owner", "addr0000"), - log("return_collateral_amount", "956905uusd"), - log("liquidated_amount", "6666asset0000"), - log("tax_amount", "45567uusd"), - log("protocol_fee", "9665uusd"), - ] - ); - - // If the price goes too high, the return collateral amount - // must be capped to position's collateral amount - deps.querier.with_oracle_price(&[ - (&"uusd".to_string(), &Decimal::one()), - (&"asset0000".to_string(), &Decimal::percent(200)), - (&"asset0001".to_string(), &Decimal::percent(50)), - ]); - deps.querier.with_collateral_infos(&[ - ( - &"asset0000".to_string(), - &Decimal::percent(200), - &Decimal::one(), - &false, - ), - ( - &"asset0001".to_string(), - &Decimal::percent(50), - &Decimal::one(), - &false, - ), - ]); - - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from("addr0001"), - amount: Uint128(210000u128), - msg: Some( - to_binary(&Cw20HookMsg::Auction { - position_idx: Uint128(2u128), - }) - .unwrap(), - ), - }); - - let env = mock_env_with_block_time("asset0000", &[], 1000u64); - let res = handle(&mut deps, env, msg).unwrap(); - // cap to collateral amount - // required_asset_amount = 1000000 * 50 * 0.8 / 200 = 200000 - // refund_asset_amount = 10000 - assert_eq!( - res.messages, - vec![ - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("asset0000"), - msg: to_binary(&Cw20HandleMsg::Transfer { - recipient: HumanAddr::from("addr0001"), - amount: Uint128(10000u128), - }) - .unwrap(), - send: vec![], - }), - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("asset0000"), - msg: to_binary(&Cw20HandleMsg::Burn { - amount: Uint128(200000u128), - }) - .unwrap(), - send: vec![], - }), - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("asset0001"), - msg: to_binary(&Cw20HandleMsg::Transfer { - recipient: HumanAddr::from("addr0001"), - amount: Uint128(990000u128), // protocol fee = 200000 * 0.01 = 2000 - }) - .unwrap(), - send: vec![], - }), - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("asset0001"), - msg: to_binary(&Cw20HandleMsg::Transfer { - recipient: HumanAddr::from("collector0000"), - amount: Uint128(10000u128), - }) - .unwrap(), - send: vec![], - }) - ], - ); - assert_eq!( - res.log, - vec![ - log("action", "auction"), - log("position_idx", "2"), - log("owner", "addr0000"), - log("return_collateral_amount", "990000asset0001"), - log("liquidated_amount", "200000asset0000"), - log("tax_amount", "0asset0001"), - log("protocol_fee", "10000asset0001"), - ] - ); - } -} diff --git a/contracts/mirror_mint/src/pre_ipo_test.rs b/contracts/mirror_mint/src/pre_ipo_test.rs deleted file mode 100644 index a232c83ad..000000000 --- a/contracts/mirror_mint/src/pre_ipo_test.rs +++ /dev/null @@ -1,328 +0,0 @@ -#[cfg(test)] -mod tests { - use crate::contract::{handle, init, query}; - use crate::mock_querier::mock_dependencies; - use cosmwasm_std::testing::mock_env; - use cosmwasm_std::{ - from_binary, log, to_binary, BlockInfo, Coin, CosmosMsg, Decimal, Env, HumanAddr, StdError, - Uint128, WasmMsg, WasmQuery, - }; - use cw20::Cw20ReceiveMsg; - use mirror_protocol::collateral_oracle::{HandleMsg::RegisterCollateralAsset, SourceType}; - use mirror_protocol::mint::{ - AssetConfigResponse, Cw20HookMsg, HandleMsg, IPOParams, InitMsg, QueryMsg, - }; - use mirror_protocol::oracle::QueryMsg::Price; - use terraswap::asset::{Asset, AssetInfo}; - - static TOKEN_CODE_ID: u64 = 10u64; - fn mock_env_with_block_time>(sender: U, sent: &[Coin], time: u64) -> Env { - let env = mock_env(sender, sent); - // register time - return Env { - block: BlockInfo { - height: 1, - time, - chain_id: "columbus".to_string(), - }, - ..env - }; - } - - #[test] - fn pre_ipo_assets() { - let mut deps = mock_dependencies(20, &[]); - deps.querier.with_oracle_price(&[ - (&"uusd".to_string(), &Decimal::one()), - ( - &"preIPOAsset0000".to_string(), - &Decimal::from_ratio(10u128, 1u128), - ), - ]); - deps.querier.with_oracle_feeders(&[( - &HumanAddr::from("preIPOAsset0000"), - &HumanAddr::from("feeder0000"), - )]); - - let base_denom = "uusd".to_string(); - - let msg = InitMsg { - owner: HumanAddr::from("owner0000"), - oracle: HumanAddr::from("oracle0000"), - collector: HumanAddr::from("collector0000"), - staking: HumanAddr::from("staking0000"), - collateral_oracle: HumanAddr::from("collateraloracle0000"), - terraswap_factory: HumanAddr::from("terraswap_factory"), - lock: HumanAddr::from("lock0000"), - base_denom: base_denom.clone(), - token_code_id: TOKEN_CODE_ID, - protocol_fee_rate: Decimal::percent(1), - }; - let creator_env = mock_env("addr0000", &[]); - let _res = init(&mut deps, creator_env.clone(), msg).unwrap(); - - // register preIPO asset with mint_end parameter (10 blocks) - let mint_end = creator_env.clone().block.time + 10u64; - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("preIPOAsset0000"), - auction_discount: Decimal::percent(20), - min_collateral_ratio: Decimal::percent(1000), - ipo_params: Some(IPOParams { - mint_end, - min_collateral_ratio_after_ipo: Decimal::percent(150), - pre_ipo_price: Decimal::percent(100), - }), - }; - let env = mock_env("owner0000", &[]); - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!(res.messages.len(), 0); // not registered as collateral - - /////////////////// - // Minting phase - /////////////////// - let mut current_time = creator_env.block.time + 1; - - // open position successfully at creation_time + 1 - let msg = HandleMsg::OpenPosition { - collateral: Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: Uint128(2000000000u128), - }, - asset_info: AssetInfo::Token { - contract_addr: HumanAddr::from("preIPOAsset0000"), - }, - collateral_ratio: Decimal::percent(2000), - short_params: None, - }; - let env = mock_env_with_block_time( - "addr0000", - &[Coin { - denom: "uusd".to_string(), - amount: Uint128(2000000000u128), - }], - current_time, - ); - let res = handle(&mut deps, env.clone(), msg).unwrap(); - assert_eq!( - res.log, - vec![ - log("action", "open_position"), - log("position_idx", "1"), - log("mint_amount", "100000000preIPOAsset0000"), // 2000% cr with pre_ipo_price=1 - log("collateral_amount", "2000000000uusd"), - log("is_short", "false"), - ] - ); - - // mint successfully at creation_time + 1 - let msg = HandleMsg::Mint { - position_idx: Uint128(1u128), - asset: Asset { - info: AssetInfo::Token { - contract_addr: HumanAddr::from("preIPOAsset0000"), - }, - amount: Uint128(2000000u128), - }, - short_params: None, - }; - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - - // burn successfully at creation_time + 1 - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from("addr0000"), - amount: Uint128::from(1000000u128), - msg: Some( - to_binary(&Cw20HookMsg::Burn { - position_idx: Uint128(1u128), - }) - .unwrap(), - ), - }); - let env = mock_env_with_block_time("preIPOAsset0000", &[], current_time); - let _res = handle(&mut deps, env, msg).unwrap(); - - /////////////////// - // Trading phase - /////////////////// - current_time = creator_env.block.time + 11; // > mint_end - - // open position disabled - let msg = HandleMsg::OpenPosition { - collateral: Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: Uint128(1000000000u128), - }, - asset_info: AssetInfo::Token { - contract_addr: HumanAddr::from("preIPOAsset0000"), - }, - collateral_ratio: Decimal::percent(10000), - short_params: None, - }; - let env = mock_env_with_block_time( - "addr0000", - &[Coin { - denom: "uusd".to_string(), - amount: Uint128(1000000000u128), - }], - current_time, - ); - let res = handle(&mut deps, env.clone(), msg).unwrap_err(); - assert_eq!( - res, - StdError::generic_err(format!( - "The minting period for this asset ended at time {}", - mint_end - )) - ); - - // mint disabled - let msg = HandleMsg::Mint { - position_idx: Uint128(1u128), - asset: Asset { - info: AssetInfo::Token { - contract_addr: HumanAddr::from("preIPOAsset0000"), - }, - amount: Uint128(2000000u128), - }, - short_params: None, - }; - let res = handle(&mut deps, env.clone(), msg).unwrap_err(); - assert_eq!( - res, - StdError::generic_err(format!( - "The minting period for this asset ended at time {}", - mint_end - )) - ); - - // burn disabled - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from("addr0000"), - amount: Uint128::from(1000000u128), - msg: Some( - to_binary(&Cw20HookMsg::Burn { - position_idx: Uint128(1u128), - }) - .unwrap(), - ), - }); - let env = mock_env_with_block_time("preIPOAsset0000", &[], current_time); - let res = handle(&mut deps, env, msg).unwrap_err(); - assert_eq!( - res, - StdError::generic_err(format!( - "Burning is disabled for assets with limitied minting time. Mint period ended at time {}", - mint_end - )) - ); - - /////////////////// - // IPO/Migration - /////////////////// - current_time = creator_env.block.time + 20; - - // register migration initiated by the feeder - let msg = HandleMsg::TriggerIPO { - asset_token: HumanAddr::from("preIPOAsset0000"), - }; - - // unauthorized attempt - let env = mock_env("owner", &[]); - let res = handle(&mut deps, env, msg.clone()).unwrap_err(); - assert_eq!(res, StdError::unauthorized()); - - // succesfull attempt - let env = mock_env_with_block_time("feeder0000", &[], current_time); - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!( - res.log, - vec![ - log("action", "trigger_ipo"), - log("asset_token", "preIPOAsset0000"), - ] - ); - assert_eq!( - res.messages, - vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("collateraloracle0000"), - send: vec![], - msg: to_binary(&RegisterCollateralAsset { - asset: AssetInfo::Token { - contract_addr: HumanAddr::from("preIPOAsset0000"), - }, - multiplier: Decimal::one(), - price_source: SourceType::TerraOracle { - terra_oracle_query: to_binary(&WasmQuery::Smart { - contract_addr: HumanAddr::from("oracle0000"), - msg: to_binary(&Price { - base_asset: "uusd".to_string(), - quote_asset: "preIPOAsset0000".to_string(), - }) - .unwrap() - }) - .unwrap(), - }, - }) - .unwrap(), - })] - ); - - let res = query( - &deps, - QueryMsg::AssetConfig { - asset_token: HumanAddr::from("preIPOAsset0000"), - }, - ) - .unwrap(); - let asset_config_res: AssetConfigResponse = from_binary(&res).unwrap(); - // traditional asset configuration, feeder feeds real price - assert_eq!( - asset_config_res, - AssetConfigResponse { - token: HumanAddr::from("preIPOAsset0000"), - auction_discount: Decimal::percent(20), - min_collateral_ratio: Decimal::percent(150), - end_price: None, - ipo_params: None, - } - ); - - // open position as a postIPO asset - let msg = HandleMsg::OpenPosition { - collateral: Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: Uint128(9000u128), - }, - asset_info: AssetInfo::Token { - contract_addr: HumanAddr::from("preIPOAsset0000"), - }, - collateral_ratio: Decimal::percent(150), // new minCR - short_params: None, - }; - let env = mock_env_with_block_time( - "addr0000", - &[Coin { - denom: "uusd".to_string(), - amount: Uint128(9000u128), - }], - 1000u64, - ); - let res = handle(&mut deps, env.clone(), msg).unwrap(); - assert_eq!( - res.log, - vec![ - log("action", "open_position"), - log("position_idx", "2"), - log("mint_amount", "599preIPOAsset0000"), // 150% cr with oracle_price=10 - log("collateral_amount", "9000uusd"), - log("is_short", "false"), - ] - ); - } -} diff --git a/contracts/mirror_mint/src/querier.rs b/contracts/mirror_mint/src/querier.rs deleted file mode 100644 index 4f64422bf..000000000 --- a/contracts/mirror_mint/src/querier.rs +++ /dev/null @@ -1,159 +0,0 @@ -use cosmwasm_std::{ - from_binary, to_binary, Api, Binary, CanonicalAddr, Decimal, Extern, HumanAddr, Querier, - QueryRequest, StdError, StdResult, Storage, WasmQuery, -}; -use cosmwasm_storage::to_length_prefixed; - -use crate::state::{read_config, read_fixed_price, Config}; -use mirror_protocol::collateral_oracle::{ - CollateralPriceResponse, QueryMsg as CollateralOracleQueryMsg, -}; -use mirror_protocol::oracle::{PriceResponse, QueryMsg as OracleQueryMsg}; -use terraswap::asset::AssetInfoRaw; - -const PRICE_EXPIRE_TIME: u64 = 60; - -pub fn load_oracle_feeder( - deps: &Extern, - contract_addr: &HumanAddr, - asset_token: &CanonicalAddr, -) -> StdResult { - let res: StdResult = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Raw { - contract_addr: HumanAddr::from(contract_addr), - key: Binary::from(concat( - &to_length_prefixed(b"feeder"), - asset_token.as_slice(), - )), - })); - - let res = match res { - Ok(v) => v, - Err(_) => { - return Err(StdError::generic_err("Falied to fetch the oracle feeder")); - } - }; - - let feeder: StdResult = from_binary(&res); - let feeder: CanonicalAddr = match feeder { - Ok(v) => v, - Err(_) => { - return Err(StdError::generic_err("Falied to fetch the oracle feeder")); - } - }; - - Ok(feeder) -} - -pub fn load_asset_price( - deps: &Extern, - oracle: &HumanAddr, - asset: &AssetInfoRaw, - block_time: Option, -) -> StdResult { - let config: Config = read_config(&deps.storage)?; - - // check if the asset has a stored end_price or pre_ipo_price - let stored_price = read_fixed_price(&deps.storage, &asset); - - let price: Decimal = if let Some(stored_price) = stored_price { - stored_price - } else { - let asset_denom: String = (asset.to_normal(&deps)?).to_string(); - if asset_denom == config.base_denom { - Decimal::one() - } else { - // fetch price from oracle - query_price(deps, oracle, asset_denom, config.base_denom, block_time)? - } - }; - - Ok(price) -} - -pub fn load_collateral_info( - deps: &Extern, - collateral_oracle: &HumanAddr, - collateral: &AssetInfoRaw, - block_time: Option, -) -> StdResult<(Decimal, Decimal, bool)> { - let config: Config = read_config(&deps.storage)?; - let collateral_denom: String = (collateral.to_normal(&deps)?).to_string(); - - // base collateral - if collateral_denom == config.base_denom { - return Ok((Decimal::one(), Decimal::one(), false)); - } - - // check if the collateral is a revoked mAsset, will ignore pre_ipo_price since all preIPO assets are not whitelisted in collateral oracle - let end_price = read_fixed_price(&deps.storage, &collateral); - - if let Some(end_price) = end_price { - // load collateral_multiplier from collateral oracle - // if asset is revoked, no need to check for old price - let (_, collateral_multiplier, _) = - query_collateral(deps, collateral_oracle, collateral_denom, None)?; - - Ok((end_price, collateral_multiplier, true)) - } else { - // load collateral info from collateral oracle - let (collateral_oracle_price, collateral_multiplier, is_revoked) = - query_collateral(deps, collateral_oracle, collateral_denom, block_time)?; - - Ok((collateral_oracle_price, collateral_multiplier, is_revoked)) - } -} - -pub fn query_price( - deps: &Extern, - oracle: &HumanAddr, - base_asset: String, - quote_asset: String, - block_time: Option, -) -> StdResult { - let res: PriceResponse = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: HumanAddr::from(oracle), - msg: to_binary(&OracleQueryMsg::Price { - base_asset, - quote_asset, - })?, - }))?; - - if let Some(block_time) = block_time { - if res.last_updated_base < (block_time - PRICE_EXPIRE_TIME) - || res.last_updated_quote < (block_time - PRICE_EXPIRE_TIME) - { - return Err(StdError::generic_err("Price is too old")); - } - } - - Ok(res.rate) -} - -// queries the collateral oracle to get the asset rate and multiplier -pub fn query_collateral( - deps: &Extern, - collateral_oracle: &HumanAddr, - asset: String, - block_time: Option, -) -> StdResult<(Decimal, Decimal, bool)> { - let res: CollateralPriceResponse = - deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: HumanAddr::from(collateral_oracle), - msg: to_binary(&CollateralOracleQueryMsg::CollateralPrice { asset })?, - }))?; - - if let Some(block_time) = block_time { - if res.last_updated < (block_time - PRICE_EXPIRE_TIME) { - return Err(StdError::generic_err("Collateral price is too old")); - } - } - - Ok((res.rate, res.multiplier, res.is_revoked)) -} - -#[inline] -fn concat(namespace: &[u8], key: &[u8]) -> Vec { - let mut k = namespace.to_vec(); - k.extend_from_slice(key); - k -} diff --git a/contracts/mirror_mint/src/short_test.rs b/contracts/mirror_mint/src/short_test.rs deleted file mode 100644 index 9f0ab8c2f..000000000 --- a/contracts/mirror_mint/src/short_test.rs +++ /dev/null @@ -1,757 +0,0 @@ -#[cfg(test)] -mod test { - use crate::contract::{handle, init, query}; - use crate::mock_querier::mock_dependencies; - use cosmwasm_std::testing::{mock_env, MOCK_CONTRACT_ADDR}; - use cosmwasm_std::{ - from_binary, log, to_binary, BankMsg, BlockInfo, Coin, CosmosMsg, Decimal, Env, HumanAddr, - Uint128, WasmMsg, - }; - use cw20::{Cw20HandleMsg, Cw20ReceiveMsg}; - use mirror_protocol::lock::HandleMsg as LockHandleMsg; - use mirror_protocol::mint::{ - Cw20HookMsg, HandleMsg, InitMsg, PositionResponse, QueryMsg, ShortParams, - }; - use mirror_protocol::staking::HandleMsg as StakingHandleMsg; - use terraswap::{ - asset::{Asset, AssetInfo}, - pair::Cw20HookMsg as PairCw20HookMsg, - }; - - static TOKEN_CODE_ID: u64 = 10u64; - fn mock_env_with_block_time>(sender: U, sent: &[Coin], time: u64) -> Env { - let env = mock_env(sender, sent); - // register time - return Env { - block: BlockInfo { - height: 1, - time, - chain_id: "columbus".to_string(), - }, - ..env - }; - } - - #[test] - fn open_short_position() { - let mut deps = mock_dependencies(20, &[]); - deps.querier.with_oracle_price(&[ - (&"uusd".to_string(), &Decimal::one()), - (&"asset0000".to_string(), &Decimal::percent(100)), - (&"asset0001".to_string(), &Decimal::percent(50)), - ]); - - let base_denom = "uusd".to_string(); - - let msg = InitMsg { - owner: HumanAddr::from("owner0000"), - oracle: HumanAddr::from("oracle0000"), - collector: HumanAddr::from("collector0000"), - collateral_oracle: HumanAddr::from("collateraloracle0000"), - staking: HumanAddr::from("staking0000"), - terraswap_factory: HumanAddr::from("terraswap_factory"), - lock: HumanAddr::from("lock0000"), - base_denom: base_denom.clone(), - token_code_id: TOKEN_CODE_ID, - protocol_fee_rate: Decimal::percent(1), - }; - - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset0000"), - auction_discount: Decimal::percent(20), - min_collateral_ratio: Decimal::percent(150), - ipo_params: None, - }; - - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset0001"), - auction_discount: Decimal::percent(20), - min_collateral_ratio: Decimal::percent(150), - ipo_params: None, - }; - - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // register terraswap pair - deps.querier.with_terraswap_pair(&[( - &"uusd".to_string(), - &"asset0000".to_string(), - &HumanAddr::from("pair0000"), - )]); - - let msg = HandleMsg::OpenPosition { - collateral: Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: Uint128(1000000u128), - }, - asset_info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - collateral_ratio: Decimal::percent(150), - short_params: Some(ShortParams { - belief_price: None, - max_spread: None, - }), - }; - - let env = mock_env_with_block_time( - "addr0000", - &[Coin { - denom: "uusd".to_string(), - amount: Uint128(1000000u128), - }], - 1000, - ); - let res = handle(&mut deps, env.clone(), msg).unwrap(); - - assert_eq!( - res.log, - vec![ - log("action", "open_position"), - log("position_idx", "1"), - log("mint_amount", "666666asset0000"), - log("collateral_amount", "1000000uusd"), - log("is_short", "true"), - ] - ); - - assert_eq!( - res.messages, - vec![ - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("asset0000"), - send: vec![], - msg: to_binary(&Cw20HandleMsg::Mint { - recipient: HumanAddr::from(MOCK_CONTRACT_ADDR), - amount: Uint128(666666u128), - }) - .unwrap() - }), - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("asset0000"), - send: vec![], - msg: to_binary(&Cw20HandleMsg::Send { - contract: HumanAddr::from("pair0000"), - amount: Uint128(666666u128), - msg: Some( - to_binary(&PairCw20HookMsg::Swap { - belief_price: None, - max_spread: None, - to: Some(HumanAddr::from("lock0000")), - }) - .unwrap() - ) - }) - .unwrap() - }), - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("lock0000"), - send: vec![], - msg: to_binary(&LockHandleMsg::LockPositionFundsHook { - position_idx: Uint128(1u128), - receiver: HumanAddr::from("addr0000"), - }) - .unwrap(), - }), - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("staking0000"), - send: vec![], - msg: to_binary(&StakingHandleMsg::IncreaseShortToken { - asset_token: HumanAddr::from("asset0000"), - staker_addr: HumanAddr::from("addr0000"), - amount: Uint128(666666u128), - }) - .unwrap(), - }) - ] - ); - - let res = query( - &deps, - QueryMsg::Position { - position_idx: Uint128(1u128), - }, - ) - .unwrap(); - let position: PositionResponse = from_binary(&res).unwrap(); - assert_eq!( - position, - PositionResponse { - idx: Uint128(1u128), - owner: HumanAddr::from("addr0000"), - asset: Asset { - info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - amount: Uint128(666666u128), - }, - collateral: Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: Uint128(1000000u128), - }, - is_short: true, - } - ); - } - - #[test] - fn mint_short_position() { - let mut deps = mock_dependencies(20, &[]); - deps.querier.with_oracle_price(&[ - (&"uusd".to_string(), &Decimal::one()), - (&"asset0000".to_string(), &Decimal::percent(100)), - (&"asset0001".to_string(), &Decimal::percent(50)), - ]); - - let base_denom = "uusd".to_string(); - - let msg = InitMsg { - owner: HumanAddr::from("owner0000"), - oracle: HumanAddr::from("oracle0000"), - collector: HumanAddr::from("collector0000"), - collateral_oracle: HumanAddr::from("collateraloracle0000"), - staking: HumanAddr::from("staking0000"), - terraswap_factory: HumanAddr::from("terraswap_factory"), - lock: HumanAddr::from("lock0000"), - base_denom: base_denom.clone(), - token_code_id: TOKEN_CODE_ID, - protocol_fee_rate: Decimal::percent(1), - }; - - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset0000"), - auction_discount: Decimal::percent(20), - min_collateral_ratio: Decimal::percent(150), - ipo_params: None, - }; - - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset0001"), - auction_discount: Decimal::percent(20), - min_collateral_ratio: Decimal::percent(150), - ipo_params: None, - }; - - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // register terraswap pair - deps.querier.with_terraswap_pair(&[( - &"uusd".to_string(), - &"asset0000".to_string(), - &HumanAddr::from("pair0000"), - )]); - - let msg = HandleMsg::OpenPosition { - collateral: Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: Uint128(1000000u128), - }, - asset_info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - collateral_ratio: Decimal::percent(200), - short_params: Some(ShortParams { - belief_price: None, - max_spread: None, - }), - }; - - let env = mock_env_with_block_time( - "addr0000", - &[Coin { - denom: "uusd".to_string(), - amount: Uint128(1000000u128), - }], - 1000, - ); - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - - // mint more tokens from the short position - let msg = HandleMsg::Mint { - position_idx: Uint128(1u128), - asset: Asset { - info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - amount: Uint128(100u128), - }, - short_params: None, - }; - let env = mock_env_with_block_time("addr0000", &[], 1000); - let res = handle(&mut deps, env.clone(), msg).unwrap(); - assert_eq!( - res.log, - vec![ - log("action", "mint"), - log("position_idx", "1"), - log("mint_amount", "100asset0000"), - ] - ); - - assert_eq!( - res.messages, - vec![ - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("asset0000"), - send: vec![], - msg: to_binary(&Cw20HandleMsg::Mint { - recipient: HumanAddr::from(MOCK_CONTRACT_ADDR), - amount: Uint128(100u128), - }) - .unwrap() - }), - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("asset0000"), - send: vec![], - msg: to_binary(&Cw20HandleMsg::Send { - contract: HumanAddr::from("pair0000"), - amount: Uint128(100u128), - msg: Some( - to_binary(&PairCw20HookMsg::Swap { - belief_price: None, - max_spread: None, - to: Some(HumanAddr::from("lock0000")), - }) - .unwrap() - ) - }) - .unwrap() - }), - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("lock0000"), - send: vec![], - msg: to_binary(&LockHandleMsg::LockPositionFundsHook { - position_idx: Uint128(1u128), - receiver: HumanAddr::from("addr0000"), - }) - .unwrap(), - }), - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("staking0000"), - send: vec![], - msg: to_binary(&StakingHandleMsg::IncreaseShortToken { - asset_token: HumanAddr::from("asset0000"), - staker_addr: HumanAddr::from("addr0000"), - amount: Uint128(100u128), - }) - .unwrap(), - }) - ] - ); - } - - #[test] - fn burn_short_position() { - let mut deps = mock_dependencies(20, &[]); - deps.querier.with_oracle_price(&[ - (&"uusd".to_string(), &Decimal::one()), - (&"asset0000".to_string(), &Decimal::percent(100)), - (&"asset0001".to_string(), &Decimal::percent(50)), - ]); - - let base_denom = "uusd".to_string(); - - let msg = InitMsg { - owner: HumanAddr::from("owner0000"), - oracle: HumanAddr::from("oracle0000"), - collector: HumanAddr::from("collector0000"), - collateral_oracle: HumanAddr::from("collateraloracle0000"), - staking: HumanAddr::from("staking0000"), - terraswap_factory: HumanAddr::from("terraswap_factory"), - lock: HumanAddr::from("lock0000"), - base_denom: base_denom.clone(), - token_code_id: TOKEN_CODE_ID, - protocol_fee_rate: Decimal::percent(1), - }; - - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset0000"), - auction_discount: Decimal::percent(20), - min_collateral_ratio: Decimal::percent(150), - ipo_params: None, - }; - - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset0001"), - auction_discount: Decimal::percent(20), - min_collateral_ratio: Decimal::percent(150), - ipo_params: None, - }; - - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // register terraswap pair - deps.querier.with_terraswap_pair(&[( - &"uusd".to_string(), - &"asset0000".to_string(), - &HumanAddr::from("pair0000"), - )]); - - let msg = HandleMsg::OpenPosition { - collateral: Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: Uint128(1000000u128), - }, - asset_info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - collateral_ratio: Decimal::percent(200), - short_params: Some(ShortParams { - belief_price: None, - max_spread: None, - }), - }; - - let env = mock_env_with_block_time( - "addr0000", - &[Coin { - denom: "uusd".to_string(), - amount: Uint128(1000000u128), - }], - 1000, - ); - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - - // burn asset tokens from the short position - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from("addr0000"), - amount: Uint128(100u128), - msg: Some( - to_binary(&Cw20HookMsg::Burn { - position_idx: Uint128(1u128), - }) - .unwrap(), - ), - }); - let env = mock_env("asset0000", &[]); - let res = handle(&mut deps, env.clone(), msg).unwrap(); - - assert_eq!( - res.log, - vec![ - log("action", "burn"), - log("position_idx", "1"), - log("burn_amount", "100asset0000"), - ] - ); - - assert_eq!( - res.messages, - vec![ - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("asset0000"), - send: vec![], - msg: to_binary(&Cw20HandleMsg::Burn { - amount: Uint128(100u128), - }) - .unwrap(), - }), - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("staking0000"), - send: vec![], - msg: to_binary(&StakingHandleMsg::DecreaseShortToken { - staker_addr: HumanAddr::from("addr0000"), - asset_token: HumanAddr::from("asset0000"), - amount: Uint128(100u128), - }) - .unwrap(), - }) - ] - ); - } - - #[test] - fn auction_short_position() { - let mut deps = mock_dependencies(20, &[]); - deps.querier.with_oracle_price(&[ - (&"uusd".to_string(), &Decimal::one()), - (&"asset0000".to_string(), &Decimal::percent(100)), - (&"asset0001".to_string(), &Decimal::percent(50)), - ]); - - let base_denom = "uusd".to_string(); - - let msg = InitMsg { - owner: HumanAddr::from("owner0000"), - oracle: HumanAddr::from("oracle0000"), - collector: HumanAddr::from("collector0000"), - staking: HumanAddr::from("staking0000"), - collateral_oracle: HumanAddr::from("collateraloracle0000"), - terraswap_factory: HumanAddr::from("terraswap_factory"), - lock: HumanAddr::from("lock0000"), - base_denom: base_denom.clone(), - token_code_id: TOKEN_CODE_ID, - protocol_fee_rate: Decimal::percent(1), - }; - - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset0000"), - auction_discount: Decimal::percent(20), - min_collateral_ratio: Decimal::percent(150), - ipo_params: None, - }; - - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset0001"), - auction_discount: Decimal::percent(20), - min_collateral_ratio: Decimal::percent(150), - ipo_params: None, - }; - - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // register terraswap pair - deps.querier.with_terraswap_pair(&[( - &"uusd".to_string(), - &"asset0000".to_string(), - &HumanAddr::from("pair0000"), - )]); - - let msg = HandleMsg::OpenPosition { - collateral: Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: Uint128(1000000u128), - }, - asset_info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - collateral_ratio: Decimal::percent(150), - short_params: Some(ShortParams { - belief_price: None, - max_spread: None, - }), - }; - - let env = mock_env_with_block_time( - "addr0000", - &[Coin { - denom: "uusd".to_string(), - amount: Uint128(1000000u128), - }], - 1000, - ); - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - - // asset price increased - deps.querier.with_oracle_price(&[ - (&"uusd".to_string(), &Decimal::one()), - (&"asset0000".to_string(), &Decimal::percent(115)), - (&"asset0001".to_string(), &Decimal::percent(50)), - ]); - - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from("addr0000"), - amount: Uint128(100u128), - msg: Some( - to_binary(&Cw20HookMsg::Auction { - position_idx: Uint128(1u128), - }) - .unwrap(), - ), - }); - let env = mock_env_with_block_time(HumanAddr::from("asset0000"), &[], 1000); - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!( - res.log, - vec![ - log("action", "auction"), - log("position_idx", "1"), - log("owner", "addr0000"), - log("return_collateral_amount", "142uusd"), - log("liquidated_amount", "100asset0000"), - log("tax_amount", "0uusd"), - log("protocol_fee", "1uusd"), - ] - ); - - assert_eq!( - res.messages, - vec![ - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("asset0000"), - msg: to_binary(&Cw20HandleMsg::Burn { - amount: Uint128(100u128), - }) - .unwrap(), - send: vec![], - }), - CosmosMsg::Bank(BankMsg::Send { - from_address: HumanAddr::from(MOCK_CONTRACT_ADDR), - to_address: HumanAddr::from("addr0000"), - amount: vec![Coin { - denom: "uusd".to_string(), - amount: Uint128(142u128) - }], - }), - CosmosMsg::Bank(BankMsg::Send { - from_address: HumanAddr::from(MOCK_CONTRACT_ADDR), - to_address: HumanAddr::from("collector0000"), - amount: vec![Coin { - denom: "uusd".to_string(), - amount: Uint128(1u128) - }] - }), - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("staking0000"), - send: vec![], - msg: to_binary(&StakingHandleMsg::DecreaseShortToken { - staker_addr: HumanAddr::from("addr0000"), - asset_token: HumanAddr::from("asset0000"), - amount: Uint128(100u128), - }) - .unwrap(), - }) - ] - ); - } - - #[test] - fn close_short_position() { - let mut deps = mock_dependencies(20, &[]); - deps.querier.with_oracle_price(&[ - (&"uusd".to_string(), &Decimal::one()), - (&"asset0000".to_string(), &Decimal::percent(100)), - ]); - - let base_denom = "uusd".to_string(); - - let msg = InitMsg { - owner: HumanAddr::from("owner0000"), - oracle: HumanAddr::from("oracle0000"), - collector: HumanAddr::from("collector0000"), - collateral_oracle: HumanAddr::from("collateraloracle0000"), - staking: HumanAddr::from("staking0000"), - terraswap_factory: HumanAddr::from("terraswap_factory"), - lock: HumanAddr::from("lock0000"), - base_denom: base_denom.clone(), - token_code_id: TOKEN_CODE_ID, - protocol_fee_rate: Decimal::percent(1), - }; - - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset0000"), - auction_discount: Decimal::percent(20), - min_collateral_ratio: Decimal::percent(200), - ipo_params: None, - }; - - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // register terraswap pair - deps.querier.with_terraswap_pair(&[( - &"uusd".to_string(), - &"asset0000".to_string(), - &HumanAddr::from("pair0000"), - )]); - - let msg = HandleMsg::OpenPosition { - collateral: Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: Uint128(200u128), // will mint 100 mAsset and lock 100 UST - }, - asset_info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset0000"), - }, - collateral_ratio: Decimal::percent(200), - short_params: Some(ShortParams { - belief_price: None, - max_spread: None, - }), - }; - - let env = mock_env_with_block_time( - "addr0000", - &[Coin { - denom: "uusd".to_string(), - amount: Uint128(200u128), - }], - 1000, - ); - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - - // burn all asset tokens from the short position - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from("addr0000"), - amount: Uint128(100u128), - msg: Some( - to_binary(&Cw20HookMsg::Burn { - position_idx: Uint128(1u128), - }) - .unwrap(), - ), - }); - let env = mock_env("asset0000", &[]); - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - - // withdraw all collateral - let msg = HandleMsg::Withdraw { - position_idx: Uint128(1u128), - collateral: Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: Uint128(200u128), - }, - }; - let env = mock_env_with_block_time("addr0000", &[], 1000); - let res = handle(&mut deps, env.clone(), msg).unwrap(); - - dbg!(&res.messages); - // refunds collateral and releases locked funds from lock contract - assert_eq!( - res.messages.contains(&CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("lock0000"), - send: vec![], - msg: to_binary(&LockHandleMsg::ReleasePositionFunds { - position_idx: Uint128(1u128), - }) - .unwrap(), - })), - true - ); - } -} diff --git a/contracts/mirror_mint/src/state.rs b/contracts/mirror_mint/src/state.rs deleted file mode 100644 index 5592d3adc..000000000 --- a/contracts/mirror_mint/src/state.rs +++ /dev/null @@ -1,299 +0,0 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use cosmwasm_std::{ - CanonicalAddr, Decimal, ReadonlyStorage, StdError, StdResult, Storage, Uint128, -}; - -use cosmwasm_storage::{singleton, singleton_read, Bucket, ReadonlyBucket}; -use mirror_protocol::common::OrderBy; -use mirror_protocol::mint::IPOParams; -use std::convert::TryInto; -use terraswap::asset::{AssetInfoRaw, AssetRaw}; - -static PREFIX_ASSET_CONFIG: &[u8] = b"asset_config"; -static PREFIX_POSITION: &[u8] = b"position"; -static PREFIX_INDEX_BY_USER: &[u8] = b"by_user"; -static PREFIX_INDEX_BY_ASSET: &[u8] = b"by_asset"; -static PREFIX_SHORT_POSITION: &[u8] = b"short_position"; - -pub static KEY_CONFIG: &[u8] = b"config"; -static KEY_POSITION_IDX: &[u8] = b"position_idx"; - -pub fn store_position_idx(storage: &mut S, position_idx: Uint128) -> StdResult<()> { - singleton(storage, KEY_POSITION_IDX).save(&position_idx) -} - -pub fn read_position_idx(storage: &S) -> StdResult { - singleton_read(storage, KEY_POSITION_IDX).load() -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct Config { - pub owner: CanonicalAddr, - pub oracle: CanonicalAddr, - pub collector: CanonicalAddr, - pub collateral_oracle: CanonicalAddr, - pub staking: CanonicalAddr, - pub terraswap_factory: CanonicalAddr, - pub lock: CanonicalAddr, - pub base_denom: String, - pub token_code_id: u64, - pub protocol_fee_rate: Decimal, -} - -pub fn store_config(storage: &mut S, config: &Config) -> StdResult<()> { - singleton(storage, KEY_CONFIG).save(config) -} - -pub fn read_config(storage: &S) -> StdResult { - singleton_read(storage, KEY_CONFIG).load() -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct AssetConfig { - pub token: CanonicalAddr, - pub auction_discount: Decimal, - pub min_collateral_ratio: Decimal, - pub end_price: Option, - pub ipo_params: Option, -} - -pub fn store_asset_config( - storage: &mut S, - asset_token: &CanonicalAddr, - asset: &AssetConfig, -) -> StdResult<()> { - let mut asset_bucket: Bucket = Bucket::new(PREFIX_ASSET_CONFIG, storage); - asset_bucket.save(asset_token.as_slice(), &asset) -} - -pub fn read_asset_config( - storage: &S, - asset_token: &CanonicalAddr, -) -> StdResult { - let asset_bucket: ReadonlyBucket = - ReadonlyBucket::new(PREFIX_ASSET_CONFIG, storage); - let res = asset_bucket.load(asset_token.as_slice()); - match res { - Ok(data) => Ok(data), - _ => Err(StdError::generic_err("no asset data stored")), - } -} - -// check if the asset has either end_price or pre_ipo_price -pub fn read_fixed_price(storage: &S, asset_info: &AssetInfoRaw) -> Option { - match asset_info { - AssetInfoRaw::Token { contract_addr } => { - let asset_bucket: ReadonlyBucket = - ReadonlyBucket::new(PREFIX_ASSET_CONFIG, storage); - let res = asset_bucket.load(contract_addr.as_slice()); - match res { - Ok(data) => { - if data.end_price.is_some() { - data.end_price - } else if let Some(ipo_params) = data.ipo_params { - Some(ipo_params.pre_ipo_price) - } else { - None - } - } - _ => None, - } - } - _ => None, - } -} - -pub fn store_short_position(storage: &mut S, idx: Uint128) -> StdResult<()> { - let mut short_position_bucket: Bucket = Bucket::new(PREFIX_SHORT_POSITION, storage); - short_position_bucket.save(&idx.u128().to_be_bytes(), &true) -} - -pub fn remove_short_position(storage: &mut S, idx: Uint128) { - let mut short_position_bucket: Bucket = Bucket::new(PREFIX_SHORT_POSITION, storage); - short_position_bucket.remove(&idx.u128().to_be_bytes()) -} - -pub fn is_short_position(storage: &S, idx: Uint128) -> StdResult { - let short_position_bucket: ReadonlyBucket = - ReadonlyBucket::new(PREFIX_SHORT_POSITION, storage); - let res = short_position_bucket.may_load(&idx.u128().to_be_bytes())?; - Ok(res.is_some()) -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct Position { - pub idx: Uint128, - pub owner: CanonicalAddr, - pub collateral: AssetRaw, - pub asset: AssetRaw, -} - -/// create position with index -pub fn create_position( - storage: &mut S, - idx: Uint128, - position: &Position, -) -> StdResult<()> { - let mut position_bucket: Bucket = Bucket::new(PREFIX_POSITION, storage); - position_bucket.save(&idx.u128().to_be_bytes(), &position)?; - - let mut position_indexer_by_user: Bucket = - Bucket::multilevel(&[PREFIX_INDEX_BY_USER, position.owner.as_slice()], storage); - position_indexer_by_user.save(&idx.u128().to_be_bytes(), &true)?; - - let mut position_indexer_by_asset: Bucket = Bucket::multilevel( - &[PREFIX_INDEX_BY_ASSET, position.asset.info.as_bytes()], - storage, - ); - position_indexer_by_asset.save(&idx.u128().to_be_bytes(), &true)?; - - Ok(()) -} - -/// store position with idx -pub fn store_position( - storage: &mut S, - idx: Uint128, - position: &Position, -) -> StdResult<()> { - let mut position_bucket: Bucket = Bucket::new(PREFIX_POSITION, storage); - position_bucket.save(&idx.u128().to_be_bytes(), &position)?; - Ok(()) -} - -/// remove position with idx -pub fn remove_position(storage: &mut S, idx: Uint128) -> StdResult<()> { - let position: Position = read_position(storage, idx)?; - let mut position_bucket: Bucket = Bucket::new(PREFIX_POSITION, storage); - position_bucket.remove(&idx.u128().to_be_bytes()); - - // remove indexer - let mut position_indexer_by_user: Bucket = - Bucket::multilevel(&[PREFIX_INDEX_BY_USER, position.owner.as_slice()], storage); - position_indexer_by_user.remove(&idx.u128().to_be_bytes()); - - // remove indexer - let mut position_indexer_by_asset: Bucket = Bucket::multilevel( - &[PREFIX_INDEX_BY_ASSET, position.asset.info.as_bytes()], - storage, - ); - position_indexer_by_asset.remove(&idx.u128().to_be_bytes()); - - // remove short position flag - remove_short_position(storage, idx); - - Ok(()) -} - -/// read position from store with position idx -pub fn read_position(storage: &S, idx: Uint128) -> StdResult { - let position_bucket: ReadonlyBucket = - ReadonlyBucket::new(PREFIX_POSITION, storage); - position_bucket.load(&idx.u128().to_be_bytes()) -} - -// settings for pagination -const MAX_LIMIT: u32 = 30; -const DEFAULT_LIMIT: u32 = 10; -pub fn read_positions( - storage: &S, - start_after: Option, - limit: Option, - order_by: Option, -) -> StdResult> { - let position_bucket: ReadonlyBucket = - ReadonlyBucket::new(PREFIX_POSITION, storage); - - let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - let start = calc_range_start(start_after); - - position_bucket - .range( - start.as_deref(), - None, - order_by.unwrap_or(OrderBy::Desc).into(), - ) - .take(limit) - .map(|item| { - let (_, v) = item?; - Ok(v) - }) - .collect() -} - -pub fn read_positions_with_user_indexer( - storage: &S, - position_owner: &CanonicalAddr, - start_after: Option, - limit: Option, - order_by: Option, -) -> StdResult> { - let position_indexer: ReadonlyBucket = - ReadonlyBucket::multilevel(&[PREFIX_INDEX_BY_USER, position_owner.as_slice()], storage); - - let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - let (start, end, order_by) = match order_by { - Some(OrderBy::Asc) => (calc_range_start(start_after), None, OrderBy::Asc), - _ => (None, calc_range_end(start_after), OrderBy::Desc), - }; - - position_indexer - .range(start.as_deref(), end.as_deref(), order_by.into()) - .take(limit) - .map(|item| { - let (k, _) = item?; - read_position(storage, Uint128(bytes_to_u128(&k)?)) - }) - .collect() -} - -pub fn read_positions_with_asset_indexer( - storage: &S, - asset_token: &CanonicalAddr, - start_after: Option, - limit: Option, - order_by: Option, -) -> StdResult> { - let position_indexer: ReadonlyBucket = - ReadonlyBucket::multilevel(&[PREFIX_INDEX_BY_ASSET, asset_token.as_slice()], storage); - - let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - let (start, end, order_by) = match order_by { - Some(OrderBy::Asc) => (calc_range_start(start_after), None, OrderBy::Asc), - _ => (None, calc_range_end(start_after), OrderBy::Desc), - }; - - position_indexer - .range(start.as_deref(), end.as_deref(), order_by.into()) - .take(limit) - .map(|item| { - let (k, _) = item?; - read_position(storage, Uint128(bytes_to_u128(&k)?)) - }) - .collect() -} - -fn bytes_to_u128(data: &[u8]) -> StdResult { - match data[0..16].try_into() { - Ok(bytes) => Ok(u128::from_be_bytes(bytes)), - Err(_) => Err(StdError::generic_err( - "Corrupted data found. 16 byte expected.", - )), - } -} - -// this will set the first key after the provided key, by appending a 1 byte -fn calc_range_start(start_after: Option) -> Option> { - start_after.map(|idx| { - let mut v = idx.u128().to_be_bytes().to_vec(); - v.push(1); - v - }) -} - -// this will set the first key after the provided key, by appending a 1 byte -fn calc_range_end(start_after: Option) -> Option> { - start_after.map(|idx| idx.u128().to_be_bytes().to_vec()) -} diff --git a/contracts/mirror_oracle/.cargo/config b/contracts/mirror_oracle/.cargo/config deleted file mode 100644 index 7c115322a..000000000 --- a/contracts/mirror_oracle/.cargo/config +++ /dev/null @@ -1,6 +0,0 @@ -[alias] -wasm = "build --release --target wasm32-unknown-unknown" -wasm-debug = "build --target wasm32-unknown-unknown" -unit-test = "test --lib --features backtraces" -integration-test = "test --test integration" -schema = "run --example schema" diff --git a/contracts/mirror_oracle/.editorconfig b/contracts/mirror_oracle/.editorconfig deleted file mode 100644 index 3d36f20b1..000000000 --- a/contracts/mirror_oracle/.editorconfig +++ /dev/null @@ -1,11 +0,0 @@ -root = true - -[*] -indent_style = space -indent_size = 2 -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true - -[*.rs] -indent_size = 4 diff --git a/contracts/mirror_oracle/.gitignore b/contracts/mirror_oracle/.gitignore deleted file mode 100644 index 10fe5d615..000000000 --- a/contracts/mirror_oracle/.gitignore +++ /dev/null @@ -1,12 +0,0 @@ -# Build results -/target - -# Text file backups -**/*.rs.bk - -# macOS -.DS_Store - -# IDEs -*.iml -.idea diff --git a/contracts/mirror_oracle/Cargo.toml b/contracts/mirror_oracle/Cargo.toml deleted file mode 100644 index 18dc9a5e9..000000000 --- a/contracts/mirror_oracle/Cargo.toml +++ /dev/null @@ -1,44 +0,0 @@ -[package] -name = "mirror-oracle" -version = "1.1.0" -authors = ["Terraform Labs, PTE."] -edition = "2018" -description = "A Oracle contract for Mirror Protocol - allows you to feed asset oracle price with owner key" -license = "Apache-2.0" - -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] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces"] - -[dependencies] -cosmwasm-std = { version = "0.10.1", features = ["iterator"] } -cosmwasm-storage = { version = "0.10.1", features = ["iterator"] } -mirror-protocol = { version = "1.1.0", path = "../../packages/mirror_protocol" } -schemars = "0.7" -serde = { version = "1.0.103", default-features = false, features = ["derive"] } - -[dev-dependencies] -cosmwasm-schema = "0.10.1" \ No newline at end of file diff --git a/contracts/mirror_oracle/README.md b/contracts/mirror_oracle/README.md deleted file mode 100644 index 56658eaed..000000000 --- a/contracts/mirror_oracle/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Mirror Oracle - -**NOTE**: Reference documentation for this contract is available [here](https://docs.mirror.finance/contracts/oracle). - -The Oracle Contract exposes an interface for accessing the latest reported price for mAssets. Price quotes are kept up-to-date by oracle feeders that are tasked with periodically fetching exchange rates from reputable sources and reporting them to the Oracle contract. - -Prices are only considered valid for 60 seconds. If no new prices are published after the data has expired, Mirror will disable CDP operations (mint, burn, deposit, withdraw) until the price feed resumes. diff --git a/contracts/mirror_oracle/examples/schema.rs b/contracts/mirror_oracle/examples/schema.rs deleted file mode 100644 index 427a549a8..000000000 --- a/contracts/mirror_oracle/examples/schema.rs +++ /dev/null @@ -1,24 +0,0 @@ -use std::env::current_dir; -use std::fs::create_dir_all; - -use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; -use mirror_protocol::oracle::{ - ConfigResponse, FeederResponse, HandleMsg, InitMsg, MigrateMsg, PriceResponse, PricesResponse, - QueryMsg, -}; - -fn main() { - let mut out_dir = current_dir().unwrap(); - out_dir.push("schema"); - create_dir_all(&out_dir).unwrap(); - remove_schemas(&out_dir).unwrap(); - - export_schema(&schema_for!(InitMsg), &out_dir); - export_schema(&schema_for!(HandleMsg), &out_dir); - export_schema(&schema_for!(MigrateMsg), &out_dir); - export_schema(&schema_for!(QueryMsg), &out_dir); - export_schema(&schema_for!(FeederResponse), &out_dir); - export_schema(&schema_for!(ConfigResponse), &out_dir); - export_schema(&schema_for!(PriceResponse), &out_dir); - export_schema(&schema_for!(PricesResponse), &out_dir); -} diff --git a/contracts/mirror_oracle/rustfmt.toml b/contracts/mirror_oracle/rustfmt.toml deleted file mode 100644 index 11a85e6a9..000000000 --- a/contracts/mirror_oracle/rustfmt.toml +++ /dev/null @@ -1,15 +0,0 @@ -# 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/mirror_oracle/schema/config_response.json b/contracts/mirror_oracle/schema/config_response.json deleted file mode 100644 index 79f4a50bb..000000000 --- a/contracts/mirror_oracle/schema/config_response.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ConfigResponse", - "type": "object", - "required": [ - "base_asset", - "owner" - ], - "properties": { - "base_asset": { - "type": "string" - }, - "owner": { - "$ref": "#/definitions/HumanAddr" - } - }, - "definitions": { - "HumanAddr": { - "type": "string" - } - } -} diff --git a/contracts/mirror_oracle/schema/feeder_response.json b/contracts/mirror_oracle/schema/feeder_response.json deleted file mode 100644 index 928f7b52d..000000000 --- a/contracts/mirror_oracle/schema/feeder_response.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "FeederResponse", - "type": "object", - "required": [ - "asset_token", - "feeder" - ], - "properties": { - "asset_token": { - "$ref": "#/definitions/HumanAddr" - }, - "feeder": { - "$ref": "#/definitions/HumanAddr" - } - }, - "definitions": { - "HumanAddr": { - "type": "string" - } - } -} diff --git a/contracts/mirror_oracle/schema/handle_msg.json b/contracts/mirror_oracle/schema/handle_msg.json deleted file mode 100644 index 527b7a379..000000000 --- a/contracts/mirror_oracle/schema/handle_msg.json +++ /dev/null @@ -1,94 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "HandleMsg", - "anyOf": [ - { - "type": "object", - "required": [ - "update_config" - ], - "properties": { - "update_config": { - "type": "object", - "properties": { - "owner": { - "anyOf": [ - { - "$ref": "#/definitions/HumanAddr" - }, - { - "type": "null" - } - ] - } - } - } - } - }, - { - "description": "Used to register new asset or to update feeder", - "type": "object", - "required": [ - "register_asset" - ], - "properties": { - "register_asset": { - "type": "object", - "required": [ - "asset_token", - "feeder" - ], - "properties": { - "asset_token": { - "$ref": "#/definitions/HumanAddr" - }, - "feeder": { - "$ref": "#/definitions/HumanAddr" - } - } - } - } - }, - { - "type": "object", - "required": [ - "feed_price" - ], - "properties": { - "feed_price": { - "type": "object", - "required": [ - "prices" - ], - "properties": { - "prices": { - "type": "array", - "items": { - "type": "array", - "items": [ - { - "$ref": "#/definitions/HumanAddr" - }, - { - "$ref": "#/definitions/Decimal" - } - ], - "maxItems": 2, - "minItems": 2 - } - } - } - } - } - } - ], - "definitions": { - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0 The greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "HumanAddr": { - "type": "string" - } - } -} diff --git a/contracts/mirror_oracle/schema/init_msg.json b/contracts/mirror_oracle/schema/init_msg.json deleted file mode 100644 index 71ac78dc9..000000000 --- a/contracts/mirror_oracle/schema/init_msg.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InitMsg", - "type": "object", - "required": [ - "base_asset", - "owner" - ], - "properties": { - "base_asset": { - "type": "string" - }, - "owner": { - "$ref": "#/definitions/HumanAddr" - } - }, - "definitions": { - "HumanAddr": { - "type": "string" - } - } -} diff --git a/contracts/mirror_oracle/schema/migrate_msg.json b/contracts/mirror_oracle/schema/migrate_msg.json deleted file mode 100644 index 666fb7e9f..000000000 --- a/contracts/mirror_oracle/schema/migrate_msg.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "MigrateMsg", - "description": "We currently take no arguments for migrations", - "type": "object" -} diff --git a/contracts/mirror_oracle/schema/price_response.json b/contracts/mirror_oracle/schema/price_response.json deleted file mode 100644 index 225aeca7a..000000000 --- a/contracts/mirror_oracle/schema/price_response.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "PriceResponse", - "type": "object", - "required": [ - "last_updated_base", - "last_updated_quote", - "rate" - ], - "properties": { - "last_updated_base": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "last_updated_quote": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "rate": { - "$ref": "#/definitions/Decimal" - } - }, - "definitions": { - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0 The greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - } - } -} diff --git a/contracts/mirror_oracle/schema/prices_response.json b/contracts/mirror_oracle/schema/prices_response.json deleted file mode 100644 index f5e4f4e3d..000000000 --- a/contracts/mirror_oracle/schema/prices_response.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "PricesResponse", - "type": "object", - "required": [ - "prices" - ], - "properties": { - "prices": { - "type": "array", - "items": { - "$ref": "#/definitions/PricesResponseElem" - } - } - }, - "definitions": { - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0 The greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "HumanAddr": { - "type": "string" - }, - "PricesResponseElem": { - "type": "object", - "required": [ - "asset_token", - "last_updated_time", - "price" - ], - "properties": { - "asset_token": { - "$ref": "#/definitions/HumanAddr" - }, - "last_updated_time": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "price": { - "$ref": "#/definitions/Decimal" - } - } - } - } -} diff --git a/contracts/mirror_oracle/schema/query_msg.json b/contracts/mirror_oracle/schema/query_msg.json deleted file mode 100644 index 133616400..000000000 --- a/contracts/mirror_oracle/schema/query_msg.json +++ /dev/null @@ -1,112 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryMsg", - "anyOf": [ - { - "type": "object", - "required": [ - "config" - ], - "properties": { - "config": { - "type": "object" - } - } - }, - { - "type": "object", - "required": [ - "feeder" - ], - "properties": { - "feeder": { - "type": "object", - "required": [ - "asset_token" - ], - "properties": { - "asset_token": { - "$ref": "#/definitions/HumanAddr" - } - } - } - } - }, - { - "type": "object", - "required": [ - "price" - ], - "properties": { - "price": { - "type": "object", - "required": [ - "base_asset", - "quote_asset" - ], - "properties": { - "base_asset": { - "type": "string" - }, - "quote_asset": { - "type": "string" - } - } - } - } - }, - { - "type": "object", - "required": [ - "prices" - ], - "properties": { - "prices": { - "type": "object", - "properties": { - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "order_by": { - "anyOf": [ - { - "$ref": "#/definitions/OrderBy" - }, - { - "type": "null" - } - ] - }, - "start_after": { - "anyOf": [ - { - "$ref": "#/definitions/HumanAddr" - }, - { - "type": "null" - } - ] - } - } - } - } - } - ], - "definitions": { - "HumanAddr": { - "type": "string" - }, - "OrderBy": { - "type": "string", - "enum": [ - "asc", - "desc" - ] - } - } -} diff --git a/contracts/mirror_oracle/src/contract.rs b/contracts/mirror_oracle/src/contract.rs deleted file mode 100644 index 175919adc..000000000 --- a/contracts/mirror_oracle/src/contract.rs +++ /dev/null @@ -1,239 +0,0 @@ -use cosmwasm_std::{ - log, to_binary, Api, Binary, Decimal, Env, Extern, HandleResponse, HandleResult, HumanAddr, - InitResponse, MigrateResponse, MigrateResult, Querier, StdError, StdResult, Storage, -}; - -use crate::math::decimal_division; -use crate::state::{ - read_config, read_feeder, read_price, read_prices, store_config, store_feeder, store_price, - Config, PriceInfo, -}; - -use mirror_protocol::common::OrderBy; -use mirror_protocol::oracle::{ - ConfigResponse, FeederResponse, HandleMsg, InitMsg, MigrateMsg, PriceResponse, PricesResponse, - PricesResponseElem, QueryMsg, -}; - -pub fn init( - deps: &mut Extern, - _env: Env, - msg: InitMsg, -) -> StdResult { - store_config( - &mut deps.storage, - &Config { - owner: deps.api.canonical_address(&msg.owner)?, - base_asset: msg.base_asset, - }, - )?; - - Ok(InitResponse::default()) -} - -pub fn handle( - deps: &mut Extern, - env: Env, - msg: HandleMsg, -) -> HandleResult { - match msg { - HandleMsg::UpdateConfig { owner } => try_update_config(deps, env, owner), - HandleMsg::RegisterAsset { - asset_token, - feeder, - } => try_register_asset(deps, env, asset_token, feeder), - HandleMsg::FeedPrice { prices } => try_feed_price(deps, env, prices), - } -} - -pub fn try_update_config( - deps: &mut Extern, - env: Env, - owner: Option, -) -> HandleResult { - let mut config: Config = read_config(&deps.storage)?; - if deps.api.canonical_address(&env.message.sender)? != config.owner { - return Err(StdError::unauthorized()); - } - - if let Some(owner) = owner { - config.owner = deps.api.canonical_address(&owner)?; - } - - store_config(&mut deps.storage, &config)?; - Ok(HandleResponse::default()) -} - -pub fn try_register_asset( - deps: &mut Extern, - env: Env, - asset_token: HumanAddr, - feeder: HumanAddr, -) -> HandleResult { - let config: Config = read_config(&deps.storage)?; - if config.owner != deps.api.canonical_address(&env.message.sender)? { - return Err(StdError::unauthorized()); - } - - let asset_token_raw = deps.api.canonical_address(&asset_token)?; - - // check if it is a new asset - if read_feeder(&deps.storage, &asset_token_raw).is_err() { - // new asset, initialize asset price info - store_price( - &mut deps.storage, - &asset_token_raw, - &PriceInfo { - price: Decimal::zero(), - last_updated_time: 0u64, - }, - )?; - } - - // update/store feeder - store_feeder( - &mut deps.storage, - &asset_token_raw, - &deps.api.canonical_address(&feeder)?, - )?; - - Ok(HandleResponse::default()) -} - -pub fn try_feed_price( - deps: &mut Extern, - env: Env, - prices: Vec<(HumanAddr, Decimal)>, -) -> HandleResult { - let feeder_raw = deps.api.canonical_address(&env.message.sender)?; - - let mut logs = vec![log("action", "price_feed")]; - for price in prices { - logs.push(log("asset", price.0.to_string())); - logs.push(log("price", price.1.to_string())); - - // Check feeder permission - let asset_token_raw = deps.api.canonical_address(&price.0)?; - if feeder_raw != read_feeder(&deps.storage, &asset_token_raw)? { - return Err(StdError::unauthorized()); - } - - let mut state: PriceInfo = read_price(&deps.storage, &asset_token_raw)?; - state.last_updated_time = env.block.time; - state.price = price.1; - - store_price(&mut deps.storage, &asset_token_raw, &state)?; - } - - Ok(HandleResponse { - messages: vec![], - log: logs, - data: None, - }) -} - -pub fn query( - deps: &Extern, - msg: QueryMsg, -) -> StdResult { - match msg { - QueryMsg::Config {} => to_binary(&query_config(deps)?), - QueryMsg::Feeder { asset_token } => to_binary(&query_feeder(deps, asset_token)?), - QueryMsg::Price { - base_asset, - quote_asset, - } => to_binary(&query_price(deps, base_asset, quote_asset)?), - QueryMsg::Prices { - start_after, - limit, - order_by, - } => to_binary(&query_prices(deps, start_after, limit, order_by)?), - } -} - -fn query_config( - deps: &Extern, -) -> StdResult { - let state = read_config(&deps.storage)?; - let resp = ConfigResponse { - owner: deps.api.human_address(&state.owner)?, - base_asset: state.base_asset, - }; - - Ok(resp) -} - -fn query_feeder( - deps: &Extern, - asset_token: HumanAddr, -) -> StdResult { - let feeder = read_feeder(&deps.storage, &deps.api.canonical_address(&asset_token)?)?; - let resp = FeederResponse { - asset_token, - feeder: deps.api.human_address(&feeder)?, - }; - - Ok(resp) -} - -fn query_price( - deps: &Extern, - base: String, - quote: String, -) -> StdResult { - let config: Config = read_config(&deps.storage)?; - let quote_price = if config.base_asset == quote { - PriceInfo { - price: Decimal::one(), - last_updated_time: u64::MAX, - } - } else { - read_price( - &deps.storage, - &deps.api.canonical_address(&HumanAddr::from(quote))?, - )? - }; - - let base_price = if config.base_asset == base { - PriceInfo { - price: Decimal::one(), - last_updated_time: u64::MAX, - } - } else { - read_price( - &deps.storage, - &deps.api.canonical_address(&HumanAddr::from(base))?, - )? - }; - - Ok(PriceResponse { - rate: decimal_division(base_price.price, quote_price.price), - last_updated_base: base_price.last_updated_time, - last_updated_quote: quote_price.last_updated_time, - }) -} - -fn query_prices( - deps: &Extern, - start_after: Option, - limit: Option, - order_by: Option, -) -> StdResult { - let start_after = if let Some(start_after) = start_after { - Some(deps.api.canonical_address(&start_after)?) - } else { - None - }; - - let prices: Vec = read_prices(&deps, start_after, limit, order_by)?; - - Ok(PricesResponse { prices }) -} - -pub fn migrate( - _deps: &mut Extern, - _env: Env, - _msg: MigrateMsg, -) -> MigrateResult { - Ok(MigrateResponse::default()) -} diff --git a/contracts/mirror_oracle/src/lib.rs b/contracts/mirror_oracle/src/lib.rs deleted file mode 100644 index 207da706f..000000000 --- a/contracts/mirror_oracle/src/lib.rs +++ /dev/null @@ -1,9 +0,0 @@ -pub mod contract; -pub mod math; -pub mod state; - -#[cfg(test)] -mod tests; - -#[cfg(target_arch = "wasm32")] -cosmwasm_std::create_entry_points_with_migration!(contract); diff --git a/contracts/mirror_oracle/src/math.rs b/contracts/mirror_oracle/src/math.rs deleted file mode 100644 index 22ea32790..000000000 --- a/contracts/mirror_oracle/src/math.rs +++ /dev/null @@ -1,8 +0,0 @@ -use cosmwasm_std::{Decimal, Uint128}; - -const DECIMAL_FRACTIONAL: Uint128 = Uint128(1_000_000_000u128); - -/// return a / b -pub fn decimal_division(a: Decimal, b: Decimal) -> Decimal { - Decimal::from_ratio(DECIMAL_FRACTIONAL * a, b * DECIMAL_FRACTIONAL) -} diff --git a/contracts/mirror_oracle/src/state.rs b/contracts/mirror_oracle/src/state.rs deleted file mode 100644 index a99ea917d..000000000 --- a/contracts/mirror_oracle/src/state.rs +++ /dev/null @@ -1,114 +0,0 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use cosmwasm_std::{Api, CanonicalAddr, Decimal, Extern, Querier, StdResult, Storage}; -use cosmwasm_storage::{singleton, singleton_read, Bucket, ReadonlyBucket}; - -use mirror_protocol::common::OrderBy; -use mirror_protocol::oracle::PricesResponseElem; - -static PREFIX_FEEDER: &[u8] = b"feeder"; -static PREFIX_PRICE: &[u8] = b"price"; - -static KEY_CONFIG: &[u8] = b"config"; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct Config { - pub owner: CanonicalAddr, - pub base_asset: String, -} - -pub fn store_config(storage: &mut S, config: &Config) -> StdResult<()> { - singleton(storage, KEY_CONFIG).save(config) -} - -pub fn read_config(storage: &S) -> StdResult { - singleton_read(storage, KEY_CONFIG).load() -} - -pub fn store_feeder( - storage: &mut S, - asset_token: &CanonicalAddr, - feeder: &CanonicalAddr, -) -> StdResult<()> { - let mut feeder_bucket: Bucket = Bucket::new(PREFIX_FEEDER, storage); - - feeder_bucket.save(asset_token.as_slice(), feeder) -} - -pub fn read_feeder( - storage: &S, - asset_token: &CanonicalAddr, -) -> StdResult { - let feeder_bucket: ReadonlyBucket = - ReadonlyBucket::new(PREFIX_FEEDER, storage); - feeder_bucket.load(asset_token.as_slice()) -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct PriceInfo { - pub price: Decimal, - pub last_updated_time: u64, -} - -pub fn store_price( - storage: &mut S, - asset_token: &CanonicalAddr, - price: &PriceInfo, -) -> StdResult<()> { - let mut price_bucket: Bucket = Bucket::new(PREFIX_PRICE, storage); - price_bucket.save(asset_token.as_slice(), price) -} - -pub fn read_price(storage: &S, asset_token: &CanonicalAddr) -> StdResult { - let price_bucket: ReadonlyBucket = ReadonlyBucket::new(PREFIX_PRICE, storage); - price_bucket.load(asset_token.as_slice()) -} - -// settings for pagination -const MAX_LIMIT: u32 = 30; -const DEFAULT_LIMIT: u32 = 10; -pub fn read_prices( - deps: &Extern, - start_after: Option, - limit: Option, - order_by: Option, -) -> StdResult> { - let price_bucket: ReadonlyBucket = - ReadonlyBucket::new(PREFIX_PRICE, &deps.storage); - - let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - let (start, end, order_by) = match order_by { - Some(OrderBy::Asc) => (calc_range_start(start_after), None, OrderBy::Asc), - _ => (None, calc_range_end(start_after), OrderBy::Desc), - }; - - price_bucket - .range(start.as_deref(), end.as_deref(), order_by.into()) - .take(limit) - .map(|item| { - let (k, v) = item?; - - let asset_token = deps.api.human_address(&CanonicalAddr::from(k))?; - Ok(PricesResponseElem { - asset_token, - price: v.price, - last_updated_time: v.last_updated_time, - }) - }) - .collect() -} - -// this will set the first key after the provided key, by appending a 1 byte -fn calc_range_start(start_after: Option) -> Option> { - start_after.map(|idx| { - let mut v = idx.as_slice().to_vec(); - v.push(1); - v - }) -} - -// this will set the first key after the provided key in Desc -fn calc_range_end(start_after: Option) -> Option> { - start_after.map(|idx| idx.as_slice().to_vec()) -} diff --git a/contracts/mirror_oracle/src/tests.rs b/contracts/mirror_oracle/src/tests.rs deleted file mode 100644 index 9c7412502..000000000 --- a/contracts/mirror_oracle/src/tests.rs +++ /dev/null @@ -1,321 +0,0 @@ -use crate::contract::{handle, init, query}; -use cosmwasm_std::testing::{mock_dependencies, mock_env}; -use cosmwasm_std::{ - from_binary, Decimal, HandleResponse, HandleResult, HumanAddr, InitResponse, StdError, -}; -use mirror_protocol::common::OrderBy; -use mirror_protocol::oracle::{ - ConfigResponse, FeederResponse, HandleMsg, InitMsg, PriceResponse, PricesResponse, - PricesResponseElem, QueryMsg, -}; - -#[test] -fn proper_initialization() { - let mut deps = mock_dependencies(20, &[]); - - let msg = InitMsg { - owner: HumanAddr::from("owner0000"), - base_asset: "base0000".to_string(), - }; - - let env = mock_env("addr0000", &[]); - - // we can just call .unwrap() to assert this was a success - let res: InitResponse = init(&mut deps, env, msg).unwrap(); - assert_eq!(0, res.messages.len()); - - // it worked, let's query the state - let res = query(&mut deps, QueryMsg::Config {}).unwrap(); - let value: ConfigResponse = from_binary(&res).unwrap(); - assert_eq!("owner0000", value.owner.as_str()); - assert_eq!("base0000", value.base_asset); -} - -#[test] -fn update_owner() { - let mut deps = mock_dependencies(20, &[]); - let msg = InitMsg { - owner: HumanAddr("owner0000".to_string()), - base_asset: "base0000".to_string(), - }; - - let env = mock_env("addr0000", &[]); - - // we can just call .unwrap() to assert this was a success - let _res: InitResponse = init(&mut deps, env, msg).unwrap(); - // update owner - let env = mock_env("owner0000", &[]); - let msg = HandleMsg::UpdateConfig { - owner: Some(HumanAddr("owner0001".to_string())), - }; - - let res: HandleResponse = handle(&mut deps, env, msg).unwrap(); - assert_eq!(0, res.messages.len()); - - // it worked, let's query the state - let query_result = query(&mut deps, QueryMsg::Config {}).unwrap(); - let value: ConfigResponse = from_binary(&query_result).unwrap(); - assert_eq!("owner0001", value.owner.as_str()); - assert_eq!("base0000", value.base_asset); - - // Unauthorzied err - let env = mock_env("addr0000", &[]); - let msg = HandleMsg::UpdateConfig { owner: None }; - - let res: HandleResult = handle(&mut deps, env, msg); - match res.unwrap_err() { - StdError::Unauthorized { .. } => {} - _ => panic!("Must return unauthorized error"), - } -} - -#[test] -fn update_price() { - let mut deps = mock_dependencies(20, &[]); - let msg = InitMsg { - owner: HumanAddr("owner0000".to_string()), - base_asset: "base0000".to_string(), - }; - - let env = mock_env("addr0000", &[]); - - // we can just call .unwrap() to assert this was a success - let _res: InitResponse = init(&mut deps, env, msg).unwrap(); - - // register asset - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("mAAPL"), - feeder: HumanAddr::from("addr0000"), - }; - - let env = mock_env("addr0000", &[]); - let res: HandleResult = handle(&mut deps, env, msg); - match res { - Err(StdError::Unauthorized { .. }) => {} - _ => panic!("DO NOT ENTER HERE"), - } - - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("mAAPL"), - feeder: HumanAddr::from("addr0001"), - }; - - let env = mock_env("owner0000", &[]); - let _res: HandleResponse = handle(&mut deps, env, msg).unwrap(); - - // try update an asset already exists - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("mAAPL"), - feeder: HumanAddr::from("addr0000"), - }; - - let env = mock_env("owner0000", &[]); - let _res: HandleResponse = handle(&mut deps, env, msg).unwrap(); - - // update price - let env = mock_env("addr0000", &[]); - let msg = HandleMsg::FeedPrice { - prices: vec![( - HumanAddr::from("mAAPL"), - Decimal::from_ratio(12u128, 10u128), - )], - }; - - let res: HandleResponse = handle(&mut deps, env, msg).unwrap(); - assert_eq!(0, res.messages.len()); - - // it worked, let's query the state - let query_result = query( - &mut deps, - QueryMsg::Price { - base_asset: "mAAPL".to_string(), - quote_asset: "base0000".to_string(), - }, - ) - .unwrap(); - let value: PriceResponse = from_binary(&query_result).unwrap(); - assert_eq!("1.2", format!("{}", value.rate)); - - // Unauthorzied err - let env = mock_env("addr0001", &[]); - let msg = HandleMsg::FeedPrice { - prices: vec![( - HumanAddr::from("mAAPL"), - Decimal::from_ratio(12u128, 10u128), - )], - }; - - let res: HandleResult = handle(&mut deps, env, msg); - match res.unwrap_err() { - StdError::Unauthorized { .. } => {} - _ => panic!("Must return unauthorized error"), - } -} - -#[test] -fn feed_price() { - let mut deps = mock_dependencies(20, &[]); - - let msg = InitMsg { - owner: HumanAddr("owner0000".to_string()), - base_asset: "base0000".to_string(), - }; - - let env = mock_env("addr0000", &[]); - let _res = init(&mut deps, env, msg).unwrap(); - - // update price - let env = mock_env("addr0000", &[]); - let msg = HandleMsg::FeedPrice { - prices: vec![( - HumanAddr::from("mAAPL"), - Decimal::from_ratio(12u128, 10u128), - )], - }; - - let _res = handle(&mut deps, env, msg).unwrap_err(); - - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("mAAPL"), - feeder: HumanAddr::from("addr0000"), - }; - - let env = mock_env("addr0000", &[]); - let res = handle(&mut deps, env, msg).unwrap_err(); - match res { - StdError::Unauthorized { .. } => {} - _ => panic!("DO NOT ENTER HERE"), - } - - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("mAAPL"), - feeder: HumanAddr::from("addr0000"), - }; - - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("mGOGL"), - feeder: HumanAddr::from("addr0000"), - }; - - let env = mock_env("owner0000", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - let value: FeederResponse = from_binary( - &query( - &deps, - QueryMsg::Feeder { - asset_token: HumanAddr::from("mAAPL"), - }, - ) - .unwrap(), - ) - .unwrap(); - assert_eq!( - value, - FeederResponse { - asset_token: HumanAddr::from("mAAPL"), - feeder: HumanAddr::from("addr0000"), - } - ); - - let value: PriceResponse = from_binary( - &query( - &deps, - QueryMsg::Price { - base_asset: "mAAPL".to_string(), - quote_asset: "base0000".to_string(), - }, - ) - .unwrap(), - ) - .unwrap(); - assert_eq!( - value, - PriceResponse { - rate: Decimal::zero(), - last_updated_base: 0u64, - last_updated_quote: u64::MAX, - } - ); - - let msg = HandleMsg::FeedPrice { - prices: vec![ - ( - HumanAddr::from("mAAPL"), - Decimal::from_ratio(12u128, 10u128), - ), - ( - HumanAddr::from("mGOGL"), - Decimal::from_ratio(22u128, 10u128), - ), - ], - }; - let env = mock_env("addr0000", &[]); - let _res = handle(&mut deps, env.clone(), msg).unwrap(); - let value: PriceResponse = from_binary( - &query( - &deps, - QueryMsg::Price { - base_asset: "mAAPL".to_string(), - quote_asset: "base0000".to_string(), - }, - ) - .unwrap(), - ) - .unwrap(); - assert_eq!( - value, - PriceResponse { - rate: Decimal::from_ratio(12u128, 10u128), - last_updated_base: env.block.time, - last_updated_quote: u64::MAX, - } - ); - - let value: PricesResponse = from_binary( - &query( - &deps, - QueryMsg::Prices { - start_after: None, - limit: None, - order_by: Some(OrderBy::Asc), - }, - ) - .unwrap(), - ) - .unwrap(); - assert_eq!( - value, - PricesResponse { - prices: vec![ - PricesResponseElem { - asset_token: HumanAddr::from("mAAPL"), - price: Decimal::from_ratio(12u128, 10u128), - last_updated_time: env.block.time, - }, - PricesResponseElem { - asset_token: HumanAddr::from("mGOGL"), - price: Decimal::from_ratio(22u128, 10u128), - last_updated_time: env.block.time, - } - ], - } - ); - - // Unautorized try - let env = mock_env("addr0001", &[]); - let msg = HandleMsg::FeedPrice { - prices: vec![( - HumanAddr::from("mAAPL"), - Decimal::from_ratio(12u128, 10u128), - )], - }; - - let res = handle(&mut deps, env, msg); - match res { - Err(StdError::Unauthorized { .. }) => {} - _ => panic!("Must return unauthorized error"), - } -} diff --git a/contracts/mirror_staking/.cargo/config b/contracts/mirror_staking/.cargo/config deleted file mode 100644 index 7c115322a..000000000 --- a/contracts/mirror_staking/.cargo/config +++ /dev/null @@ -1,6 +0,0 @@ -[alias] -wasm = "build --release --target wasm32-unknown-unknown" -wasm-debug = "build --target wasm32-unknown-unknown" -unit-test = "test --lib --features backtraces" -integration-test = "test --test integration" -schema = "run --example schema" diff --git a/contracts/mirror_staking/.editorconfig b/contracts/mirror_staking/.editorconfig deleted file mode 100644 index 3d36f20b1..000000000 --- a/contracts/mirror_staking/.editorconfig +++ /dev/null @@ -1,11 +0,0 @@ -root = true - -[*] -indent_style = space -indent_size = 2 -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true - -[*.rs] -indent_size = 4 diff --git a/contracts/mirror_staking/.gitignore b/contracts/mirror_staking/.gitignore deleted file mode 100644 index 10fe5d615..000000000 --- a/contracts/mirror_staking/.gitignore +++ /dev/null @@ -1,12 +0,0 @@ -# Build results -/target - -# Text file backups -**/*.rs.bk - -# macOS -.DS_Store - -# IDEs -*.iml -.idea diff --git a/contracts/mirror_staking/Cargo.toml b/contracts/mirror_staking/Cargo.toml deleted file mode 100644 index d4aa15fd7..000000000 --- a/contracts/mirror_staking/Cargo.toml +++ /dev/null @@ -1,47 +0,0 @@ -[package] -name = "mirror-staking" -version = "1.1.0" -authors = ["Terraform Labs, PTE."] -edition = "2018" -description = "A Staking contract for Mirror Protocol - distribute rewards to stakers" -license = "Apache-2.0" - -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] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces"] - -[dependencies] -cw20 = "0.2" -terraswap = "1.1.0" -cosmwasm-std = { version = "0.10.1", features = ["iterator"] } -cosmwasm-storage = { version = "0.10.1", features = ["iterator"] } -mirror-protocol = { version = "1.1.0", path = "../../packages/mirror_protocol" } -schemars = "0.7" -serde = { version = "1.0.103", default-features = false, features = ["derive"] } - -[dev-dependencies] -terra-cosmwasm = { version = "1.2.2" } -cosmwasm-schema = "0.10.1" \ No newline at end of file diff --git a/contracts/mirror_staking/README.md b/contracts/mirror_staking/README.md deleted file mode 100644 index ccb7dedb0..000000000 --- a/contracts/mirror_staking/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Mirror Staking - -**NOTE**: Reference documentation for this contract is available [here](https://docs.mirror.finance/contracts/staking). - -The Staking Contract contains the logic for LP Token staking and reward distribution. Staking rewards for LP stakers come from the new MIR tokens generated at each block by the Factory Contract and are split between all combined staking pools. The new MIR tokens are distributed in proportion to size of staked LP tokens multiplied by the weight of that asset's staking pool. diff --git a/contracts/mirror_staking/examples/schema.rs b/contracts/mirror_staking/examples/schema.rs deleted file mode 100644 index 39df5e7c5..000000000 --- a/contracts/mirror_staking/examples/schema.rs +++ /dev/null @@ -1,24 +0,0 @@ -use std::env::current_dir; -use std::fs::create_dir_all; - -use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; -use mirror_protocol::staking::{ - ConfigResponse, Cw20HookMsg, HandleMsg, InitMsg, MigrateMsg, PoolInfoResponse, QueryMsg, - RewardInfoResponse, -}; - -fn main() { - let mut out_dir = current_dir().unwrap(); - out_dir.push("schema"); - create_dir_all(&out_dir).unwrap(); - remove_schemas(&out_dir).unwrap(); - - export_schema(&schema_for!(InitMsg), &out_dir); - export_schema(&schema_for!(HandleMsg), &out_dir); - export_schema(&schema_for!(Cw20HookMsg), &out_dir); - export_schema(&schema_for!(QueryMsg), &out_dir); - export_schema(&schema_for!(MigrateMsg), &out_dir); - export_schema(&schema_for!(ConfigResponse), &out_dir); - export_schema(&schema_for!(RewardInfoResponse), &out_dir); - export_schema(&schema_for!(PoolInfoResponse), &out_dir); -} diff --git a/contracts/mirror_staking/rustfmt.toml b/contracts/mirror_staking/rustfmt.toml deleted file mode 100644 index 11a85e6a9..000000000 --- a/contracts/mirror_staking/rustfmt.toml +++ /dev/null @@ -1,15 +0,0 @@ -# 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/mirror_staking/schema/config_response.json b/contracts/mirror_staking/schema/config_response.json deleted file mode 100644 index 1e3cc0fe8..000000000 --- a/contracts/mirror_staking/schema/config_response.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ConfigResponse", - "type": "object", - "required": [ - "base_denom", - "mint_contract", - "mirror_token", - "oracle_contract", - "owner", - "premium_min_update_interval", - "terraswap_factory" - ], - "properties": { - "base_denom": { - "type": "string" - }, - "mint_contract": { - "$ref": "#/definitions/HumanAddr" - }, - "mirror_token": { - "$ref": "#/definitions/HumanAddr" - }, - "oracle_contract": { - "$ref": "#/definitions/HumanAddr" - }, - "owner": { - "$ref": "#/definitions/HumanAddr" - }, - "premium_min_update_interval": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "terraswap_factory": { - "$ref": "#/definitions/HumanAddr" - } - }, - "definitions": { - "HumanAddr": { - "type": "string" - } - } -} diff --git a/contracts/mirror_staking/schema/cw20_hook_msg.json b/contracts/mirror_staking/schema/cw20_hook_msg.json deleted file mode 100644 index 893e25a27..000000000 --- a/contracts/mirror_staking/schema/cw20_hook_msg.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Cw20HookMsg", - "anyOf": [ - { - "type": "object", - "required": [ - "bond" - ], - "properties": { - "bond": { - "type": "object", - "required": [ - "asset_token" - ], - "properties": { - "asset_token": { - "$ref": "#/definitions/HumanAddr" - } - } - } - } - }, - { - "type": "object", - "required": [ - "deposit_reward" - ], - "properties": { - "deposit_reward": { - "type": "object", - "required": [ - "rewards" - ], - "properties": { - "rewards": { - "type": "array", - "items": { - "type": "array", - "items": [ - { - "$ref": "#/definitions/HumanAddr" - }, - { - "$ref": "#/definitions/Uint128" - } - ], - "maxItems": 2, - "minItems": 2 - } - } - } - } - } - } - ], - "definitions": { - "HumanAddr": { - "type": "string" - }, - "Uint128": { - "type": "string" - } - } -} diff --git a/contracts/mirror_staking/schema/handle_msg.json b/contracts/mirror_staking/schema/handle_msg.json deleted file mode 100644 index 5d4deefe6..000000000 --- a/contracts/mirror_staking/schema/handle_msg.json +++ /dev/null @@ -1,364 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "HandleMsg", - "anyOf": [ - { - "type": "object", - "required": [ - "receive" - ], - "properties": { - "receive": { - "$ref": "#/definitions/Cw20ReceiveMsg" - } - } - }, - { - "description": "Owner operations ///", - "type": "object", - "required": [ - "update_config" - ], - "properties": { - "update_config": { - "type": "object", - "properties": { - "owner": { - "anyOf": [ - { - "$ref": "#/definitions/HumanAddr" - }, - { - "type": "null" - } - ] - }, - "premium_min_update_interval": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - } - } - } - } - }, - { - "type": "object", - "required": [ - "register_asset" - ], - "properties": { - "register_asset": { - "type": "object", - "required": [ - "asset_token", - "staking_token" - ], - "properties": { - "asset_token": { - "$ref": "#/definitions/HumanAddr" - }, - "staking_token": { - "$ref": "#/definitions/HumanAddr" - } - } - } - } - }, - { - "description": "User operations ///", - "type": "object", - "required": [ - "unbond" - ], - "properties": { - "unbond": { - "type": "object", - "required": [ - "amount", - "asset_token" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "asset_token": { - "$ref": "#/definitions/HumanAddr" - } - } - } - } - }, - { - "description": "Withdraw pending rewards", - "type": "object", - "required": [ - "withdraw" - ], - "properties": { - "withdraw": { - "type": "object", - "properties": { - "asset_token": { - "anyOf": [ - { - "$ref": "#/definitions/HumanAddr" - }, - { - "type": "null" - } - ] - } - } - } - } - }, - { - "description": "Provides liquidity and automatically stakes the LP tokens", - "type": "object", - "required": [ - "auto_stake" - ], - "properties": { - "auto_stake": { - "type": "object", - "required": [ - "assets" - ], - "properties": { - "assets": { - "type": "array", - "items": { - "$ref": "#/definitions/Asset" - }, - "maxItems": 2, - "minItems": 2 - }, - "slippage_tolerance": { - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - } - } - } - } - }, - { - "description": "Hook to stake the minted LP tokens", - "type": "object", - "required": [ - "auto_stake_hook" - ], - "properties": { - "auto_stake_hook": { - "type": "object", - "required": [ - "asset_token", - "prev_staking_token_amount", - "staker_addr", - "staking_token" - ], - "properties": { - "asset_token": { - "$ref": "#/definitions/HumanAddr" - }, - "prev_staking_token_amount": { - "$ref": "#/definitions/Uint128" - }, - "staker_addr": { - "$ref": "#/definitions/HumanAddr" - }, - "staking_token": { - "$ref": "#/definitions/HumanAddr" - } - } - } - } - }, - { - "description": "Permission-less operations ///", - "type": "object", - "required": [ - "adjust_premium" - ], - "properties": { - "adjust_premium": { - "type": "object", - "required": [ - "asset_tokens" - ], - "properties": { - "asset_tokens": { - "type": "array", - "items": { - "$ref": "#/definitions/HumanAddr" - } - } - } - } - } - }, - { - "description": "Mint contract operations ///", - "type": "object", - "required": [ - "increase_short_token" - ], - "properties": { - "increase_short_token": { - "type": "object", - "required": [ - "amount", - "asset_token", - "staker_addr" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "asset_token": { - "$ref": "#/definitions/HumanAddr" - }, - "staker_addr": { - "$ref": "#/definitions/HumanAddr" - } - } - } - } - }, - { - "type": "object", - "required": [ - "decrease_short_token" - ], - "properties": { - "decrease_short_token": { - "type": "object", - "required": [ - "amount", - "asset_token", - "staker_addr" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "asset_token": { - "$ref": "#/definitions/HumanAddr" - }, - "staker_addr": { - "$ref": "#/definitions/HumanAddr" - } - } - } - } - } - ], - "definitions": { - "Asset": { - "type": "object", - "required": [ - "amount", - "info" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "info": { - "$ref": "#/definitions/AssetInfo" - } - } - }, - "AssetInfo": { - "anyOf": [ - { - "type": "object", - "required": [ - "token" - ], - "properties": { - "token": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "$ref": "#/definitions/HumanAddr" - } - } - } - } - }, - { - "type": "object", - "required": [ - "native_token" - ], - "properties": { - "native_token": { - "type": "object", - "required": [ - "denom" - ], - "properties": { - "denom": { - "type": "string" - } - } - } - } - } - ] - }, - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", - "type": "string" - }, - "Cw20ReceiveMsg": { - "description": "Cw20ReceiveMsg should be de/serialized under `Receive()` variant in a HandleMsg", - "type": "object", - "required": [ - "amount", - "sender" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "msg": { - "anyOf": [ - { - "$ref": "#/definitions/Binary" - }, - { - "type": "null" - } - ] - }, - "sender": { - "$ref": "#/definitions/HumanAddr" - } - } - }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "HumanAddr": { - "type": "string" - }, - "Uint128": { - "type": "string" - } - } -} diff --git a/contracts/mirror_staking/schema/init_msg.json b/contracts/mirror_staking/schema/init_msg.json deleted file mode 100644 index e5526d651..000000000 --- a/contracts/mirror_staking/schema/init_msg.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InitMsg", - "type": "object", - "required": [ - "base_denom", - "mint_contract", - "mirror_token", - "oracle_contract", - "owner", - "premium_min_update_interval", - "terraswap_factory" - ], - "properties": { - "base_denom": { - "type": "string" - }, - "mint_contract": { - "$ref": "#/definitions/HumanAddr" - }, - "mirror_token": { - "$ref": "#/definitions/HumanAddr" - }, - "oracle_contract": { - "$ref": "#/definitions/HumanAddr" - }, - "owner": { - "$ref": "#/definitions/HumanAddr" - }, - "premium_min_update_interval": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "terraswap_factory": { - "$ref": "#/definitions/HumanAddr" - } - }, - "definitions": { - "HumanAddr": { - "type": "string" - } - } -} diff --git a/contracts/mirror_staking/schema/migrate_msg.json b/contracts/mirror_staking/schema/migrate_msg.json deleted file mode 100644 index 3b2e0822f..000000000 --- a/contracts/mirror_staking/schema/migrate_msg.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "MigrateMsg", - "description": "We currently take no arguments for migrations", - "type": "object", - "required": [ - "base_denom", - "mint_contract", - "oracle_contract", - "premium_min_update_interval", - "terraswap_factory" - ], - "properties": { - "base_denom": { - "type": "string" - }, - "mint_contract": { - "$ref": "#/definitions/HumanAddr" - }, - "oracle_contract": { - "$ref": "#/definitions/HumanAddr" - }, - "premium_min_update_interval": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "terraswap_factory": { - "$ref": "#/definitions/HumanAddr" - } - }, - "definitions": { - "HumanAddr": { - "type": "string" - } - } -} diff --git a/contracts/mirror_staking/schema/pool_info_response.json b/contracts/mirror_staking/schema/pool_info_response.json deleted file mode 100644 index e8f95fe5e..000000000 --- a/contracts/mirror_staking/schema/pool_info_response.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "PoolInfoResponse", - "type": "object", - "required": [ - "asset_token", - "pending_reward", - "premium_rate", - "premium_updated_time", - "reward_index", - "short_pending_reward", - "short_reward_index", - "short_reward_weight", - "staking_token", - "total_bond_amount", - "total_short_amount" - ], - "properties": { - "asset_token": { - "$ref": "#/definitions/HumanAddr" - }, - "pending_reward": { - "$ref": "#/definitions/Uint128" - }, - "premium_rate": { - "$ref": "#/definitions/Decimal" - }, - "premium_updated_time": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "reward_index": { - "$ref": "#/definitions/Decimal" - }, - "short_pending_reward": { - "$ref": "#/definitions/Uint128" - }, - "short_reward_index": { - "$ref": "#/definitions/Decimal" - }, - "short_reward_weight": { - "$ref": "#/definitions/Decimal" - }, - "staking_token": { - "$ref": "#/definitions/HumanAddr" - }, - "total_bond_amount": { - "$ref": "#/definitions/Uint128" - }, - "total_short_amount": { - "$ref": "#/definitions/Uint128" - } - }, - "definitions": { - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "HumanAddr": { - "type": "string" - }, - "Uint128": { - "type": "string" - } - } -} diff --git a/contracts/mirror_staking/schema/query_msg.json b/contracts/mirror_staking/schema/query_msg.json deleted file mode 100644 index 4613ca6c1..000000000 --- a/contracts/mirror_staking/schema/query_msg.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryMsg", - "anyOf": [ - { - "type": "object", - "required": [ - "config" - ], - "properties": { - "config": { - "type": "object" - } - } - }, - { - "type": "object", - "required": [ - "pool_info" - ], - "properties": { - "pool_info": { - "type": "object", - "required": [ - "asset_token" - ], - "properties": { - "asset_token": { - "$ref": "#/definitions/HumanAddr" - } - } - } - } - }, - { - "type": "object", - "required": [ - "reward_info" - ], - "properties": { - "reward_info": { - "type": "object", - "required": [ - "staker_addr" - ], - "properties": { - "asset_token": { - "anyOf": [ - { - "$ref": "#/definitions/HumanAddr" - }, - { - "type": "null" - } - ] - }, - "staker_addr": { - "$ref": "#/definitions/HumanAddr" - } - } - } - } - } - ], - "definitions": { - "HumanAddr": { - "type": "string" - } - } -} diff --git a/contracts/mirror_staking/schema/reward_info_response.json b/contracts/mirror_staking/schema/reward_info_response.json deleted file mode 100644 index 5ed0073a5..000000000 --- a/contracts/mirror_staking/schema/reward_info_response.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "RewardInfoResponse", - "type": "object", - "required": [ - "reward_infos", - "staker_addr" - ], - "properties": { - "reward_infos": { - "type": "array", - "items": { - "$ref": "#/definitions/RewardInfoResponseItem" - } - }, - "staker_addr": { - "$ref": "#/definitions/HumanAddr" - } - }, - "definitions": { - "HumanAddr": { - "type": "string" - }, - "RewardInfoResponseItem": { - "type": "object", - "required": [ - "asset_token", - "bond_amount", - "is_short", - "pending_reward" - ], - "properties": { - "asset_token": { - "$ref": "#/definitions/HumanAddr" - }, - "bond_amount": { - "$ref": "#/definitions/Uint128" - }, - "is_short": { - "type": "boolean" - }, - "pending_reward": { - "$ref": "#/definitions/Uint128" - } - } - }, - "Uint128": { - "type": "string" - } - } -} diff --git a/contracts/mirror_staking/src/contract.rs b/contracts/mirror_staking/src/contract.rs deleted file mode 100644 index 50eebacd1..000000000 --- a/contracts/mirror_staking/src/contract.rs +++ /dev/null @@ -1,276 +0,0 @@ -use cosmwasm_std::{ - from_binary, log, to_binary, Api, Binary, Decimal, Env, Extern, HandleResponse, HandleResult, - HumanAddr, InitResponse, MigrateResponse, MigrateResult, Querier, StdError, StdResult, Storage, - Uint128, -}; - -use mirror_protocol::staking::{ - ConfigResponse, Cw20HookMsg, HandleMsg, InitMsg, MigrateMsg, PoolInfoResponse, QueryMsg, -}; - -use crate::migration::{migrate_config, migrate_pool_infos}; -use crate::rewards::{adjust_premium, deposit_reward, query_reward_info, withdraw_reward}; -use crate::staking::{ - auto_stake, auto_stake_hook, bond, decrease_short_token, increase_short_token, unbond, -}; -use crate::state::{read_config, read_pool_info, store_config, store_pool_info, Config, PoolInfo}; - -use cw20::Cw20ReceiveMsg; - -pub fn init( - deps: &mut Extern, - _env: Env, - msg: InitMsg, -) -> StdResult { - store_config( - &mut deps.storage, - &Config { - owner: deps.api.canonical_address(&msg.owner)?, - mirror_token: deps.api.canonical_address(&msg.mirror_token)?, - mint_contract: deps.api.canonical_address(&msg.mint_contract)?, - oracle_contract: deps.api.canonical_address(&msg.oracle_contract)?, - terraswap_factory: deps.api.canonical_address(&msg.terraswap_factory)?, - base_denom: msg.base_denom, - premium_min_update_interval: msg.premium_min_update_interval, - }, - )?; - - Ok(InitResponse::default()) -} - -pub fn handle( - deps: &mut Extern, - env: Env, - msg: HandleMsg, -) -> StdResult { - match msg { - HandleMsg::Receive(msg) => receive_cw20(deps, env, msg), - HandleMsg::UpdateConfig { - owner, - premium_min_update_interval, - } => update_config(deps, env, owner, premium_min_update_interval), - HandleMsg::RegisterAsset { - asset_token, - staking_token, - } => register_asset(deps, env, asset_token, staking_token), - HandleMsg::Unbond { - asset_token, - amount, - } => unbond(deps, env.message.sender, asset_token, amount), - HandleMsg::Withdraw { asset_token } => withdraw_reward(deps, env, asset_token), - HandleMsg::AdjustPremium { asset_tokens } => adjust_premium(deps, env, asset_tokens), - HandleMsg::IncreaseShortToken { - staker_addr, - asset_token, - amount, - } => increase_short_token(deps, env, staker_addr, asset_token, amount), - HandleMsg::DecreaseShortToken { - staker_addr, - asset_token, - amount, - } => decrease_short_token(deps, env, staker_addr, asset_token, amount), - HandleMsg::AutoStake { - assets, - slippage_tolerance, - } => auto_stake(deps, env, assets, slippage_tolerance), - HandleMsg::AutoStakeHook { - asset_token, - staking_token, - staker_addr, - prev_staking_token_amount, - } => auto_stake_hook( - deps, - env, - asset_token, - staking_token, - staker_addr, - prev_staking_token_amount, - ), - } -} - -pub fn receive_cw20( - deps: &mut Extern, - env: Env, - cw20_msg: Cw20ReceiveMsg, -) -> HandleResult { - if let Some(msg) = cw20_msg.msg { - let config: Config = read_config(&deps.storage)?; - - match from_binary(&msg)? { - Cw20HookMsg::Bond { asset_token } => { - let pool_info: PoolInfo = - read_pool_info(&deps.storage, &deps.api.canonical_address(&asset_token)?)?; - - // only staking token contract can execute this message - if pool_info.staking_token != deps.api.canonical_address(&env.message.sender)? { - return Err(StdError::unauthorized()); - } - - bond(deps, env, cw20_msg.sender, asset_token, cw20_msg.amount) - } - Cw20HookMsg::DepositReward { rewards } => { - // only reward token contract can execute this message - if config.mirror_token != deps.api.canonical_address(&env.message.sender)? { - return Err(StdError::unauthorized()); - } - - let mut rewards_amount = Uint128::zero(); - for (_, amount) in rewards.iter() { - rewards_amount += *amount; - } - - if rewards_amount != cw20_msg.amount { - return Err(StdError::generic_err("rewards amount miss matched")); - } - - deposit_reward(deps, rewards, rewards_amount) - } - } - } else { - Err(StdError::generic_err("data should be given")) - } -} - -pub fn update_config( - deps: &mut Extern, - env: Env, - owner: Option, - premium_min_update_interval: Option, -) -> StdResult { - let mut config: Config = read_config(&deps.storage)?; - - if deps.api.canonical_address(&env.message.sender)? != config.owner { - return Err(StdError::unauthorized()); - } - - if let Some(owner) = owner { - config.owner = deps.api.canonical_address(&owner)?; - } - - if let Some(premium_min_update_interval) = premium_min_update_interval { - config.premium_min_update_interval = premium_min_update_interval; - } - - store_config(&mut deps.storage, &config)?; - Ok(HandleResponse { - messages: vec![], - log: vec![log("action", "update_config")], - data: None, - }) -} - -fn register_asset( - deps: &mut Extern, - env: Env, - asset_token: HumanAddr, - staking_token: HumanAddr, -) -> HandleResult { - let config: Config = read_config(&deps.storage)?; - let asset_token_raw = deps.api.canonical_address(&asset_token)?; - - if config.owner != deps.api.canonical_address(&env.message.sender)? { - return Err(StdError::unauthorized()); - } - - if read_pool_info(&deps.storage, &asset_token_raw).is_ok() { - return Err(StdError::generic_err("Asset was already registered")); - } - - store_pool_info( - &mut deps.storage, - &asset_token_raw, - &PoolInfo { - staking_token: deps.api.canonical_address(&staking_token)?, - total_bond_amount: Uint128::zero(), - total_short_amount: Uint128::zero(), - reward_index: Decimal::zero(), - short_reward_index: Decimal::zero(), - pending_reward: Uint128::zero(), - short_pending_reward: Uint128::zero(), - premium_rate: Decimal::zero(), - short_reward_weight: Decimal::zero(), - premium_updated_time: 0, - }, - )?; - - Ok(HandleResponse { - messages: vec![], - log: vec![ - log("action", "register_asset"), - log("asset_token", asset_token.as_str()), - ], - data: None, - }) -} - -pub fn query( - deps: &Extern, - msg: QueryMsg, -) -> StdResult { - match msg { - QueryMsg::Config {} => to_binary(&query_config(deps)?), - QueryMsg::PoolInfo { asset_token } => to_binary(&query_pool_info(deps, asset_token)?), - QueryMsg::RewardInfo { - staker_addr, - asset_token, - } => to_binary(&query_reward_info(deps, staker_addr, asset_token)?), - } -} - -pub fn query_config( - deps: &Extern, -) -> StdResult { - let state = read_config(&deps.storage)?; - let resp = ConfigResponse { - owner: deps.api.human_address(&state.owner)?, - mirror_token: deps.api.human_address(&state.mirror_token)?, - mint_contract: deps.api.human_address(&state.mint_contract)?, - oracle_contract: deps.api.human_address(&state.oracle_contract)?, - terraswap_factory: deps.api.human_address(&state.terraswap_factory)?, - base_denom: state.base_denom, - premium_min_update_interval: state.premium_min_update_interval, - }; - - Ok(resp) -} - -pub fn query_pool_info( - deps: &Extern, - asset_token: HumanAddr, -) -> StdResult { - let asset_token_raw = deps.api.canonical_address(&asset_token)?; - let pool_info: PoolInfo = read_pool_info(&deps.storage, &asset_token_raw)?; - Ok(PoolInfoResponse { - asset_token, - staking_token: deps.api.human_address(&pool_info.staking_token)?, - total_bond_amount: pool_info.total_bond_amount, - total_short_amount: pool_info.total_short_amount, - reward_index: pool_info.reward_index, - short_reward_index: pool_info.short_reward_index, - pending_reward: pool_info.pending_reward, - short_pending_reward: pool_info.short_pending_reward, - premium_rate: pool_info.premium_rate, - short_reward_weight: pool_info.short_reward_weight, - premium_updated_time: pool_info.premium_updated_time, - }) -} - -pub fn migrate( - deps: &mut Extern, - _env: Env, - msg: MigrateMsg, -) -> MigrateResult { - migrate_config( - &mut deps.storage, - deps.api.canonical_address(&msg.mint_contract)?, - deps.api.canonical_address(&msg.oracle_contract)?, - deps.api.canonical_address(&msg.terraswap_factory)?, - msg.base_denom, - msg.premium_min_update_interval, - )?; - - migrate_pool_infos(&mut deps.storage)?; - - Ok(MigrateResponse::default()) -} diff --git a/contracts/mirror_staking/src/contract_test.rs b/contracts/mirror_staking/src/contract_test.rs deleted file mode 100644 index 72f6ac36e..000000000 --- a/contracts/mirror_staking/src/contract_test.rs +++ /dev/null @@ -1,169 +0,0 @@ -#[cfg(test)] -mod tests { - - use crate::contract::{handle, init, query}; - use cosmwasm_std::testing::{mock_dependencies, mock_env}; - use cosmwasm_std::{from_binary, log, Decimal, HumanAddr, StdError, Uint128}; - - use mirror_protocol::staking::{ - ConfigResponse, HandleMsg, InitMsg, PoolInfoResponse, QueryMsg, - }; - - #[test] - fn proper_initialization() { - let mut deps = mock_dependencies(20, &[]); - - let msg = InitMsg { - owner: HumanAddr::from("owner"), - mirror_token: HumanAddr::from("reward"), - mint_contract: HumanAddr::from("mint"), - oracle_contract: HumanAddr::from("oracle"), - terraswap_factory: HumanAddr::from("terraswap_factory"), - base_denom: "uusd".to_string(), - premium_min_update_interval: 3600, - }; - - let env = mock_env("addr", &[]); - - // we can just call .unwrap() to assert this was a success - let _res = init(&mut deps, env, msg).unwrap(); - - // it worked, let's query the state - let res = query(&deps, QueryMsg::Config {}).unwrap(); - let config: ConfigResponse = from_binary(&res).unwrap(); - assert_eq!( - ConfigResponse { - owner: HumanAddr::from("owner"), - mirror_token: HumanAddr::from("reward"), - mint_contract: HumanAddr::from("mint"), - oracle_contract: HumanAddr::from("oracle"), - terraswap_factory: HumanAddr::from("terraswap_factory"), - base_denom: "uusd".to_string(), - premium_min_update_interval: 3600, - }, - config - ); - } - - #[test] - fn update_config() { - let mut deps = mock_dependencies(20, &[]); - - let msg = InitMsg { - owner: HumanAddr::from("owner"), - mirror_token: HumanAddr::from("reward"), - mint_contract: HumanAddr::from("mint"), - oracle_contract: HumanAddr::from("oracle"), - terraswap_factory: HumanAddr::from("terraswap_factory"), - base_denom: "uusd".to_string(), - premium_min_update_interval: 3600, - }; - - let env = mock_env("addr", &[]); - let _res = init(&mut deps, env.clone(), msg).unwrap(); - - // update owner - let env = mock_env("owner", &[]); - let msg = HandleMsg::UpdateConfig { - owner: Some(HumanAddr("owner2".to_string())), - premium_min_update_interval: Some(7200), - }; - - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!(0, res.messages.len()); - - // it worked, let's query the state - let res = query(&deps, QueryMsg::Config {}).unwrap(); - let config: ConfigResponse = from_binary(&res).unwrap(); - assert_eq!( - ConfigResponse { - owner: HumanAddr::from("owner2"), - mirror_token: HumanAddr::from("reward"), - mint_contract: HumanAddr::from("mint"), - oracle_contract: HumanAddr::from("oracle"), - terraswap_factory: HumanAddr::from("terraswap_factory"), - base_denom: "uusd".to_string(), - premium_min_update_interval: 7200, - }, - config - ); - - // unauthorized err - let env = mock_env("owner", &[]); - let msg = HandleMsg::UpdateConfig { - owner: None, - premium_min_update_interval: Some(7200), - }; - - let res = handle(&mut deps, env, msg); - match res { - Err(StdError::Unauthorized { .. }) => {} - _ => panic!("Must return unauthorized error"), - } - } - - #[test] - fn test_register() { - let mut deps = mock_dependencies(20, &[]); - - let msg = InitMsg { - owner: HumanAddr::from("owner"), - mirror_token: HumanAddr::from("reward"), - mint_contract: HumanAddr::from("mint"), - oracle_contract: HumanAddr::from("oracle"), - terraswap_factory: HumanAddr::from("terraswap_factory"), - base_denom: "uusd".to_string(), - premium_min_update_interval: 3600, - }; - - let env = mock_env("addr", &[]); - - // we can just call .unwrap() to assert this was a success - let _res = init(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset"), - staking_token: HumanAddr::from("staking"), - }; - - // failed with unauthorized error - let env = mock_env("addr", &[]); - let res = handle(&mut deps, env, msg.clone()).unwrap_err(); - match res { - StdError::Unauthorized { .. } => {} - _ => panic!("DO NOT ENTER HERE"), - } - - let env = mock_env("owner", &[]); - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!( - res.log, - vec![log("action", "register_asset"), log("asset_token", "asset"),] - ); - - let res = query( - &deps, - QueryMsg::PoolInfo { - asset_token: HumanAddr::from("asset"), - }, - ) - .unwrap(); - let pool_info: PoolInfoResponse = from_binary(&res).unwrap(); - assert_eq!( - pool_info, - PoolInfoResponse { - asset_token: HumanAddr::from("asset"), - staking_token: HumanAddr::from("staking"), - total_bond_amount: Uint128::zero(), - total_short_amount: Uint128::zero(), - reward_index: Decimal::zero(), - short_reward_index: Decimal::zero(), - pending_reward: Uint128::zero(), - short_pending_reward: Uint128::zero(), - premium_rate: Decimal::zero(), - short_reward_weight: Decimal::zero(), - premium_updated_time: 0, - } - ); - } -} diff --git a/contracts/mirror_staking/src/lib.rs b/contracts/mirror_staking/src/lib.rs deleted file mode 100644 index 61181602e..000000000 --- a/contracts/mirror_staking/src/lib.rs +++ /dev/null @@ -1,20 +0,0 @@ -pub mod contract; -mod math; -mod migration; -mod querier; -mod rewards; -mod staking; -mod state; - -// Testing files -mod contract_test; -mod math_test; -mod migration_test; -mod reward_test; -mod staking_test; - -#[cfg(test)] -mod mock_querier; - -#[cfg(target_arch = "wasm32")] -cosmwasm_std::create_entry_points_with_migration!(contract); diff --git a/contracts/mirror_staking/src/math.rs b/contracts/mirror_staking/src/math.rs deleted file mode 100644 index 917a1703d..000000000 --- a/contracts/mirror_staking/src/math.rs +++ /dev/null @@ -1,158 +0,0 @@ -use cosmwasm_std::{Decimal, Uint128}; - -const DECIMAL_FRACTIONAL: Uint128 = Uint128(1_000_000_000u128); - -/// return a / b -pub fn decimal_division(a: Decimal, b: Decimal) -> Decimal { - Decimal::from_ratio(DECIMAL_FRACTIONAL * a, b * DECIMAL_FRACTIONAL) -} - -pub fn decimal_subtraction(a: Decimal, b: Decimal) -> Decimal { - Decimal::from_ratio( - (DECIMAL_FRACTIONAL * a - DECIMAL_FRACTIONAL * b).unwrap(), - DECIMAL_FRACTIONAL, - ) -} - -pub fn decimal_multiplication(a: Decimal, b: Decimal) -> Decimal { - Decimal::from_ratio(a * DECIMAL_FRACTIONAL * b, DECIMAL_FRACTIONAL) -} - -pub fn reverse_decimal(decimal: Decimal) -> Decimal { - Decimal::from_ratio(DECIMAL_FRACTIONAL, decimal * DECIMAL_FRACTIONAL) -} - -// p == premium * 100 -fn erf_plus_one(sign_x: Sign, x: Decimal) -> Decimal { - let e6 = 1000000u128; - let e10 = 10000000000u128; - let a1 = Decimal::from_ratio(705230784u128, e10); - let a2 = Decimal::from_ratio(422820123u128, e10); - let a3 = Decimal::from_ratio(92705272u128, e10); - let a4 = Decimal::from_ratio(1520143u128, e10); - let a5 = Decimal::from_ratio(2765672u128, e10); - let a6 = Decimal::from_ratio(430638u128, e10); - - let one = Decimal::one(); - let two = one + one; - - // ((((((a6 * x) + a5) * x + a4 ) * x + a3) * x + a2) * x + a1) * x + 1 - let sign = sign_x.clone(); - let num = decimal_multiplication(a6, x); - let (sign, num) = sum_and_multiply_x(sign, Sign::Positive, sign_x.clone(), num, a5, x); - let (sign, num) = sum_and_multiply_x(sign, Sign::Positive, sign_x.clone(), num, a4, x); - let (sign, num) = sum_and_multiply_x(sign, Sign::Positive, sign_x.clone(), num, a3, x); - let (sign, num) = sum_and_multiply_x(sign, Sign::Positive, sign_x.clone(), num, a2, x); - let (sign, num) = sum_and_multiply_x(sign, Sign::Positive, sign_x, num, a1, x); - let num = if sign == Sign::Positive { - num + one - } else if num > one { - decimal_subtraction(num, one) - } else { - decimal_subtraction(one, num) - }; - - // ignore sign - let num2 = decimal_multiplication(num, num); - let num4 = decimal_multiplication(num2, num2); - let num8 = decimal_multiplication(num4, num4); - let num16 = decimal_multiplication(num8, num8); - let reverse_num16 = reverse_decimal(num16); - if reverse_num16 > two { - return Decimal::zero(); - } - - let erf_plus_one = decimal_subtraction(two, reverse_num16); - - // maximum error: 3 * 10^-7 - // so only use 6 decimal digits - Decimal::from_ratio(erf_plus_one * e6.into(), e6) -} - -// (1 + erf(premium * 100)) / 5 -pub fn short_reward_weight(premium_rate: Decimal) -> Decimal { - if premium_rate > Decimal::percent(7) { - return Decimal::percent(40); - } - - let one = Decimal::one(); - let two = one + one; - let e10 = 10000000000u128; - let sqrt_two = Decimal::from_ratio(14142135624u128, e10); - - let p = decimal_multiplication(premium_rate, Decimal::from_ratio(100u128, 1u128)); - let (sign_x, x) = if p > two { - ( - Sign::Positive, - decimal_division(decimal_subtraction(p, two), sqrt_two), - ) - } else { - ( - Sign::Negative, - decimal_division(decimal_subtraction(two, p), sqrt_two), - ) - }; - - return decimal_division(erf_plus_one(sign_x, x), Decimal::from_ratio(5u128, 1u128)); -} - -#[derive(PartialEq, Clone)] -enum Sign { - Positive, - Negative, -} - -// return (sign, result) -fn sum_and_multiply_x( - sign_1: Sign, - sign_2: Sign, - sign_x: Sign, - num1: Decimal, - num2: Decimal, - x: Decimal, -) -> (Sign, Decimal) { - if sign_1 == sign_2 { - let val = decimal_multiplication(num1 + num2, x); - if sign_1 == sign_x { - (Sign::Positive, val) - } else { - (Sign::Negative, val) - } - } else if num1 > num2 { - let val = decimal_multiplication(decimal_subtraction(num1, num2), x); - if sign_1 == sign_x { - (Sign::Positive, val) - } else { - (Sign::Negative, val) - } - } else { - let val = decimal_multiplication(decimal_subtraction(num2, num1), x); - if sign_2 == sign_x { - (Sign::Positive, val) - } else { - (Sign::Negative, val) - } - } -} - -#[test] -fn erf_plus_one_test() { - let e6 = 1000000u128; - let e10 = 10000000000u128; - assert_eq!( - erf_plus_one(Sign::Negative, Decimal::from_ratio(21213203435u128, e10)), - Decimal::zero() - ); - assert_eq!( - erf_plus_one(Sign::Negative, Decimal::from_ratio(14142135623u128, e10)), - Decimal::from_ratio(013090u128, e6) - ); - assert_eq!( - erf_plus_one(Sign::Positive, Decimal::zero()), - Decimal::from_ratio(1000000u128, e6) - ); - assert_eq!( - erf_plus_one(Sign::Positive, Decimal::from_ratio(14142135623u128, e10)), - Decimal::from_ratio(1954499u128, e6) - ); -} diff --git a/contracts/mirror_staking/src/math_test.rs b/contracts/mirror_staking/src/math_test.rs deleted file mode 100644 index 807c52a1b..000000000 --- a/contracts/mirror_staking/src/math_test.rs +++ /dev/null @@ -1,35 +0,0 @@ -#[cfg(test)] -mod tests { - use crate::math::short_reward_weight; - use cosmwasm_std::Decimal; - - #[test] - fn short_reward_weight_test() { - let e6 = 1000000u128; - let e7 = 10000000u128; - assert_eq!( - short_reward_weight(Decimal::zero()), - Decimal::from_ratio(002618u128, e6) - ); - assert_eq!( - short_reward_weight(Decimal::percent(1)), - Decimal::from_ratio(0634168u128, e7), - ); - assert_eq!( - short_reward_weight(Decimal::percent(2)), - Decimal::percent(20) - ); - assert_eq!( - short_reward_weight(Decimal::percent(4)), - Decimal::from_ratio(3908998u128, e7) - ); - assert_eq!( - short_reward_weight(Decimal::percent(8)), - Decimal::percent(40) - ); - assert_eq!( - short_reward_weight(Decimal::percent(15)), - Decimal::percent(40) - ); - } -} diff --git a/contracts/mirror_staking/src/migration.rs b/contracts/mirror_staking/src/migration.rs deleted file mode 100644 index 18f30483c..000000000 --- a/contracts/mirror_staking/src/migration.rs +++ /dev/null @@ -1,87 +0,0 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use cosmwasm_std::{CanonicalAddr, Decimal, Order, StdResult, Storage, Uint128}; -use cosmwasm_storage::{singleton_read, ReadonlyBucket}; - -use crate::state::{store_config, store_pool_info, Config, PoolInfo, KEY_CONFIG, PREFIX_POOL_INFO}; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct LegacyConfig { - pub owner: CanonicalAddr, - pub mirror_token: CanonicalAddr, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct LegacyPoolInfo { - pub staking_token: CanonicalAddr, - pub pending_reward: Uint128, // not distributed amount due to zero bonding - pub total_bond_amount: Uint128, - pub reward_index: Decimal, -} - -fn read_legacy_config(storage: &S) -> StdResult { - singleton_read(storage, KEY_CONFIG).load() -} - -fn read_legacy_pool_infos( - storage: &S, -) -> StdResult> { - let pool_info_bucket: ReadonlyBucket = - ReadonlyBucket::new(PREFIX_POOL_INFO, storage); - pool_info_bucket - .range(None, None, Order::Ascending) - .map(|item| { - let (k, v) = item?; - Ok((CanonicalAddr::from(k), v)) - }) - .collect() -} - -pub fn migrate_config( - storage: &mut S, - mint_contract: CanonicalAddr, - oracle_contract: CanonicalAddr, - terraswap_factory: CanonicalAddr, - base_denom: String, - premium_min_update_interval: u64, -) -> StdResult<()> { - let legacy_config = read_legacy_config(storage)?; - - store_config( - storage, - &Config { - owner: legacy_config.owner, - mirror_token: legacy_config.mirror_token, - mint_contract, - oracle_contract, - terraswap_factory, - base_denom, - premium_min_update_interval, - }, - ) -} - -pub fn migrate_pool_infos(storage: &mut S) -> StdResult<()> { - let legacy_pool_infos: Vec<(CanonicalAddr, LegacyPoolInfo)> = read_legacy_pool_infos(storage)?; - for (asset_token, legacy_pool_info) in legacy_pool_infos.iter() { - store_pool_info( - storage, - &asset_token, - &PoolInfo { - staking_token: legacy_pool_info.staking_token.clone(), - pending_reward: legacy_pool_info.pending_reward, - total_bond_amount: legacy_pool_info.total_bond_amount, - reward_index: legacy_pool_info.reward_index, - short_pending_reward: Uint128::zero(), - total_short_amount: Uint128::zero(), - short_reward_index: Decimal::zero(), - premium_rate: Decimal::zero(), - short_reward_weight: Decimal::zero(), - premium_updated_time: 0, - }, - )?; - } - - Ok(()) -} diff --git a/contracts/mirror_staking/src/migration_test.rs b/contracts/mirror_staking/src/migration_test.rs deleted file mode 100644 index 4d4dac18d..000000000 --- a/contracts/mirror_staking/src/migration_test.rs +++ /dev/null @@ -1,181 +0,0 @@ -#[cfg(test)] -mod tests { - use cosmwasm_std::testing::mock_dependencies; - use cosmwasm_std::Api; - use cosmwasm_std::{CanonicalAddr, Decimal, HumanAddr, StdResult, Storage, Uint128}; - use cosmwasm_storage::{singleton, Bucket}; - - use crate::migration::{migrate_config, migrate_pool_infos, LegacyConfig, LegacyPoolInfo}; - use crate::state::{ - read_config, read_pool_info, Config, PoolInfo, KEY_CONFIG, PREFIX_POOL_INFO, - }; - - fn store_legacy_config(storage: &mut S, config: &LegacyConfig) -> StdResult<()> { - singleton(storage, KEY_CONFIG).save(config) - } - - fn store_legacy_pool_info( - storage: &mut S, - asset_token: &CanonicalAddr, - pool_info: &LegacyPoolInfo, - ) -> StdResult<()> { - Bucket::new(PREFIX_POOL_INFO, storage).save(asset_token.as_slice(), pool_info) - } - - #[test] - fn test_config_migration() { - let mut deps = mock_dependencies(20, &[]); - store_legacy_config( - &mut deps.storage, - &LegacyConfig { - owner: deps - .api - .canonical_address(&HumanAddr::from("owner")) - .unwrap(), - mirror_token: deps - .api - .canonical_address(&HumanAddr::from("mirror")) - .unwrap(), - }, - ) - .unwrap(); - - migrate_config( - &mut deps.storage, - deps.api - .canonical_address(&HumanAddr::from("mint")) - .unwrap(), - deps.api - .canonical_address(&HumanAddr::from("oracle")) - .unwrap(), - deps.api - .canonical_address(&HumanAddr::from("terraswap_factory")) - .unwrap(), - "uusd".to_string(), - 7200, - ) - .unwrap(); - - assert_eq!( - Config { - owner: deps - .api - .canonical_address(&HumanAddr::from("owner")) - .unwrap(), - mirror_token: deps - .api - .canonical_address(&HumanAddr::from("mirror")) - .unwrap(), - mint_contract: deps - .api - .canonical_address(&HumanAddr::from("mint")) - .unwrap(), - oracle_contract: deps - .api - .canonical_address(&HumanAddr::from("oracle")) - .unwrap(), - terraswap_factory: deps - .api - .canonical_address(&HumanAddr::from("terraswap_factory")) - .unwrap(), - base_denom: "uusd".to_string(), - premium_min_update_interval: 7200, - }, - read_config(&deps.storage).unwrap() - ); - } - - #[test] - fn test_pool_infos_migration() { - let mut deps = mock_dependencies(20, &[]); - store_legacy_pool_info( - &mut deps.storage, - &deps - .api - .canonical_address(&HumanAddr::from("asset1")) - .unwrap(), - &LegacyPoolInfo { - staking_token: deps - .api - .canonical_address(&HumanAddr::from("staking1")) - .unwrap(), - pending_reward: Uint128::zero(), - total_bond_amount: Uint128::zero(), - reward_index: Decimal::zero(), - }, - ) - .unwrap(); - - store_legacy_pool_info( - &mut deps.storage, - &deps - .api - .canonical_address(&HumanAddr::from("asset2")) - .unwrap(), - &LegacyPoolInfo { - staking_token: deps - .api - .canonical_address(&HumanAddr::from("staking2")) - .unwrap(), - pending_reward: Uint128::zero(), - total_bond_amount: Uint128::zero(), - reward_index: Decimal::zero(), - }, - ) - .unwrap(); - - migrate_pool_infos(&mut deps.storage).unwrap(); - - assert_eq!( - PoolInfo { - staking_token: deps - .api - .canonical_address(&HumanAddr::from("staking1")) - .unwrap(), - pending_reward: Uint128::zero(), - total_bond_amount: Uint128::zero(), - reward_index: Decimal::zero(), - short_pending_reward: Uint128::zero(), - total_short_amount: Uint128::zero(), - short_reward_index: Decimal::zero(), - premium_rate: Decimal::zero(), - short_reward_weight: Decimal::zero(), - premium_updated_time: 0, - }, - read_pool_info( - &deps.storage, - &deps - .api - .canonical_address(&HumanAddr::from("asset1")) - .unwrap(), - ) - .unwrap() - ); - - assert_eq!( - PoolInfo { - staking_token: deps - .api - .canonical_address(&HumanAddr::from("staking2")) - .unwrap(), - pending_reward: Uint128::zero(), - total_bond_amount: Uint128::zero(), - reward_index: Decimal::zero(), - short_pending_reward: Uint128::zero(), - total_short_amount: Uint128::zero(), - short_reward_index: Decimal::zero(), - premium_rate: Decimal::zero(), - short_reward_weight: Decimal::zero(), - premium_updated_time: 0, - }, - read_pool_info( - &deps.storage, - &deps - .api - .canonical_address(&HumanAddr::from("asset2")) - .unwrap(), - ) - .unwrap() - ); - } -} diff --git a/contracts/mirror_staking/src/mock_querier.rs b/contracts/mirror_staking/src/mock_querier.rs deleted file mode 100644 index 373aa270e..000000000 --- a/contracts/mirror_staking/src/mock_querier.rs +++ /dev/null @@ -1,165 +0,0 @@ -use cosmwasm_std::testing::{MockApi, MockQuerier, MockStorage, MOCK_CONTRACT_ADDR}; -use cosmwasm_std::{ - from_binary, from_slice, to_binary, Api, Coin, Decimal, Extern, HumanAddr, Querier, - QuerierResult, QueryRequest, SystemError, Uint128, WasmQuery, -}; -use cosmwasm_storage::to_length_prefixed; -use mirror_protocol::oracle::{PriceResponse, QueryMsg as OracleQueryMsg}; -use terra_cosmwasm::{TaxCapResponse, TaxRateResponse, TerraQuery, TerraQueryWrapper, TerraRoute}; -use terraswap::{ - asset::Asset, asset::AssetInfo, asset::PairInfo, factory::QueryMsg as FactoryQueryMsg, - pair::PoolResponse, pair::QueryMsg as PairQueryMsg, -}; - -pub struct WasmMockQuerier { - base: MockQuerier, - pair_addr: HumanAddr, - pool_assets: [Asset; 2], - oracle_price: Decimal, - token_balance: Uint128, - tax: (Decimal, Uint128), -} - -pub fn mock_dependencies_with_querier( - canonical_length: usize, - contract_balance: &[Coin], -) -> Extern { - let contract_addr = HumanAddr::from(MOCK_CONTRACT_ADDR); - let custom_querier: WasmMockQuerier = WasmMockQuerier::new( - MockQuerier::new(&[(&contract_addr, contract_balance)]), - MockApi::new(canonical_length), - canonical_length, - ); - - Extern { - storage: MockStorage::default(), - api: MockApi::new(canonical_length), - querier: custom_querier, - } -} - -impl Querier for WasmMockQuerier { - fn raw_query(&self, bin_request: &[u8]) -> QuerierResult { - // MockQuerier doesn't support Custom, so we ignore it completely here - let request: QueryRequest = match from_slice(bin_request) { - Ok(v) => v, - Err(e) => { - return Err(SystemError::InvalidRequest { - error: format!("Parsing query request: {}", e), - request: bin_request.into(), - }) - } - }; - self.handle_query(&request) - } -} - -impl WasmMockQuerier { - pub fn handle_query(&self, request: &QueryRequest) -> QuerierResult { - match &request { - QueryRequest::Custom(TerraQueryWrapper { route, query_data }) => { - if route == &TerraRoute::Treasury { - match query_data { - TerraQuery::TaxRate {} => { - let res = TaxRateResponse { rate: self.tax.0 }; - Ok(to_binary(&res)) - } - TerraQuery::TaxCap { .. } => { - let res = TaxCapResponse { cap: self.tax.1 }; - Ok(to_binary(&res)) - } - _ => panic!("DO NOT ENTER HERE"), - } - } else { - panic!("DO NOT ENTER HERE") - } - } - QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: _, - msg, - }) => match from_binary(&msg) { - Ok(FactoryQueryMsg::Pair { asset_infos }) => Ok(to_binary(&PairInfo { - asset_infos: asset_infos.clone(), - contract_addr: self.pair_addr.clone(), - liquidity_token: HumanAddr::from("lptoken"), - })), - _ => match from_binary(&msg) { - Ok(PairQueryMsg::Pool {}) => Ok(to_binary(&PoolResponse { - assets: self.pool_assets.clone(), - total_share: Uint128::zero(), - })), - _ => match from_binary(&msg) { - Ok(OracleQueryMsg::Price { - base_asset: _, - quote_asset: _, - }) => Ok(to_binary(&PriceResponse { - rate: self.oracle_price, - last_updated_base: 100, - last_updated_quote: 100, - })), - _ => panic!("DO NOT ENTER HERE"), - }, - }, - }, - QueryRequest::Wasm(WasmQuery::Raw { - contract_addr: _, - key, - }) => { - let key: &[u8] = key.as_slice(); - let prefix_balance = to_length_prefixed(b"balance").to_vec(); - if key[..prefix_balance.len()].to_vec() == prefix_balance { - Ok(to_binary(&to_binary(&self.token_balance).unwrap())) - } else { - panic!("DO NOT ENTER HERE") - } - } - _ => self.base.handle_query(request), - } - } -} - -impl WasmMockQuerier { - pub fn new( - base: MockQuerier, - _api: A, - _canonical_length: usize, - ) -> Self { - WasmMockQuerier { - base, - pair_addr: HumanAddr::default(), - pool_assets: [ - Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: Uint128::zero(), - }, - Asset { - info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset"), - }, - amount: Uint128::zero(), - }, - ], - oracle_price: Decimal::zero(), - token_balance: Uint128::zero(), - tax: (Decimal::percent(1), Uint128(1000000)), - } - } - - pub fn with_pair_info(&mut self, pair_addr: HumanAddr) { - self.pair_addr = pair_addr; - } - - pub fn with_pool_assets(&mut self, pool_assets: [Asset; 2]) { - self.pool_assets = pool_assets; - } - - pub fn with_oracle_price(&mut self, oracle_price: Decimal) { - self.oracle_price = oracle_price; - } - - pub fn with_token_balance(&mut self, token_balance: Uint128) { - self.token_balance = token_balance; - } -} diff --git a/contracts/mirror_staking/src/querier.rs b/contracts/mirror_staking/src/querier.rs deleted file mode 100644 index e21f576cc..000000000 --- a/contracts/mirror_staking/src/querier.rs +++ /dev/null @@ -1,73 +0,0 @@ -use cosmwasm_std::{ - to_binary, Api, Decimal, Extern, HumanAddr, Querier, QueryRequest, StdResult, Storage, - WasmQuery, -}; - -use terraswap::{ - asset::AssetInfo, asset::PairInfo, pair::PoolResponse, pair::QueryMsg as PairQueryMsg, - querier::query_pair_info, -}; - -use crate::math::{decimal_division, decimal_subtraction}; - -use mirror_protocol::{oracle::PriceResponse, oracle::QueryMsg as OracleQueryMsg}; - -pub fn compute_premium_rate( - deps: &Extern, - oracle_contract: &HumanAddr, - factory_contract: &HumanAddr, - asset_token: &HumanAddr, - base_denom: String, -) -> StdResult { - let pair_info: PairInfo = query_pair_info( - deps, - &factory_contract, - &[ - AssetInfo::NativeToken { - denom: base_denom.to_string(), - }, - AssetInfo::Token { - contract_addr: asset_token.clone(), - }, - ], - )?; - - let pool: PoolResponse = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: pair_info.contract_addr, - msg: to_binary(&PairQueryMsg::Pool {})?, - }))?; - - let terraswap_price: Decimal = if pool.assets[0].is_native_token() { - Decimal::from_ratio(pool.assets[0].amount, pool.assets[1].amount) - } else { - Decimal::from_ratio(pool.assets[1].amount, pool.assets[0].amount) - }; - let oracle_price: Decimal = - query_price(deps, oracle_contract, asset_token.to_string(), base_denom)?; - - if terraswap_price > oracle_price { - Ok(decimal_division( - decimal_subtraction(terraswap_price, oracle_price), - oracle_price, - )) - } else { - Ok(Decimal::zero()) - } -} - -pub fn query_price( - deps: &Extern, - oracle: &HumanAddr, - base_asset: String, - quote_asset: String, -) -> StdResult { - let res: PriceResponse = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: HumanAddr::from(oracle), - msg: to_binary(&OracleQueryMsg::Price { - base_asset, - quote_asset, - })?, - }))?; - - Ok(res.rate) -} diff --git a/contracts/mirror_staking/src/reward_test.rs b/contracts/mirror_staking/src/reward_test.rs deleted file mode 100644 index 459570aad..000000000 --- a/contracts/mirror_staking/src/reward_test.rs +++ /dev/null @@ -1,891 +0,0 @@ -#[cfg(test)] -mod tests { - use crate::contract::{handle, init, query}; - use crate::math::short_reward_weight; - use crate::mock_querier::mock_dependencies_with_querier; - use crate::state::{read_pool_info, rewards_read, store_pool_info, PoolInfo, RewardInfo}; - use cosmwasm_std::testing::{mock_dependencies, mock_env}; - use cosmwasm_std::{ - from_binary, to_binary, Api, CosmosMsg, Decimal, HumanAddr, StdError, Uint128, WasmMsg, - }; - use cw20::{Cw20HandleMsg, Cw20ReceiveMsg}; - use mirror_protocol::staking::{ - Cw20HookMsg, HandleMsg, InitMsg, PoolInfoResponse, QueryMsg, RewardInfoResponse, - RewardInfoResponseItem, - }; - use terraswap::asset::{Asset, AssetInfo}; - - #[test] - fn test_deposit_reward() { - let mut deps = mock_dependencies(20, &[]); - - let msg = InitMsg { - owner: HumanAddr::from("owner"), - mirror_token: HumanAddr::from("reward"), - mint_contract: HumanAddr::from("mint"), - oracle_contract: HumanAddr::from("oracle"), - terraswap_factory: HumanAddr::from("terraswap_factory"), - base_denom: "uusd".to_string(), - premium_min_update_interval: 3600, - }; - - let env = mock_env("addr", &[]); - let _res = init(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset"), - staking_token: HumanAddr::from("staking"), - }; - - let env = mock_env("owner", &[]); - let _res = handle(&mut deps, env, msg.clone()).unwrap(); - - // store 3% premium rate - let token_raw = deps - .api - .canonical_address(&HumanAddr::from("asset")) - .unwrap(); - let pool_info = read_pool_info(&deps.storage, &token_raw).unwrap(); - store_pool_info( - &mut deps.storage, - &token_raw, - &PoolInfo { - premium_rate: Decimal::percent(2), - short_reward_weight: short_reward_weight(Decimal::percent(2)), - ..pool_info - }, - ) - .unwrap(); - - // bond 100 tokens - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from("addr"), - amount: Uint128(100u128), - msg: Some( - to_binary(&Cw20HookMsg::Bond { - asset_token: HumanAddr::from("asset"), - }) - .unwrap(), - ), - }); - let env = mock_env("staking", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // bond 100 short token - let msg = HandleMsg::IncreaseShortToken { - asset_token: HumanAddr::from("asset"), - staker_addr: HumanAddr::from("addr"), - amount: Uint128(100u128), - }; - let env = mock_env("mint", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // factory deposit 100 reward tokens - // premium is 0, so rewards distributed 80:20 - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from("factory"), - amount: Uint128(100u128), - msg: Some( - to_binary(&Cw20HookMsg::DepositReward { - rewards: vec![(HumanAddr::from("asset"), Uint128(100u128))], - }) - .unwrap(), - ), - }); - let env = mock_env("reward", &[]); - let _res = handle(&mut deps, env.clone(), msg.clone()).unwrap(); - - // Check pool state - let res: PoolInfoResponse = from_binary( - &query( - &deps, - QueryMsg::PoolInfo { - asset_token: HumanAddr::from("asset"), - }, - ) - .unwrap(), - ) - .unwrap(); - assert_eq!( - res.clone(), - PoolInfoResponse { - total_bond_amount: Uint128(100u128), - total_short_amount: Uint128(100u128), - reward_index: Decimal::from_ratio(80u128, 100u128), - short_reward_index: Decimal::from_ratio(20u128, 100u128), - ..res - } - ); - - // if premium_rate is over threshold, distribution weight should be 60:40 - let asset_token_raw = deps - .api - .canonical_address(&HumanAddr::from("asset")) - .unwrap(); - let pool_info: PoolInfo = read_pool_info(&deps.storage, &asset_token_raw).unwrap(); - store_pool_info( - &mut deps.storage, - &asset_token_raw, - &PoolInfo { - reward_index: Decimal::zero(), - short_reward_index: Decimal::zero(), - premium_rate: Decimal::percent(10), - short_reward_weight: short_reward_weight(Decimal::percent(10)), - ..pool_info - }, - ) - .unwrap(); - - let _res = handle(&mut deps, env.clone(), msg.clone()).unwrap(); - - let res: PoolInfoResponse = from_binary( - &query( - &deps, - QueryMsg::PoolInfo { - asset_token: HumanAddr::from("asset"), - }, - ) - .unwrap(), - ) - .unwrap(); - assert_eq!( - res.clone(), - PoolInfoResponse { - total_bond_amount: Uint128(100u128), - total_short_amount: Uint128(100u128), - reward_index: Decimal::from_ratio(60u128, 100u128), - short_reward_index: Decimal::from_ratio(40u128, 100u128), - ..res - } - ); - } - - #[test] - fn test_deposit_reward_when_no_bonding() { - let mut deps = mock_dependencies(20, &[]); - - let msg = InitMsg { - owner: HumanAddr::from("owner"), - mirror_token: HumanAddr::from("reward"), - mint_contract: HumanAddr::from("mint"), - oracle_contract: HumanAddr::from("oracle"), - terraswap_factory: HumanAddr::from("terraswap_factory"), - base_denom: "uusd".to_string(), - premium_min_update_interval: 3600, - }; - - let env = mock_env("addr", &[]); - let _res = init(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset"), - staking_token: HumanAddr::from("staking"), - }; - - let env = mock_env("owner", &[]); - let _res = handle(&mut deps, env, msg.clone()).unwrap(); - - // store 3% premium rate - let token_raw = deps - .api - .canonical_address(&HumanAddr::from("asset")) - .unwrap(); - let pool_info = read_pool_info(&deps.storage, &token_raw).unwrap(); - store_pool_info( - &mut deps.storage, - &token_raw, - &PoolInfo { - premium_rate: Decimal::percent(2), - short_reward_weight: short_reward_weight(Decimal::percent(2)), - ..pool_info - }, - ) - .unwrap(); - - // factory deposit 100 reward tokens - // premium is 0, so rewards distributed 80:20 - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from("factory"), - amount: Uint128(100u128), - msg: Some( - to_binary(&Cw20HookMsg::DepositReward { - rewards: vec![(HumanAddr::from("asset"), Uint128(100u128))], - }) - .unwrap(), - ), - }); - let env = mock_env("reward", &[]); - let _res = handle(&mut deps, env.clone(), msg.clone()).unwrap(); - - // Check pool state - let res: PoolInfoResponse = from_binary( - &query( - &deps, - QueryMsg::PoolInfo { - asset_token: HumanAddr::from("asset"), - }, - ) - .unwrap(), - ) - .unwrap(); - assert_eq!( - res.clone(), - PoolInfoResponse { - reward_index: Decimal::zero(), - short_reward_index: Decimal::zero(), - pending_reward: Uint128(80u128), - short_pending_reward: Uint128(20u128), - ..res - } - ); - - // if premium_rate is over threshold, distribution weight should be 60:40 - let asset_token_raw = deps - .api - .canonical_address(&HumanAddr::from("asset")) - .unwrap(); - let pool_info: PoolInfo = read_pool_info(&deps.storage, &asset_token_raw).unwrap(); - store_pool_info( - &mut deps.storage, - &asset_token_raw, - &PoolInfo { - pending_reward: Uint128::zero(), - short_pending_reward: Uint128::zero(), - premium_rate: Decimal::percent(10), - short_reward_weight: short_reward_weight(Decimal::percent(10)), - ..pool_info - }, - ) - .unwrap(); - - let _res = handle(&mut deps, env.clone(), msg.clone()).unwrap(); - - let res: PoolInfoResponse = from_binary( - &query( - &deps, - QueryMsg::PoolInfo { - asset_token: HumanAddr::from("asset"), - }, - ) - .unwrap(), - ) - .unwrap(); - assert_eq!( - res.clone(), - PoolInfoResponse { - reward_index: Decimal::zero(), - short_reward_index: Decimal::zero(), - pending_reward: Uint128(60u128), - short_pending_reward: Uint128(40u128), - ..res - } - ); - } - - #[test] - fn test_before_share_changes() { - let mut deps = mock_dependencies(20, &[]); - - let msg = InitMsg { - owner: HumanAddr::from("owner"), - mirror_token: HumanAddr::from("reward"), - mint_contract: HumanAddr::from("mint"), - oracle_contract: HumanAddr::from("oracle"), - terraswap_factory: HumanAddr::from("terraswap_factory"), - base_denom: "uusd".to_string(), - premium_min_update_interval: 3600, - }; - - let env = mock_env("addr", &[]); - let _res = init(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset"), - staking_token: HumanAddr::from("staking"), - }; - - let env = mock_env("owner", &[]); - let _res = handle(&mut deps, env, msg.clone()).unwrap(); - - // store 3% premium rate - let token_raw = deps - .api - .canonical_address(&HumanAddr::from("asset")) - .unwrap(); - let pool_info = read_pool_info(&deps.storage, &token_raw).unwrap(); - store_pool_info( - &mut deps.storage, - &token_raw, - &PoolInfo { - premium_rate: Decimal::percent(2), - short_reward_weight: short_reward_weight(Decimal::percent(2)), - ..pool_info - }, - ) - .unwrap(); - - // bond 100 tokens - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from("addr"), - amount: Uint128(100u128), - msg: Some( - to_binary(&Cw20HookMsg::Bond { - asset_token: HumanAddr::from("asset"), - }) - .unwrap(), - ), - }); - let env = mock_env("staking", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // bond 100 short token - let msg = HandleMsg::IncreaseShortToken { - asset_token: HumanAddr::from("asset"), - staker_addr: HumanAddr::from("addr"), - amount: Uint128(100u128), - }; - let env = mock_env("mint", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // factory deposit 100 reward tokens - // premium is 0, so rewards distributed 80:20 - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from("factory"), - amount: Uint128(100u128), - msg: Some( - to_binary(&Cw20HookMsg::DepositReward { - rewards: vec![(HumanAddr::from("asset"), Uint128(100u128))], - }) - .unwrap(), - ), - }); - - let env = mock_env("reward", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - let asset_raw = deps - .api - .canonical_address(&HumanAddr::from("asset")) - .unwrap(); - let addr_raw = deps - .api - .canonical_address(&HumanAddr::from("addr")) - .unwrap(); - let reward_bucket = rewards_read(&deps.storage, &addr_raw, false); - let reward_info: RewardInfo = reward_bucket.load(asset_raw.as_slice()).unwrap(); - assert_eq!( - RewardInfo { - pending_reward: Uint128::zero(), - bond_amount: Uint128(100u128), - index: Decimal::zero(), - }, - reward_info - ); - - // bond 100 more tokens - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from("addr"), - amount: Uint128(100u128), - msg: Some( - to_binary(&Cw20HookMsg::Bond { - asset_token: HumanAddr::from("asset"), - }) - .unwrap(), - ), - }); - let env = mock_env("staking", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - let reward_bucket = rewards_read(&deps.storage, &addr_raw, false); - let reward_info: RewardInfo = reward_bucket.load(asset_raw.as_slice()).unwrap(); - assert_eq!( - RewardInfo { - pending_reward: Uint128(80u128), - bond_amount: Uint128(200u128), - index: Decimal::from_ratio(80u128, 100u128), - }, - reward_info - ); - - // factory deposit 100 reward tokens; = 0.8 + 0.4 = 1.2 is reward_index - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from("factory"), - amount: Uint128(100u128), - msg: Some( - to_binary(&Cw20HookMsg::DepositReward { - rewards: vec![(HumanAddr::from("asset"), Uint128(100u128))], - }) - .unwrap(), - ), - }); - let env = mock_env("reward", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // unbond - let msg = HandleMsg::Unbond { - asset_token: HumanAddr::from("asset"), - amount: Uint128(100u128), - }; - let env = mock_env("addr", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - let reward_bucket = rewards_read(&deps.storage, &addr_raw, false); - let reward_info: RewardInfo = reward_bucket.load(asset_raw.as_slice()).unwrap(); - assert_eq!( - RewardInfo { - pending_reward: Uint128(160u128), - bond_amount: Uint128(100u128), - index: Decimal::from_ratio(120u128, 100u128), - }, - reward_info - ); - } - - #[test] - fn test_withdraw() { - let mut deps = mock_dependencies(20, &[]); - - let msg = InitMsg { - owner: HumanAddr::from("owner"), - mirror_token: HumanAddr::from("reward"), - mint_contract: HumanAddr::from("mint"), - oracle_contract: HumanAddr::from("oracle"), - terraswap_factory: HumanAddr::from("terraswap_factory"), - base_denom: "uusd".to_string(), - premium_min_update_interval: 3600, - }; - - let env = mock_env("addr", &[]); - let _res = init(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset"), - staking_token: HumanAddr::from("staking"), - }; - - let env = mock_env("owner", &[]); - let _res = handle(&mut deps, env, msg.clone()).unwrap(); - - // store 3% premium rate - let token_raw = deps - .api - .canonical_address(&HumanAddr::from("asset")) - .unwrap(); - let pool_info = read_pool_info(&deps.storage, &token_raw).unwrap(); - store_pool_info( - &mut deps.storage, - &token_raw, - &PoolInfo { - premium_rate: Decimal::percent(2), - short_reward_weight: short_reward_weight(Decimal::percent(2)), - ..pool_info - }, - ) - .unwrap(); - - // bond 100 tokens - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from("addr"), - amount: Uint128(100u128), - msg: Some( - to_binary(&Cw20HookMsg::Bond { - asset_token: HumanAddr::from("asset"), - }) - .unwrap(), - ), - }); - let env = mock_env("staking", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // factory deposit 100 reward tokens - // premium_rate is zero; distribute weight => 80:20 - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from("factory"), - amount: Uint128(100u128), - msg: Some( - to_binary(&Cw20HookMsg::DepositReward { - rewards: vec![(HumanAddr::from("asset"), Uint128(100u128))], - }) - .unwrap(), - ), - }); - let env = mock_env("reward", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::Withdraw { - asset_token: Some(HumanAddr::from("asset")), - }; - let env = mock_env("addr", &[]); - let res = handle(&mut deps, env, msg).unwrap(); - - assert_eq!( - res.messages, - vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("reward"), - msg: to_binary(&Cw20HandleMsg::Transfer { - recipient: HumanAddr::from("addr"), - amount: Uint128(80u128), - }) - .unwrap(), - send: vec![], - })] - ); - } - - #[test] - fn withdraw_multiple_rewards() { - let mut deps = mock_dependencies(20, &[]); - - let msg = InitMsg { - owner: HumanAddr::from("owner"), - mirror_token: HumanAddr::from("reward"), - mint_contract: HumanAddr::from("mint"), - oracle_contract: HumanAddr::from("oracle"), - terraswap_factory: HumanAddr::from("terraswap_factory"), - base_denom: "uusd".to_string(), - premium_min_update_interval: 3600, - }; - - let env = mock_env("addr", &[]); - let _res = init(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset"), - staking_token: HumanAddr::from("staking"), - }; - - let env = mock_env("owner", &[]); - let _res = handle(&mut deps, env, msg.clone()).unwrap(); - - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset2"), - staking_token: HumanAddr::from("staking2"), - }; - - let env = mock_env("owner", &[]); - let _res = handle(&mut deps, env, msg.clone()).unwrap(); - - // store 3% premium rate - let token_raw = deps - .api - .canonical_address(&HumanAddr::from("asset")) - .unwrap(); - let pool_info = read_pool_info(&deps.storage, &token_raw).unwrap(); - store_pool_info( - &mut deps.storage, - &token_raw, - &PoolInfo { - premium_rate: Decimal::percent(2), - short_reward_weight: short_reward_weight(Decimal::percent(2)), - ..pool_info - }, - ) - .unwrap(); - - // store 3% premium rate for asset2 - let token_raw = deps - .api - .canonical_address(&HumanAddr::from("asset2")) - .unwrap(); - let pool_info = read_pool_info(&deps.storage, &token_raw).unwrap(); - store_pool_info( - &mut deps.storage, - &token_raw, - &PoolInfo { - premium_rate: Decimal::percent(2), - short_reward_weight: short_reward_weight(Decimal::percent(2)), - ..pool_info - }, - ) - .unwrap(); - - // bond 100 tokens - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from("addr"), - amount: Uint128(100u128), - msg: Some( - to_binary(&Cw20HookMsg::Bond { - asset_token: HumanAddr::from("asset"), - }) - .unwrap(), - ), - }); - let env = mock_env("staking", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // bond second 1000 tokens - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from("addr"), - amount: Uint128(1000u128), - msg: Some( - to_binary(&Cw20HookMsg::Bond { - asset_token: HumanAddr::from("asset2"), - }) - .unwrap(), - ), - }); - let env = mock_env("staking2", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // bond 50 short token - let msg = HandleMsg::IncreaseShortToken { - asset_token: HumanAddr::from("asset"), - staker_addr: HumanAddr::from("addr"), - amount: Uint128(50u128), - }; - let env = mock_env("mint", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // factory deposit asset - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from("factory"), - amount: Uint128(300u128), - msg: Some( - to_binary(&Cw20HookMsg::DepositReward { - rewards: vec![ - (HumanAddr::from("asset"), Uint128(100u128)), - (HumanAddr::from("asset2"), Uint128(200u128)), - ], - }) - .unwrap(), - ), - }); - let env = mock_env("reward", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - let data = query( - &deps, - QueryMsg::RewardInfo { - asset_token: None, - staker_addr: HumanAddr::from("addr"), - }, - ) - .unwrap(); - let res: RewardInfoResponse = from_binary(&data).unwrap(); - assert_eq!( - res, - RewardInfoResponse { - staker_addr: HumanAddr::from("addr"), - reward_infos: vec![ - RewardInfoResponseItem { - asset_token: HumanAddr::from("asset"), - bond_amount: Uint128(100u128), - pending_reward: Uint128(80u128), - is_short: false, - }, - RewardInfoResponseItem { - asset_token: HumanAddr::from("asset2"), - bond_amount: Uint128(1000u128), - pending_reward: Uint128(160u128), - is_short: false, - }, - RewardInfoResponseItem { - asset_token: HumanAddr::from("asset"), - bond_amount: Uint128(50u128), - pending_reward: Uint128(20u128), - is_short: true, - }, - ], - } - ); - - // withdraw all - let msg = HandleMsg::Withdraw { asset_token: None }; - let env = mock_env("addr", &[]); - let res = handle(&mut deps, env, msg).unwrap(); - - assert_eq!( - res.messages, - vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("reward"), - msg: to_binary(&Cw20HandleMsg::Transfer { - recipient: HumanAddr::from("addr"), - amount: Uint128(260u128), - }) - .unwrap(), - send: vec![], - })] - ); - - let data = query( - &deps, - QueryMsg::RewardInfo { - asset_token: None, - staker_addr: HumanAddr::from("addr"), - }, - ) - .unwrap(); - let res: RewardInfoResponse = from_binary(&data).unwrap(); - assert_eq!( - res, - RewardInfoResponse { - staker_addr: HumanAddr::from("addr"), - reward_infos: vec![ - RewardInfoResponseItem { - asset_token: HumanAddr::from("asset"), - bond_amount: Uint128(100u128), - pending_reward: Uint128::zero(), - is_short: false, - }, - RewardInfoResponseItem { - asset_token: HumanAddr::from("asset2"), - bond_amount: Uint128(1000u128), - pending_reward: Uint128::zero(), - is_short: false, - }, - RewardInfoResponseItem { - asset_token: HumanAddr::from("asset"), - bond_amount: Uint128(50u128), - pending_reward: Uint128::zero(), - is_short: true, - }, - ], - } - ); - } - - #[test] - fn test_adjust_premium() { - let mut deps = mock_dependencies_with_querier(20, &[]); - - // terraswap price 100 - // oracle price 100 - // premium zero - deps.querier.with_pair_info(HumanAddr::from("pair")); - deps.querier.with_pool_assets([ - Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: Uint128::from(100u128), - }, - Asset { - info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset"), - }, - amount: Uint128::from(1u128), - }, - ]); - deps.querier - .with_oracle_price(Decimal::from_ratio(100u128, 1u128)); - - let msg = InitMsg { - owner: HumanAddr::from("owner"), - mirror_token: HumanAddr::from("reward"), - mint_contract: HumanAddr::from("mint"), - oracle_contract: HumanAddr::from("oracle"), - terraswap_factory: HumanAddr::from("terraswap_factory"), - base_denom: "uusd".to_string(), - premium_min_update_interval: 3600, - }; - - let env = mock_env("addr", &[]); - let _res = init(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset"), - staking_token: HumanAddr::from("staking"), - }; - - let env = mock_env("owner", &[]); - let _res = handle(&mut deps, env, msg.clone()).unwrap(); - - let msg = HandleMsg::AdjustPremium { - asset_tokens: vec![HumanAddr::from("asset")], - }; - let mut env = mock_env("addr", &[]); - let _ = handle(&mut deps, env.clone(), msg.clone()).unwrap(); - - // Check pool state - let res: PoolInfoResponse = from_binary( - &query( - &deps, - QueryMsg::PoolInfo { - asset_token: HumanAddr::from("asset"), - }, - ) - .unwrap(), - ) - .unwrap(); - assert_eq!(res.premium_rate, Decimal::zero()); - assert_eq!(res.premium_updated_time, env.block.time); - - // terraswap price = 90 - // premium rate = 0 - deps.querier.with_pool_assets([ - Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: Uint128::from(90u128), - }, - Asset { - info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset"), - }, - amount: Uint128::from(1u128), - }, - ]); - - // assert premium update interval - let res = handle(&mut deps, env.clone(), msg.clone()); - match res { - Err(StdError::GenericErr { msg, .. }) => assert_eq!( - msg, - "cannot adjust premium before premium_min_update_interval passed" - ), - _ => panic!("DO NOT ENTER HERE"), - } - - env.block.time += 3600; - let _ = handle(&mut deps, env.clone(), msg.clone()).unwrap(); - - // Check pool state - let res: PoolInfoResponse = from_binary( - &query( - &deps, - QueryMsg::PoolInfo { - asset_token: HumanAddr::from("asset"), - }, - ) - .unwrap(), - ) - .unwrap(); - assert_eq!(res.premium_rate, Decimal::zero()); - assert_eq!(res.premium_updated_time, env.block.time); - - // terraswap price = 105 - // premium rate = 5% - deps.querier.with_pool_assets([ - Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: Uint128::from(105u128), - }, - Asset { - info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset"), - }, - amount: Uint128::from(1u128), - }, - ]); - - env.block.time += 3600; - let _ = handle(&mut deps, env.clone(), msg.clone()).unwrap(); - - // Check pool state - let res: PoolInfoResponse = from_binary( - &query( - &deps, - QueryMsg::PoolInfo { - asset_token: HumanAddr::from("asset"), - }, - ) - .unwrap(), - ) - .unwrap(); - assert_eq!(res.premium_rate, Decimal::percent(5)); - assert_eq!(res.premium_updated_time, env.block.time); - } -} diff --git a/contracts/mirror_staking/src/rewards.rs b/contracts/mirror_staking/src/rewards.rs deleted file mode 100644 index 6d55b1710..000000000 --- a/contracts/mirror_staking/src/rewards.rs +++ /dev/null @@ -1,285 +0,0 @@ -use cosmwasm_std::{ - log, to_binary, Api, CanonicalAddr, CosmosMsg, Decimal, Env, Extern, HandleResponse, - HandleResult, HumanAddr, Order, Querier, StdError, StdResult, Storage, Uint128, WasmMsg, -}; - -use crate::math::short_reward_weight; -use crate::querier::compute_premium_rate; -use crate::state::{ - read_config, read_pool_info, rewards_read, rewards_store, store_pool_info, Config, PoolInfo, - RewardInfo, -}; -use mirror_protocol::staking::{RewardInfoResponse, RewardInfoResponseItem}; - -use cw20::Cw20HandleMsg; - -pub fn adjust_premium( - deps: &mut Extern, - env: Env, - asset_tokens: Vec, -) -> HandleResult { - let config: Config = read_config(&deps.storage)?; - let oracle_contract = deps.api.human_address(&config.oracle_contract)?; - let terraswap_factory = deps.api.human_address(&config.terraswap_factory)?; - for asset_token in asset_tokens.iter() { - let asset_token_raw = deps.api.canonical_address(&asset_token)?; - let pool_info: PoolInfo = read_pool_info(&deps.storage, &asset_token_raw)?; - if env.block.time < pool_info.premium_updated_time + config.premium_min_update_interval { - return Err(StdError::generic_err( - "cannot adjust premium before premium_min_update_interval passed", - )); - } - - let premium_rate = compute_premium_rate( - deps, - &oracle_contract, - &terraswap_factory, - asset_token, - config.base_denom.to_string(), - )?; - let short_reward_weight = short_reward_weight(premium_rate); - - store_pool_info( - &mut deps.storage, - &asset_token_raw, - &PoolInfo { - premium_rate, - short_reward_weight, - premium_updated_time: env.block.time, - ..pool_info - }, - )?; - } - - Ok(HandleResponse { - log: vec![log("action", "premium_adjustment")], - ..HandleResponse::default() - }) -} - -// deposit_reward must be from reward token contract -pub fn deposit_reward( - deps: &mut Extern, - rewards: Vec<(HumanAddr, Uint128)>, - rewards_amount: Uint128, -) -> HandleResult { - for (asset_token, amount) in rewards.iter() { - let asset_token_raw: CanonicalAddr = deps.api.canonical_address(&asset_token)?; - let mut pool_info: PoolInfo = read_pool_info(&deps.storage, &asset_token_raw)?; - - // Decimal::from_ratio(1, 5).mul() - // erf(pool_info.premium_rate.0) - // 3.0f64 - let total_reward = *amount; - let mut short_reward = total_reward * pool_info.short_reward_weight; - let mut normal_reward = (total_reward - short_reward).unwrap(); - - if pool_info.total_bond_amount.is_zero() { - pool_info.pending_reward += normal_reward; - } else { - normal_reward += pool_info.pending_reward; - let normal_reward_per_bond = - Decimal::from_ratio(normal_reward, pool_info.total_bond_amount); - pool_info.reward_index = pool_info.reward_index + normal_reward_per_bond; - pool_info.pending_reward = Uint128::zero(); - } - - if pool_info.total_short_amount.is_zero() { - pool_info.short_pending_reward += short_reward; - } else { - short_reward += pool_info.short_pending_reward; - let short_reward_per_bond = - Decimal::from_ratio(short_reward, pool_info.total_short_amount); - pool_info.short_reward_index = pool_info.short_reward_index + short_reward_per_bond; - pool_info.short_pending_reward = Uint128::zero(); - } - - store_pool_info(&mut deps.storage, &asset_token_raw, &pool_info)?; - } - - Ok(HandleResponse { - messages: vec![], - log: vec![ - log("action", "deposit_reward"), - log("rewards_amount", rewards_amount.to_string()), - ], - data: None, - }) -} - -// withdraw all rewards or single reward depending on asset_token -pub fn withdraw_reward( - deps: &mut Extern, - env: Env, - asset_token: Option, -) -> HandleResult { - let staker_addr = deps.api.canonical_address(&env.message.sender)?; - let asset_token = asset_token.map(|a| deps.api.canonical_address(&a).unwrap()); - let normal_reward = _withdraw_reward(&mut deps.storage, &staker_addr, &asset_token, false)?; - let short_reward = _withdraw_reward(&mut deps.storage, &staker_addr, &asset_token, true)?; - - let amount = normal_reward + short_reward; - let config: Config = read_config(&deps.storage)?; - Ok(HandleResponse { - messages: vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: deps.api.human_address(&config.mirror_token)?, - msg: to_binary(&Cw20HandleMsg::Transfer { - recipient: env.message.sender, - amount, - })?, - send: vec![], - })], - log: vec![log("action", "withdraw"), log("amount", amount.to_string())], - data: None, - }) -} - -fn _withdraw_reward( - storage: &mut S, - staker_addr: &CanonicalAddr, - asset_token: &Option, - is_short: bool, -) -> StdResult { - let rewards_bucket = rewards_read(storage, &staker_addr, is_short); - - // single reward withdraw - let reward_pairs: Vec<(CanonicalAddr, RewardInfo)>; - if let Some(asset_token) = asset_token { - let reward_info = rewards_bucket.may_load(asset_token.as_slice())?; - reward_pairs = if let Some(reward_info) = reward_info { - vec![(asset_token.clone(), reward_info)] - } else { - vec![] - }; - } else { - reward_pairs = rewards_bucket - .range(None, None, Order::Ascending) - .map(|item| { - let (k, v) = item?; - Ok((CanonicalAddr::from(k), v)) - }) - .collect::>>()?; - } - - let mut amount: Uint128 = Uint128::zero(); - for reward_pair in reward_pairs { - let (asset_token_raw, mut reward_info) = reward_pair; - let pool_info: PoolInfo = read_pool_info(storage, &asset_token_raw)?; - - // Withdraw reward to pending reward - before_share_change(&pool_info, &mut reward_info, is_short)?; - - amount += reward_info.pending_reward; - reward_info.pending_reward = Uint128::zero(); - - // Update rewards info - if reward_info.pending_reward.is_zero() && reward_info.bond_amount.is_zero() { - rewards_store(storage, &staker_addr, is_short).remove(asset_token_raw.as_slice()); - } else { - rewards_store(storage, &staker_addr, is_short) - .save(asset_token_raw.as_slice(), &reward_info)?; - } - } - - Ok(amount) -} - -// withdraw reward to pending reward -pub fn before_share_change( - pool_info: &PoolInfo, - reward_info: &mut RewardInfo, - is_short: bool, -) -> StdResult<()> { - let pool_index = if is_short { - pool_info.short_reward_index - } else { - pool_info.reward_index - }; - - let pending_reward = - (reward_info.bond_amount * pool_index - reward_info.bond_amount * reward_info.index)?; - - reward_info.index = pool_index; - reward_info.pending_reward += pending_reward; - Ok(()) -} - -pub fn query_reward_info( - deps: &Extern, - staker_addr: HumanAddr, - asset_token: Option, -) -> StdResult { - let staker_addr_raw = deps.api.canonical_address(&staker_addr)?; - - let reward_infos: Vec = vec![ - _read_reward_infos( - &deps.api, - &deps.storage, - &staker_addr_raw, - &asset_token, - false, - )?, - _read_reward_infos( - &deps.api, - &deps.storage, - &staker_addr_raw, - &asset_token, - true, - )?, - ] - .concat(); - - Ok(RewardInfoResponse { - staker_addr, - reward_infos, - }) -} - -fn _read_reward_infos( - api: &A, - storage: &S, - staker_addr: &CanonicalAddr, - asset_token: &Option, - is_short: bool, -) -> StdResult> { - let rewards_bucket = rewards_read(storage, &staker_addr, is_short); - let reward_infos: Vec; - if let Some(asset_token) = asset_token { - let asset_token_raw = api.canonical_address(&asset_token)?; - - reward_infos = - if let Some(mut reward_info) = rewards_bucket.may_load(asset_token_raw.as_slice())? { - let pool_info = read_pool_info(storage, &asset_token_raw)?; - before_share_change(&pool_info, &mut reward_info, is_short)?; - - vec![RewardInfoResponseItem { - asset_token: asset_token.clone(), - bond_amount: reward_info.bond_amount, - pending_reward: reward_info.pending_reward, - is_short, - }] - } else { - vec![] - }; - } else { - reward_infos = rewards_bucket - .range(None, None, Order::Ascending) - .map(|item| { - let (k, v) = item?; - let asset_token_raw = CanonicalAddr::from(k); - let mut reward_info = v; - let pool_info = read_pool_info(storage, &asset_token_raw)?; - before_share_change(&pool_info, &mut reward_info, is_short)?; - - Ok(RewardInfoResponseItem { - asset_token: api.human_address(&asset_token_raw)?, - bond_amount: reward_info.bond_amount, - pending_reward: reward_info.pending_reward, - is_short, - }) - }) - .collect::>>()?; - } - - Ok(reward_infos) -} diff --git a/contracts/mirror_staking/src/staking.rs b/contracts/mirror_staking/src/staking.rs deleted file mode 100644 index 32acf640a..000000000 --- a/contracts/mirror_staking/src/staking.rs +++ /dev/null @@ -1,373 +0,0 @@ -use cosmwasm_std::{ - log, to_binary, Api, CanonicalAddr, Coin, CosmosMsg, Decimal, Env, Extern, HandleResponse, - HandleResult, HumanAddr, Querier, StdError, StdResult, Storage, Uint128, WasmMsg, -}; - -use crate::rewards::before_share_change; -use crate::state::{ - read_config, read_pool_info, rewards_read, rewards_store, store_pool_info, Config, PoolInfo, - RewardInfo, -}; - -use cw20::Cw20HandleMsg; -use mirror_protocol::staking::HandleMsg; -use terraswap::asset::{Asset, AssetInfo, PairInfo}; -use terraswap::pair::HandleMsg as PairHandleMsg; -use terraswap::querier::{query_pair_info, query_token_balance}; - -pub fn bond( - deps: &mut Extern, - _env: Env, - staker_addr: HumanAddr, - asset_token: HumanAddr, - amount: Uint128, -) -> HandleResult { - let staker_addr_raw: CanonicalAddr = deps.api.canonical_address(&staker_addr)?; - let asset_token_raw: CanonicalAddr = deps.api.canonical_address(&asset_token)?; - _increase_bond_amount( - &mut deps.storage, - &staker_addr_raw, - &asset_token_raw, - amount, - false, - )?; - - Ok(HandleResponse { - messages: vec![], - log: vec![ - log("action", "bond"), - log("staker_addr", staker_addr.as_str()), - log("asset_token", asset_token.as_str()), - log("amount", amount.to_string()), - ], - data: None, - }) -} - -pub fn unbond( - deps: &mut Extern, - staker_addr: HumanAddr, - asset_token: HumanAddr, - amount: Uint128, -) -> HandleResult { - let staker_addr_raw: CanonicalAddr = deps.api.canonical_address(&staker_addr)?; - let asset_token_raw: CanonicalAddr = deps.api.canonical_address(&asset_token)?; - let staking_token: CanonicalAddr = _decrease_bond_amount( - &mut deps.storage, - &staker_addr_raw, - &asset_token_raw, - amount, - false, - )?; - - Ok(HandleResponse { - messages: vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: deps.api.human_address(&staking_token)?, - msg: to_binary(&Cw20HandleMsg::Transfer { - recipient: staker_addr.clone(), - amount, - })?, - send: vec![], - })], - log: vec![ - log("action", "unbond"), - log("staker_addr", staker_addr.as_str()), - log("asset_token", asset_token.as_str()), - log("amount", amount.to_string()), - ], - data: None, - }) -} - -// only mint contract can execute the operation -pub fn increase_short_token( - deps: &mut Extern, - env: Env, - staker_addr: HumanAddr, - asset_token: HumanAddr, - amount: Uint128, -) -> HandleResult { - let config: Config = read_config(&deps.storage)?; - if deps.api.canonical_address(&env.message.sender)? != config.mint_contract { - return Err(StdError::unauthorized()); - } - - let staker_addr_raw: CanonicalAddr = deps.api.canonical_address(&staker_addr)?; - let asset_token_raw: CanonicalAddr = deps.api.canonical_address(&asset_token)?; - - _increase_bond_amount( - &mut deps.storage, - &staker_addr_raw, - &asset_token_raw, - amount, - true, - )?; - - Ok(HandleResponse { - messages: vec![], - log: vec![ - log("action", "increase_short_token"), - log("staker_addr", staker_addr.as_str()), - log("asset_token", asset_token.as_str()), - log("amount", amount.to_string()), - ], - data: None, - }) -} - -// only mint contract can execute the operation -pub fn decrease_short_token( - deps: &mut Extern, - env: Env, - staker_addr: HumanAddr, - asset_token: HumanAddr, - amount: Uint128, -) -> HandleResult { - let config: Config = read_config(&deps.storage)?; - if deps.api.canonical_address(&env.message.sender)? != config.mint_contract { - return Err(StdError::unauthorized()); - } - - let staker_addr_raw: CanonicalAddr = deps.api.canonical_address(&staker_addr)?; - let asset_token_raw: CanonicalAddr = deps.api.canonical_address(&asset_token)?; - - // not used - let _ = _decrease_bond_amount( - &mut deps.storage, - &staker_addr_raw, - &asset_token_raw, - amount, - true, - )?; - - Ok(HandleResponse { - messages: vec![], - log: vec![ - log("action", "decrease_short_token"), - log("staker_addr", staker_addr.as_str()), - log("asset_token", asset_token.as_str()), - log("amount", amount.to_string()), - ], - data: None, - }) -} - -pub fn auto_stake( - deps: &mut Extern, - env: Env, - assets: [Asset; 2], - slippage_tolerance: Option, -) -> HandleResult { - let config: Config = read_config(&deps.storage)?; - let terraswap_factory: HumanAddr = deps.api.human_address(&config.terraswap_factory)?; - - let mut native_asset_op: Option = None; - let mut token_info_op: Option<(HumanAddr, Uint128)> = None; - for asset in assets.iter() { - match asset.info.clone() { - AssetInfo::NativeToken { .. } => { - asset.assert_sent_native_token_balance(&env)?; - native_asset_op = Some(asset.clone()) - } - AssetInfo::Token { contract_addr } => { - token_info_op = Some((contract_addr, asset.amount)) - } - } - } - - // will fail if one of them is missing - let native_asset: Asset = match native_asset_op { - Some(v) => v, - None => return Err(StdError::generic_err("Missing native asset")), - }; - let (token_addr, token_amount) = match token_info_op { - Some(v) => v, - None => return Err(StdError::generic_err("Missing token asset")), - }; - - // query pair info to obtain pair contract address - let asset_infos: [AssetInfo; 2] = [assets[0].info.clone(), assets[1].info.clone()]; - let terraswap_pair: PairInfo = query_pair_info(deps, &terraswap_factory, &asset_infos)?; - - // assert the token and lp token match with pool info - let pool_info: PoolInfo = - read_pool_info(&deps.storage, &deps.api.canonical_address(&token_addr)?)?; - - if pool_info.staking_token - != deps - .api - .canonical_address(&terraswap_pair.liquidity_token)? - { - return Err(StdError::generic_err("Invalid staking token")); - } - - // get current lp token amount to later compute the recived amount - let prev_staking_token_amount = query_token_balance( - &deps, - &terraswap_pair.liquidity_token, - &env.contract.address, - )?; - - // compute tax - let tax_amount: Uint128 = native_asset.compute_tax(deps)?; - - // 1. Transfer token asset to staking contract - // 2. Increase allowance of token for pair contract - // 3. Provide liquidity - // 4. Execute staking hook, will stake in the name of the sender - Ok(HandleResponse { - messages: vec![ - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: token_addr.clone(), - msg: to_binary(&Cw20HandleMsg::TransferFrom { - owner: env.message.sender.clone(), - recipient: env.contract.address.clone(), - amount: token_amount, - })?, - send: vec![], - }), - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: token_addr.clone(), - msg: to_binary(&Cw20HandleMsg::IncreaseAllowance { - spender: terraswap_pair.contract_addr.clone(), - amount: token_amount, - expires: None, - })?, - send: vec![], - }), - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: terraswap_pair.contract_addr, - msg: to_binary(&PairHandleMsg::ProvideLiquidity { - assets: [ - Asset { - amount: (native_asset.amount.clone() - tax_amount)?, - info: native_asset.info.clone(), - }, - Asset { - amount: token_amount, - info: AssetInfo::Token { - contract_addr: token_addr.clone(), - }, - }, - ], - slippage_tolerance, - })?, - send: vec![Coin { - denom: native_asset.info.to_string(), - amount: (native_asset.amount - tax_amount)?, - }], - }), - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: env.contract.address, - msg: to_binary(&HandleMsg::AutoStakeHook { - asset_token: token_addr.clone(), - staking_token: terraswap_pair.liquidity_token, - staker_addr: env.message.sender, - prev_staking_token_amount, - })?, - send: vec![], - }), - ], - log: vec![ - log("action", "auto_stake"), - log("asset_token", token_addr.to_string()), - log("tax_amount", tax_amount.to_string()), - ], - data: None, - }) -} - -pub fn auto_stake_hook( - deps: &mut Extern, - env: Env, - asset_token: HumanAddr, - staking_token: HumanAddr, - staker_addr: HumanAddr, - prev_staking_token_amount: Uint128, -) -> HandleResult { - // only can be called by itself - if env.message.sender != env.contract.address { - return Err(StdError::unauthorized()); - } - - // stake all lp tokens received, compare with staking token amount before liquidity provision was executed - let current_staking_token_amount = - query_token_balance(&deps, &staking_token, &env.contract.address)?; - let amount_to_stake = (current_staking_token_amount - prev_staking_token_amount)?; - - bond(deps, env, staker_addr, asset_token, amount_to_stake) -} - -fn _increase_bond_amount( - storage: &mut S, - staker_addr: &CanonicalAddr, - asset_token: &CanonicalAddr, - amount: Uint128, - is_short: bool, -) -> StdResult<()> { - let mut pool_info: PoolInfo = read_pool_info(storage, &asset_token)?; - let mut reward_info: RewardInfo = rewards_read(storage, &staker_addr, is_short) - .load(asset_token.as_slice()) - .unwrap_or_else(|_| RewardInfo { - index: Decimal::zero(), - bond_amount: Uint128::zero(), - pending_reward: Uint128::zero(), - }); - - // Withdraw reward to pending reward; before changing share - before_share_change(&pool_info, &mut reward_info, is_short)?; - - // Increase total short or bond amount - if is_short { - pool_info.total_short_amount += amount; - } else { - pool_info.total_bond_amount += amount; - } - - reward_info.bond_amount += amount; - rewards_store(storage, &staker_addr, is_short).save(&asset_token.as_slice(), &reward_info)?; - store_pool_info(storage, &asset_token, &pool_info)?; - - Ok(()) -} - -fn _decrease_bond_amount( - storage: &mut S, - staker_addr: &CanonicalAddr, - asset_token: &CanonicalAddr, - amount: Uint128, - is_short: bool, -) -> StdResult { - let mut pool_info: PoolInfo = read_pool_info(storage, &asset_token)?; - let mut reward_info: RewardInfo = - rewards_read(storage, &staker_addr, is_short).load(asset_token.as_slice())?; - - if reward_info.bond_amount < amount { - return Err(StdError::generic_err("Cannot unbond more than bond amount")); - } - - // Distribute reward to pending reward; before changing share - before_share_change(&pool_info, &mut reward_info, is_short)?; - - // Decrease total short or bond amount - if is_short { - pool_info.total_short_amount = (pool_info.total_short_amount - amount)?; - } else { - pool_info.total_bond_amount = (pool_info.total_bond_amount - amount)?; - } - - reward_info.bond_amount = (reward_info.bond_amount - amount)?; - - // Update rewards info - if reward_info.pending_reward.is_zero() && reward_info.bond_amount.is_zero() { - rewards_store(storage, &staker_addr, is_short).remove(asset_token.as_slice()); - } else { - rewards_store(storage, &staker_addr, is_short) - .save(asset_token.as_slice(), &reward_info)?; - } - - // Update pool info - store_pool_info(storage, &asset_token, &pool_info)?; - - Ok(pool_info.staking_token) -} diff --git a/contracts/mirror_staking/src/staking_test.rs b/contracts/mirror_staking/src/staking_test.rs deleted file mode 100644 index 1459eb925..000000000 --- a/contracts/mirror_staking/src/staking_test.rs +++ /dev/null @@ -1,726 +0,0 @@ -#[cfg(test)] -mod tests { - use crate::contract::{handle, init, query}; - use crate::mock_querier::mock_dependencies_with_querier; - use cosmwasm_std::testing::{mock_dependencies, mock_env, MOCK_CONTRACT_ADDR}; - use cosmwasm_std::{ - from_binary, log, to_binary, Coin, CosmosMsg, Decimal, HumanAddr, StdError, Uint128, - WasmMsg, - }; - use cw20::{Cw20HandleMsg, Cw20ReceiveMsg}; - use mirror_protocol::staking::{ - Cw20HookMsg, HandleMsg, InitMsg, PoolInfoResponse, QueryMsg, RewardInfoResponse, - RewardInfoResponseItem, - }; - use terraswap::asset::{Asset, AssetInfo}; - use terraswap::pair::HandleMsg as PairHandleMsg; - - #[test] - fn test_bond_tokens() { - let mut deps = mock_dependencies(20, &[]); - - let msg = InitMsg { - owner: HumanAddr::from("owner"), - mirror_token: HumanAddr::from("reward"), - mint_contract: HumanAddr::from("mint"), - oracle_contract: HumanAddr::from("oracle"), - terraswap_factory: HumanAddr::from("terraswap_factory"), - base_denom: "uusd".to_string(), - premium_min_update_interval: 3600, - }; - - let env = mock_env("addr", &[]); - let _res = init(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset"), - staking_token: HumanAddr::from("staking"), - }; - - let env = mock_env("owner", &[]); - let _res = handle(&mut deps, env, msg.clone()).unwrap(); - - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from("addr"), - amount: Uint128(100u128), - msg: Some( - to_binary(&Cw20HookMsg::Bond { - asset_token: HumanAddr::from("asset"), - }) - .unwrap(), - ), - }); - - let env = mock_env("staking", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - let data = query( - &deps, - QueryMsg::RewardInfo { - asset_token: Some(HumanAddr::from("asset")), - staker_addr: HumanAddr::from("addr"), - }, - ) - .unwrap(); - let res: RewardInfoResponse = from_binary(&data).unwrap(); - assert_eq!( - res, - RewardInfoResponse { - staker_addr: HumanAddr::from("addr"), - reward_infos: vec![RewardInfoResponseItem { - asset_token: HumanAddr::from("asset"), - pending_reward: Uint128::zero(), - bond_amount: Uint128(100u128), - is_short: false, - }], - } - ); - - let data = query( - &deps, - QueryMsg::PoolInfo { - asset_token: HumanAddr::from("asset"), - }, - ) - .unwrap(); - - let pool_info: PoolInfoResponse = from_binary(&data).unwrap(); - assert_eq!( - pool_info, - PoolInfoResponse { - asset_token: HumanAddr::from("asset"), - staking_token: HumanAddr::from("staking"), - total_bond_amount: Uint128(100u128), - total_short_amount: Uint128::zero(), - reward_index: Decimal::zero(), - short_reward_index: Decimal::zero(), - pending_reward: Uint128::zero(), - short_pending_reward: Uint128::zero(), - premium_rate: Decimal::zero(), - short_reward_weight: Decimal::zero(), - premium_updated_time: 0, - } - ); - - // bond 100 more tokens from other account - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from("addr2"), - amount: Uint128(100u128), - msg: Some( - to_binary(&Cw20HookMsg::Bond { - asset_token: HumanAddr::from("asset"), - }) - .unwrap(), - ), - }); - let env = mock_env("staking", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - let data = query( - &deps, - QueryMsg::PoolInfo { - asset_token: HumanAddr::from("asset"), - }, - ) - .unwrap(); - let pool_info: PoolInfoResponse = from_binary(&data).unwrap(); - assert_eq!( - pool_info, - PoolInfoResponse { - asset_token: HumanAddr::from("asset"), - staking_token: HumanAddr::from("staking"), - total_bond_amount: Uint128(200u128), - total_short_amount: Uint128::zero(), - reward_index: Decimal::zero(), - short_reward_index: Decimal::zero(), - pending_reward: Uint128::zero(), - short_pending_reward: Uint128::zero(), - premium_rate: Decimal::zero(), - short_reward_weight: Decimal::zero(), - premium_updated_time: 0, - } - ); - - // failed with unauthorized - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from("addr"), - amount: Uint128(100u128), - msg: Some( - to_binary(&Cw20HookMsg::Bond { - asset_token: HumanAddr::from("asset"), - }) - .unwrap(), - ), - }); - - let env = mock_env("staking2", &[]); - let res = handle(&mut deps, env, msg); - match res { - Err(StdError::Unauthorized { .. }) => {} - _ => panic!("Must return unauthorized error"), - } - } - - #[test] - fn test_unbond() { - let mut deps = mock_dependencies(20, &[]); - - let msg = InitMsg { - owner: HumanAddr::from("owner"), - mirror_token: HumanAddr::from("reward"), - mint_contract: HumanAddr::from("mint"), - oracle_contract: HumanAddr::from("oracle"), - terraswap_factory: HumanAddr::from("terraswap_factory"), - base_denom: "uusd".to_string(), - premium_min_update_interval: 3600, - }; - - let env = mock_env("addr", &[]); - let _res = init(&mut deps, env, msg).unwrap(); - - // register asset - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset"), - staking_token: HumanAddr::from("staking"), - }; - - let env = mock_env("owner", &[]); - let _res = handle(&mut deps, env, msg.clone()).unwrap(); - - // bond 100 tokens - let msg = HandleMsg::Receive(Cw20ReceiveMsg { - sender: HumanAddr::from("addr"), - amount: Uint128(100u128), - msg: Some( - to_binary(&Cw20HookMsg::Bond { - asset_token: HumanAddr::from("asset"), - }) - .unwrap(), - ), - }); - let env = mock_env("staking", &[]); - let _res = handle(&mut deps, env, msg).unwrap(); - - // unbond 150 tokens; failed - let msg = HandleMsg::Unbond { - asset_token: HumanAddr::from("asset"), - amount: Uint128(150u128), - }; - - let env = mock_env("addr", &[]); - let res = handle(&mut deps, env, msg).unwrap_err(); - match res { - StdError::GenericErr { msg, .. } => { - assert_eq!(msg, "Cannot unbond more than bond amount"); - } - _ => panic!("Must return generic error"), - }; - - // normal unbond - let msg = HandleMsg::Unbond { - asset_token: HumanAddr::from("asset"), - amount: Uint128(100u128), - }; - - let env = mock_env("addr", &[]); - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!( - res.messages, - vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("staking"), - msg: to_binary(&Cw20HandleMsg::Transfer { - recipient: HumanAddr::from("addr"), - amount: Uint128(100u128), - }) - .unwrap(), - send: vec![], - })] - ); - - let data = query( - &deps, - QueryMsg::PoolInfo { - asset_token: HumanAddr::from("asset"), - }, - ) - .unwrap(); - let pool_info: PoolInfoResponse = from_binary(&data).unwrap(); - assert_eq!( - pool_info, - PoolInfoResponse { - asset_token: HumanAddr::from("asset"), - staking_token: HumanAddr::from("staking"), - total_bond_amount: Uint128::zero(), - total_short_amount: Uint128::zero(), - reward_index: Decimal::zero(), - short_reward_index: Decimal::zero(), - pending_reward: Uint128::zero(), - short_pending_reward: Uint128::zero(), - premium_rate: Decimal::zero(), - short_reward_weight: Decimal::zero(), - premium_updated_time: 0, - } - ); - - let data = query( - &deps, - QueryMsg::RewardInfo { - asset_token: None, - staker_addr: HumanAddr::from("addr"), - }, - ) - .unwrap(); - let res: RewardInfoResponse = from_binary(&data).unwrap(); - assert_eq!( - res, - RewardInfoResponse { - staker_addr: HumanAddr::from("addr"), - reward_infos: vec![], - } - ); - } - - #[test] - fn test_increase_short_token() { - let mut deps = mock_dependencies(20, &[]); - - let msg = InitMsg { - owner: HumanAddr::from("owner"), - mirror_token: HumanAddr::from("reward"), - mint_contract: HumanAddr::from("mint"), - oracle_contract: HumanAddr::from("oracle"), - terraswap_factory: HumanAddr::from("terraswap_factory"), - base_denom: "uusd".to_string(), - premium_min_update_interval: 3600, - }; - - let env = mock_env("addr", &[]); - let _res = init(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset"), - staking_token: HumanAddr::from("staking"), - }; - - let env = mock_env("owner", &[]); - let _res = handle(&mut deps, env, msg.clone()).unwrap(); - - let msg = HandleMsg::IncreaseShortToken { - asset_token: HumanAddr::from("asset"), - staker_addr: HumanAddr::from("addr"), - amount: Uint128::from(100u128), - }; - - let env = mock_env("addr", &[]); - let res = handle(&mut deps, env, msg.clone()); - match res { - Err(StdError::Unauthorized { .. }) => {} - _ => panic!("DO NOT ENTER HERE"), - } - - let env = mock_env("mint", &[]); - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!( - vec![ - log("action", "increase_short_token"), - log("staker_addr", "addr"), - log("asset_token", "asset"), - log("amount", "100"), - ], - res.log - ); - - let data = query( - &deps, - QueryMsg::PoolInfo { - asset_token: HumanAddr::from("asset"), - }, - ) - .unwrap(); - - let pool_info: PoolInfoResponse = from_binary(&data).unwrap(); - assert_eq!( - pool_info, - PoolInfoResponse { - asset_token: HumanAddr::from("asset"), - staking_token: HumanAddr::from("staking"), - total_bond_amount: Uint128::zero(), - total_short_amount: Uint128::from(100u128), - reward_index: Decimal::zero(), - short_reward_index: Decimal::zero(), - pending_reward: Uint128::zero(), - short_pending_reward: Uint128::zero(), - premium_rate: Decimal::zero(), - short_reward_weight: Decimal::zero(), - premium_updated_time: 0, - } - ); - - let data = query( - &deps, - QueryMsg::RewardInfo { - asset_token: None, - staker_addr: HumanAddr::from("addr"), - }, - ) - .unwrap(); - let res: RewardInfoResponse = from_binary(&data).unwrap(); - assert_eq!( - res, - RewardInfoResponse { - staker_addr: HumanAddr::from("addr"), - reward_infos: vec![RewardInfoResponseItem { - asset_token: HumanAddr::from("asset"), - pending_reward: Uint128::zero(), - bond_amount: Uint128(100u128), - is_short: true, - }], - } - ); - } - - #[test] - fn test_decrease_short_token() { - let mut deps = mock_dependencies(20, &[]); - - let msg = InitMsg { - owner: HumanAddr::from("owner"), - mirror_token: HumanAddr::from("reward"), - mint_contract: HumanAddr::from("mint"), - oracle_contract: HumanAddr::from("oracle"), - terraswap_factory: HumanAddr::from("terraswap_factory"), - base_denom: "uusd".to_string(), - premium_min_update_interval: 3600, - }; - - let env = mock_env("addr", &[]); - let _res = init(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset"), - staking_token: HumanAddr::from("staking"), - }; - - let env = mock_env("owner", &[]); - let _res = handle(&mut deps, env, msg.clone()).unwrap(); - - let msg = HandleMsg::IncreaseShortToken { - asset_token: HumanAddr::from("asset"), - staker_addr: HumanAddr::from("addr"), - amount: Uint128::from(100u128), - }; - - let env = mock_env("mint", &[]); - let _ = handle(&mut deps, env.clone(), msg).unwrap(); - - let msg = HandleMsg::DecreaseShortToken { - asset_token: HumanAddr::from("asset"), - staker_addr: HumanAddr::from("addr"), - amount: Uint128::from(100u128), - }; - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!( - vec![ - log("action", "decrease_short_token"), - log("staker_addr", "addr"), - log("asset_token", "asset"), - log("amount", "100"), - ], - res.log - ); - - let data = query( - &deps, - QueryMsg::PoolInfo { - asset_token: HumanAddr::from("asset"), - }, - ) - .unwrap(); - - let pool_info: PoolInfoResponse = from_binary(&data).unwrap(); - assert_eq!( - pool_info, - PoolInfoResponse { - asset_token: HumanAddr::from("asset"), - staking_token: HumanAddr::from("staking"), - total_bond_amount: Uint128::zero(), - total_short_amount: Uint128::zero(), - reward_index: Decimal::zero(), - short_reward_index: Decimal::zero(), - pending_reward: Uint128::zero(), - short_pending_reward: Uint128::zero(), - premium_rate: Decimal::zero(), - short_reward_weight: Decimal::zero(), - premium_updated_time: 0, - } - ); - - let data = query( - &deps, - QueryMsg::RewardInfo { - asset_token: None, - staker_addr: HumanAddr::from("addr"), - }, - ) - .unwrap(); - let res: RewardInfoResponse = from_binary(&data).unwrap(); - assert_eq!( - res, - RewardInfoResponse { - staker_addr: HumanAddr::from("addr"), - reward_infos: vec![], - } - ); - } - - #[test] - fn test_auto_stake() { - let mut deps = mock_dependencies_with_querier(20, &[]); - deps.querier.with_pair_info(HumanAddr::from("pair")); - deps.querier.with_pool_assets([ - Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: Uint128::from(100u128), - }, - Asset { - info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset"), - }, - amount: Uint128::from(1u128), - }, - ]); - - let msg = InitMsg { - owner: HumanAddr::from("owner"), - mirror_token: HumanAddr::from("reward"), - mint_contract: HumanAddr::from("mint"), - oracle_contract: HumanAddr::from("oracle"), - terraswap_factory: HumanAddr::from("terraswap_factory"), - base_denom: "uusd".to_string(), - premium_min_update_interval: 3600, - }; - - let env = mock_env("addr", &[]); - let _res = init(&mut deps, env, msg).unwrap(); - - let msg = HandleMsg::RegisterAsset { - asset_token: HumanAddr::from("asset"), - staking_token: HumanAddr::from("lptoken"), - }; - - let env = mock_env("owner", &[]); - let _res = handle(&mut deps, env, msg.clone()).unwrap(); - - // no token asset - let msg = HandleMsg::AutoStake { - assets: [ - Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: Uint128(100u128), - }, - Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: Uint128(100u128), - }, - ], - slippage_tolerance: None, - }; - let env = mock_env( - "addr0000", - &[Coin { - denom: "uusd".to_string(), - amount: Uint128(100u128), - }], - ); - let res = handle(&mut deps, env, msg).unwrap_err(); - assert_eq!(res, StdError::generic_err("Missing token asset")); - - // no native asset - let msg = HandleMsg::AutoStake { - assets: [ - Asset { - info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset"), - }, - amount: Uint128::from(1u128), - }, - Asset { - info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset"), - }, - amount: Uint128::from(1u128), - }, - ], - slippage_tolerance: None, - }; - let env = mock_env("addr0000", &[]); - let res = handle(&mut deps, env, msg).unwrap_err(); - assert_eq!(res, StdError::generic_err("Missing native asset")); - - let msg = HandleMsg::AutoStake { - assets: [ - Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: Uint128(100u128), - }, - Asset { - info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset"), - }, - amount: Uint128(1u128), - }, - ], - slippage_tolerance: None, - }; - - // attempt with no coins - let env = mock_env("addr0000", &[]); - let res = handle(&mut deps, env, msg.clone()).unwrap_err(); - assert_eq!( - res, - StdError::generic_err( - "Native token balance missmatch between the argument and the transferred" - ) - ); - - let env = mock_env( - "addr0000", - &[Coin { - denom: "uusd".to_string(), - amount: Uint128(100u128), - }], - ); - let res = handle(&mut deps, env, msg.clone()).unwrap(); - assert_eq!( - res.messages, - vec![ - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("asset"), - msg: to_binary(&Cw20HandleMsg::TransferFrom { - owner: HumanAddr::from("addr0000"), - recipient: HumanAddr::from(MOCK_CONTRACT_ADDR), - amount: Uint128(1u128), - }) - .unwrap(), - send: vec![], - }), - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("asset"), - msg: to_binary(&Cw20HandleMsg::IncreaseAllowance { - spender: HumanAddr::from("pair"), - amount: Uint128(1), - expires: None, - }) - .unwrap(), - send: vec![], - }), - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from("pair"), - msg: to_binary(&PairHandleMsg::ProvideLiquidity { - assets: [ - Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string() - }, - amount: Uint128(99u128), - }, - Asset { - info: AssetInfo::Token { - contract_addr: HumanAddr::from("asset") - }, - amount: Uint128(1u128), - }, - ], - slippage_tolerance: None, - }) - .unwrap(), - send: vec![Coin { - denom: "uusd".to_string(), - amount: Uint128(99u128), // 1% tax - }], - }), - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: HumanAddr::from(MOCK_CONTRACT_ADDR), - msg: to_binary(&HandleMsg::AutoStakeHook { - asset_token: HumanAddr::from("asset"), - staking_token: HumanAddr::from("lptoken"), - staker_addr: HumanAddr::from("addr0000"), - prev_staking_token_amount: Uint128(0), - }) - .unwrap(), - send: vec![], - }) - ] - ); - - deps.querier.with_token_balance(Uint128(100u128)); // recive 100 lptoken - - // wrong asset - let msg = HandleMsg::AutoStakeHook { - asset_token: HumanAddr::from("asset1"), - staking_token: HumanAddr::from("lptoken"), - staker_addr: HumanAddr::from("addr0000"), - prev_staking_token_amount: Uint128(0), - }; - let env = mock_env(MOCK_CONTRACT_ADDR, &[]); - let _res = handle(&mut deps, env, msg).unwrap_err(); // pool not found error - - // valid msg - let msg = HandleMsg::AutoStakeHook { - asset_token: HumanAddr::from("asset"), - staking_token: HumanAddr::from("lptoken"), - staker_addr: HumanAddr::from("addr0000"), - prev_staking_token_amount: Uint128(0), - }; - - // unauthorized attempt - let env = mock_env("addr0000", &[]); - let res = handle(&mut deps, env, msg.clone()).unwrap_err(); - assert_eq!(res, StdError::unauthorized()); - - // successfull attempt - let env = mock_env(MOCK_CONTRACT_ADDR, &[]); - let res = handle(&mut deps, env, msg).unwrap(); - assert_eq!( - res.log, - vec![ - log("action", "bond"), - log("staker_addr", "addr0000"), - log("asset_token", "asset"), - log("amount", "100"), - ] - ); - - let data = query( - &deps, - QueryMsg::PoolInfo { - asset_token: HumanAddr::from("asset"), - }, - ) - .unwrap(); - let pool_info: PoolInfoResponse = from_binary(&data).unwrap(); - assert_eq!( - pool_info, - PoolInfoResponse { - asset_token: HumanAddr::from("asset"), - staking_token: HumanAddr::from("lptoken"), - total_bond_amount: Uint128(100u128), - total_short_amount: Uint128::zero(), - reward_index: Decimal::zero(), - short_reward_index: Decimal::zero(), - pending_reward: Uint128::zero(), - short_pending_reward: Uint128::zero(), - premium_rate: Decimal::zero(), - short_reward_weight: Decimal::zero(), - premium_updated_time: 0, - } - ); - } -} diff --git a/contracts/mirror_staking/src/state.rs b/contracts/mirror_staking/src/state.rs deleted file mode 100644 index 3ef365473..000000000 --- a/contracts/mirror_staking/src/state.rs +++ /dev/null @@ -1,90 +0,0 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use cosmwasm_std::{CanonicalAddr, Decimal, ReadonlyStorage, StdResult, Storage, Uint128}; -use cosmwasm_storage::{singleton, singleton_read, Bucket, ReadonlyBucket}; - -pub static KEY_CONFIG: &[u8] = b"config"; -pub static PREFIX_POOL_INFO: &[u8] = b"pool_info"; - -static PREFIX_REWARD: &[u8] = b"reward"; -static PREFIX_SHORT_REWARD: &[u8] = b"short_reward"; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct Config { - pub owner: CanonicalAddr, - pub mirror_token: CanonicalAddr, - pub mint_contract: CanonicalAddr, - pub oracle_contract: CanonicalAddr, - pub terraswap_factory: CanonicalAddr, - pub base_denom: String, - pub premium_min_update_interval: u64, -} - -pub fn store_config(storage: &mut S, config: &Config) -> StdResult<()> { - singleton(storage, KEY_CONFIG).save(config) -} - -pub fn read_config(storage: &S) -> StdResult { - singleton_read(storage, KEY_CONFIG).load() -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct PoolInfo { - pub staking_token: CanonicalAddr, - pub pending_reward: Uint128, // not distributed amount due to zero bonding - pub short_pending_reward: Uint128, // not distributed amount due to zero bonding - pub total_bond_amount: Uint128, - pub total_short_amount: Uint128, - pub reward_index: Decimal, - pub short_reward_index: Decimal, - pub premium_rate: Decimal, - pub short_reward_weight: Decimal, - pub premium_updated_time: u64, -} - -pub fn store_pool_info( - storage: &mut S, - asset_token: &CanonicalAddr, - pool_info: &PoolInfo, -) -> StdResult<()> { - Bucket::new(PREFIX_POOL_INFO, storage).save(asset_token.as_slice(), pool_info) -} - -pub fn read_pool_info(storage: &S, asset_token: &CanonicalAddr) -> StdResult { - ReadonlyBucket::new(PREFIX_POOL_INFO, storage).load(asset_token.as_slice()) -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct RewardInfo { - pub index: Decimal, - pub bond_amount: Uint128, - pub pending_reward: Uint128, -} - -/// returns a bucket with all rewards owned by this owner (query it by owner) -pub fn rewards_store<'a, S: Storage>( - storage: &'a mut S, - owner: &CanonicalAddr, - is_short: bool, -) -> Bucket<'a, S, RewardInfo> { - if is_short { - Bucket::multilevel(&[PREFIX_SHORT_REWARD, owner.as_slice()], storage) - } else { - Bucket::multilevel(&[PREFIX_REWARD, owner.as_slice()], storage) - } -} - -/// returns a bucket with all rewards owned by this owner (query it by owner) -/// (read-only version for queries) -pub fn rewards_read<'a, S: ReadonlyStorage>( - storage: &'a S, - owner: &CanonicalAddr, - is_short: bool, -) -> ReadonlyBucket<'a, S, RewardInfo> { - if is_short { - ReadonlyBucket::multilevel(&[PREFIX_SHORT_REWARD, owner.as_slice()], storage) - } else { - ReadonlyBucket::multilevel(&[PREFIX_REWARD, owner.as_slice()], storage) - } -} diff --git a/contracts/mirror_collateral_oracle/.cargo/config b/contracts/oracle/.cargo/config similarity index 78% rename from contracts/mirror_collateral_oracle/.cargo/config rename to contracts/oracle/.cargo/config index 7c115322a..882fe08f6 100644 --- a/contracts/mirror_collateral_oracle/.cargo/config +++ b/contracts/oracle/.cargo/config @@ -1,6 +1,5 @@ [alias] wasm = "build --release --target wasm32-unknown-unknown" -wasm-debug = "build --target wasm32-unknown-unknown" unit-test = "test --lib --features backtraces" integration-test = "test --test integration" schema = "run --example schema" diff --git a/contracts/oracle/.circleci/config.yml b/contracts/oracle/.circleci/config.yml new file mode 100644 index 000000000..127e1ae7d --- /dev/null +++ b/contracts/oracle/.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/mirror_limit_order/Cargo.toml b/contracts/oracle/Cargo.toml similarity index 55% rename from contracts/mirror_limit_order/Cargo.toml rename to contracts/oracle/Cargo.toml index f93150930..5eb777911 100644 --- a/contracts/mirror_limit_order/Cargo.toml +++ b/contracts/oracle/Cargo.toml @@ -1,10 +1,8 @@ [package] -name = "mirror-limit-order" -version = "1.1.0" -authors = ["Terraform Labs, PTE."] +name = "oracle" +version = "0.1.0" +authors = ["Guy Garcia "] edition = "2018" -description = "A limit order contract for Mirror Protocol - provides limit order features" -license = "Apache-2.0" 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. @@ -29,20 +27,18 @@ 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] -cw20 = "0.2" -cosmwasm-std = { version = "0.10.1", features = ["iterator"] } -cosmwasm-storage = { version = "0.10.1", features = ["iterator"] } -integer-sqrt = "0.1.5" -mirror-protocol = { version = "1.1.0", path = "../../packages/mirror_protocol" } -terraswap = "1.2.0" -terra-cosmwasm = "1.2.3" +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"] } - -[dev-dependencies] -cosmwasm-schema = "0.10.1" \ No newline at end of file +snafu = { version = "0.6.3" } diff --git a/contracts/oracle/Makefile b/contracts/oracle/Makefile new file mode 100644 index 000000000..2493c22f4 --- /dev/null +++ b/contracts/oracle/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/oracle/README.md b/contracts/oracle/README.md new file mode 100644 index 000000000..49cfadadb --- /dev/null +++ b/contracts/oracle/README.md @@ -0,0 +1,31 @@ +# Oracle Contract +* [Introduction](#Introduction) +* [Sections](#Sections) + * [Init](#Init) + * [User](#User) + * Queries + * [GetScrtPrice](#GetScrtPrice) +# Introduction +The oracle contract is used to query the price of different currencies + +# Sections + +## 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 | + +##User + +### Queries + +#### GetScrtPrice +Get SCRT's price according to band protocol. +##### Response +```json +{ + "get_scrt_price": { + } +} +``` \ No newline at end of file diff --git a/contracts/mirror_collector/examples/schema.rs b/contracts/oracle/examples/schema.rs similarity index 67% rename from contracts/mirror_collector/examples/schema.rs rename to contracts/oracle/examples/schema.rs index 68a280e6f..9cf01c780 100644 --- a/contracts/mirror_collector/examples/schema.rs +++ b/contracts/oracle/examples/schema.rs @@ -2,7 +2,9 @@ use std::env::current_dir; use std::fs::create_dir_all; use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; -use mirror_protocol::collector::{ConfigResponse, HandleMsg, InitMsg, QueryMsg}; + +use secure_secret::msg::{CountResponse, HandleMsg, InitMsg, QueryMsg}; +use secure_secret::state::State; fn main() { let mut out_dir = current_dir().unwrap(); @@ -13,5 +15,6 @@ fn main() { export_schema(&schema_for!(InitMsg), &out_dir); export_schema(&schema_for!(HandleMsg), &out_dir); export_schema(&schema_for!(QueryMsg), &out_dir); - export_schema(&schema_for!(ConfigResponse), &out_dir); + export_schema(&schema_for!(State), &out_dir); + export_schema(&schema_for!(CountResponse), &out_dir); } diff --git a/contracts/mirror_collector/rustfmt.toml b/contracts/oracle/rustfmt.toml similarity index 100% rename from contracts/mirror_collector/rustfmt.toml rename to contracts/oracle/rustfmt.toml diff --git a/contracts/mirror_gov/schema/poll_count_response.json b/contracts/oracle/schema/count_response.json similarity index 57% rename from contracts/mirror_gov/schema/poll_count_response.json rename to contracts/oracle/schema/count_response.json index 7b7bf61ee..fa1e81f32 100644 --- a/contracts/mirror_gov/schema/poll_count_response.json +++ b/contracts/oracle/schema/count_response.json @@ -1,15 +1,14 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "PollCountResponse", + "title": "CountResponse", "type": "object", "required": [ - "poll_count" + "count" ], "properties": { - "poll_count": { + "count": { "type": "integer", - "format": "uint64", - "minimum": 0.0 + "format": "int32" } } } diff --git a/contracts/mirror_collector/schema/handle_msg.json b/contracts/oracle/schema/handle_msg.json similarity index 63% rename from contracts/mirror_collector/schema/handle_msg.json rename to contracts/oracle/schema/handle_msg.json index e81e2d7a2..a9783e3e1 100644 --- a/contracts/mirror_collector/schema/handle_msg.json +++ b/contracts/oracle/schema/handle_msg.json @@ -5,37 +5,33 @@ { "type": "object", "required": [ - "convert" + "increment" ], "properties": { - "convert": { - "type": "object", - "required": [ - "asset_token" - ], - "properties": { - "asset_token": { - "$ref": "#/definitions/HumanAddr" - } - } + "increment": { + "type": "object" } } }, { "type": "object", "required": [ - "distribute" + "reset" ], "properties": { - "distribute": { - "type": "object" + "reset": { + "type": "object", + "required": [ + "count" + ], + "properties": { + "count": { + "type": "integer", + "format": "int32" + } + } } } } - ], - "definitions": { - "HumanAddr": { - "type": "string" - } - } + ] } diff --git a/contracts/oracle/schema/init_msg.json b/contracts/oracle/schema/init_msg.json new file mode 100644 index 000000000..998bc1ba9 --- /dev/null +++ b/contracts/oracle/schema/init_msg.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InitMsg", + "type": "object", + "required": [ + "count" + ], + "properties": { + "count": { + "type": "integer", + "format": "int32" + } + } +} diff --git a/contracts/mirror_community/schema/query_msg.json b/contracts/oracle/schema/query_msg.json similarity index 84% rename from contracts/mirror_community/schema/query_msg.json rename to contracts/oracle/schema/query_msg.json index 8af77727d..daf9e90b5 100644 --- a/contracts/mirror_community/schema/query_msg.json +++ b/contracts/oracle/schema/query_msg.json @@ -5,10 +5,10 @@ { "type": "object", "required": [ - "config" + "get_count" ], "properties": { - "config": { + "get_count": { "type": "object" } } diff --git a/contracts/mirror_gov/schema/execute_msg.json b/contracts/oracle/schema/state.json similarity index 72% rename from contracts/mirror_gov/schema/execute_msg.json rename to contracts/oracle/schema/state.json index 320229fce..9cd0f1bc9 100644 --- a/contracts/mirror_gov/schema/execute_msg.json +++ b/contracts/oracle/schema/state.json @@ -1,17 +1,18 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ExecuteMsg", + "title": "State", "type": "object", "required": [ - "contract", - "msg" + "count", + "owner" ], "properties": { - "contract": { - "$ref": "#/definitions/HumanAddr" + "count": { + "type": "integer", + "format": "int32" }, - "msg": { - "$ref": "#/definitions/Binary" + "owner": { + "$ref": "#/definitions/CanonicalAddr" } }, "definitions": { @@ -19,8 +20,8 @@ "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", "type": "string" }, - "HumanAddr": { - "type": "string" + "CanonicalAddr": { + "$ref": "#/definitions/Binary" } } } diff --git a/contracts/oracle/src/contract.rs b/contracts/oracle/src/contract.rs new file mode 100644 index 000000000..f19b7a7c3 --- /dev/null +++ b/contracts/oracle/src/contract.rs @@ -0,0 +1,88 @@ +use cosmwasm_std::{debug_print, to_binary, Api, Binary, Env, Extern, HandleResponse, InitResponse, Querier, StdResult, Storage, Uint128}; +use crate::state::{config}; +use shade_protocol::{ + oracle::{InitMsg, HandleMsg, QueryMsg, PriceResponse, OracleConfig}, +}; + +pub fn init( + deps: &mut Extern, + env: Env, + msg: InitMsg, +) -> StdResult { + let state = OracleConfig { + owner: match msg.admin { + None => { env.message.sender.clone() } + Some(admin) => { admin } + }, + }; + + config(&mut deps.storage).save(&state)?; + + debug_print!("Contract was initialized by {}", env.message.sender); + + Ok(InitResponse::default()) +} + +pub fn handle( + _deps: &mut Extern, + _env: Env, + msg: HandleMsg, +) -> StdResult { + match msg { + } +} + +pub fn query( + _deps: &Extern, + msg: QueryMsg, +) -> StdResult { + match msg { + QueryMsg::GetScrtPrice {} => to_binary(&query_silk_price()?), + } +} + +fn query_silk_price<>() -> StdResult { + let price:Uint128 = Uint128(10u64.pow(18) as u128); + Ok(PriceResponse { price }) +} + +#[cfg(test)] +mod tests { + use super::*; + use cosmwasm_std::testing::{mock_dependencies, mock_env, MockStorage, MockApi, MockQuerier}; + use cosmwasm_std::{coins, from_binary}; + + fn dummy_init(admin: &str) -> Extern { + let mut deps = mock_dependencies(20, &[]); + let msg = InitMsg { + admin: None, + }; + let env = mock_env(admin.to_string(), &coins(1000, "earth")); + let _res = init(&mut deps, env, msg).unwrap(); + + return deps + } + + #[test] + fn proper_initialization() { + let mut deps = mock_dependencies(20, &[]); + + let msg = InitMsg { admin: None }; + 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()); + } + + #[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); + } +} diff --git a/contracts/oracle/src/lib.rs b/contracts/oracle/src/lib.rs new file mode 100644 index 000000000..18d548e3f --- /dev/null +++ b/contracts/oracle/src/lib.rs @@ -0,0 +1,39 @@ +pub mod contract; +pub mod state; + +#[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/src/state.rs b/contracts/oracle/src/state.rs new file mode 100644 index 000000000..d5ae1b5ab --- /dev/null +++ b/contracts/oracle/src/state.rs @@ -0,0 +1,21 @@ +use cosmwasm_std::{Storage}; +use cosmwasm_storage::{singleton, singleton_read, ReadonlySingleton, Singleton}; +use shade_protocol::oracle::OracleConfig; + +pub static CONFIG_KEY: &[u8] = b"config"; + +/* +pub struct ReferenceData { + pub rate: uint256, + pub last_updated_base: uint256, + pub last_updated_quote: uint256, +} +*/ + +pub fn config(storage: &mut S) -> Singleton { + singleton(storage, CONFIG_KEY) +} + +pub fn config_read(storage: &S) -> ReadonlySingleton { + singleton_read(storage, CONFIG_KEY) +} diff --git a/contracts/oracle/tests/integration.rs b/contracts/oracle/tests/integration.rs new file mode 100644 index 000000000..6c26b034f --- /dev/null +++ b/contracts/oracle/tests/integration.rs @@ -0,0 +1,18 @@ +//! This integration test tries to run and call the generated wasm. +//! It depends on a Wasm build being available, which you can create with `cargo wasm`. +//! Then running `cargo integration-test` will validate we can properly call into that generated Wasm. +//! +//! You can easily convert unit tests to integration tests. +//! 1. First copy them over verbatum, +//! 2. Then change +//! let mut deps = mock_dependencies(20, &[]); +//! to +//! let mut deps = mock_instance(WASM, &[]); +//! 3. If you access raw storage, where ever you see something like: +//! deps.storage.get(CONFIG_KEY).expect("no data stored"); +//! replace it with: +//! deps.with_storage(|store| { +//! let data = store.get(CONFIG_KEY).expect("no data stored"); +//! //... +//! }); +//! 4. Anywhere you see query(&deps, ...) you must replace it with query(&mut deps, ...) diff --git a/packages/mirror_protocol/.cargo/config b/packages/mirror_protocol/.cargo/config deleted file mode 100644 index 7c115322a..000000000 --- a/packages/mirror_protocol/.cargo/config +++ /dev/null @@ -1,6 +0,0 @@ -[alias] -wasm = "build --release --target wasm32-unknown-unknown" -wasm-debug = "build --target wasm32-unknown-unknown" -unit-test = "test --lib --features backtraces" -integration-test = "test --test integration" -schema = "run --example schema" diff --git a/packages/mirror_protocol/.editorconfig b/packages/mirror_protocol/.editorconfig deleted file mode 100644 index 3d36f20b1..000000000 --- a/packages/mirror_protocol/.editorconfig +++ /dev/null @@ -1,11 +0,0 @@ -root = true - -[*] -indent_style = space -indent_size = 2 -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true - -[*.rs] -indent_size = 4 diff --git a/packages/mirror_protocol/Cargo.toml b/packages/mirror_protocol/Cargo.toml deleted file mode 100644 index e3c7a98b7..000000000 --- a/packages/mirror_protocol/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "mirror-protocol" -version = "1.1.0" -authors = ["Terraform Labs, PTE."] -edition = "2018" -description = "Common helpers for mirror-protocol" -license = "Apache-2.0" -repository = "https://github.com/mirror-protocol/mirror-contracts" -homepage = "https://mirror.finance" -documentation = "https://docs.mirror.finance" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[features] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces"] - -[dependencies] -cw20 = "0.2" -cosmwasm-std = { version = "0.10.1", default-features = false, features = ["iterator"] } -cosmwasm-storage = { version = "0.10.1", default-features = false, features = ["iterator"] } -terraswap = "1.1.0" -schemars = "0.7" -serde = { version = "1.0.103", default-features = false, features = ["derive"] } diff --git a/packages/mirror_protocol/src/collateral_oracle.rs b/packages/mirror_protocol/src/collateral_oracle.rs deleted file mode 100644 index f9693fdbb..000000000 --- a/packages/mirror_protocol/src/collateral_oracle.rs +++ /dev/null @@ -1,133 +0,0 @@ -use std::fmt; - -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use cosmwasm_std::{Decimal, HumanAddr}; -use terraswap::asset::AssetInfo; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct InitMsg { - pub owner: HumanAddr, - pub mint_contract: HumanAddr, - pub factory_contract: HumanAddr, - pub base_denom: String, - pub mirror_oracle: HumanAddr, - pub anchor_oracle: HumanAddr, - pub band_oracle: HumanAddr, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum HandleMsg { - UpdateConfig { - owner: Option, - mint_contract: Option, - factory_contract: Option, - base_denom: Option, - mirror_oracle: Option, - anchor_oracle: Option, - band_oracle: Option, - }, - RegisterCollateralAsset { - asset: AssetInfo, - price_source: SourceType, - multiplier: Decimal, - }, - RevokeCollateralAsset { - asset: AssetInfo, - }, - UpdateCollateralPriceSource { - asset: AssetInfo, - price_source: SourceType, - }, - UpdateCollateralMultiplier { - asset: AssetInfo, - multiplier: Decimal, - } -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum QueryMsg { - Config {}, - CollateralPrice { - asset: String, - }, - CollateralAssetInfo { - asset: String, - }, - CollateralAssetInfos {}, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct ConfigResponse { - pub owner: HumanAddr, - pub mint_contract: HumanAddr, - pub factory_contract: HumanAddr, - pub base_denom: String, - pub mirror_oracle: HumanAddr, - pub anchor_oracle: HumanAddr, - pub band_oracle: HumanAddr, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct CollateralPriceResponse { - pub asset: String, - pub rate: Decimal, - pub last_updated: u64, - pub multiplier: Decimal, - pub is_revoked: bool, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct CollateralInfoResponse { - pub asset: String, - pub multiplier: Decimal, - pub source_type: String, - pub is_revoked: bool, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct CollateralInfosResponse { - pub collaterals: Vec, -} - -/// We currently take no arguments for migrations -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct MigrateMsg {} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum SourceType { - MirrorOracle {}, - AnchorOracle {}, - BandOracle {}, - FixedPrice { - price: Decimal, - }, - Terraswap { - terraswap_pair_addr: HumanAddr, - intermediate_denom: Option, - }, - AnchorMarket { - anchor_market_addr: HumanAddr, - }, - Native { - native_denom: String, - }, -} - -impl fmt::Display for SourceType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - SourceType::MirrorOracle{..} => write!(f, "mirror_oracle"), - SourceType::AnchorOracle{..} => write!(f, "anchor_oracle"), - SourceType::BandOracle{..} => write!(f, "band_oracle"), - SourceType::FixedPrice{..} => write!(f, "fixed_price"), - SourceType::Terraswap{..} => write!(f, "terraswap"), - SourceType::AnchorMarket{..} => write!(f, "anchor_market"), - SourceType::Native {..} => write!(f, "native"), - } - } -} diff --git a/packages/mirror_protocol/src/collector.rs b/packages/mirror_protocol/src/collector.rs deleted file mode 100644 index beaedb9bb..000000000 --- a/packages/mirror_protocol/src/collector.rs +++ /dev/null @@ -1,38 +0,0 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use cosmwasm_std::HumanAddr; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct InitMsg { - pub distribution_contract: HumanAddr, // collected rewards receiver - pub terraswap_factory: HumanAddr, - pub mirror_token: HumanAddr, - pub base_denom: String, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum HandleMsg { - Convert { asset_token: HumanAddr }, - Distribute {}, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum QueryMsg { - Config {}, -} - -// We define a custom struct for each query response -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct ConfigResponse { - pub distribution_contract: HumanAddr, // collected rewards receiver - pub terraswap_factory: HumanAddr, - pub mirror_token: HumanAddr, - pub base_denom: String, -} - -/// We currently take no arguments for migrations -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct MigrateMsg {} diff --git a/packages/mirror_protocol/src/common.rs b/packages/mirror_protocol/src/common.rs deleted file mode 100644 index 226522b55..000000000 --- a/packages/mirror_protocol/src/common.rs +++ /dev/null @@ -1,21 +0,0 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use cosmwasm_std::Order; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum OrderBy { - Asc, - Desc, -} - -impl Into for OrderBy { - fn into(self) -> Order { - if self == OrderBy::Asc { - Order::Ascending - } else { - Order::Descending - } - } -} diff --git a/packages/mirror_protocol/src/factory.rs b/packages/mirror_protocol/src/factory.rs deleted file mode 100644 index 927586142..000000000 --- a/packages/mirror_protocol/src/factory.rs +++ /dev/null @@ -1,135 +0,0 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use cosmwasm_std::{Binary, Decimal, HumanAddr, Uint128}; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct InitMsg { - pub token_code_id: u64, - pub base_denom: String, - pub distribution_schedule: Vec<(u64, u64, Uint128)>, // [[start_time, end_time, distribution_amount], [], ...] -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum HandleMsg { - /////////////////// - /// Owner Operations - /////////////////// - PostInitialize { - owner: HumanAddr, - terraswap_factory: HumanAddr, - mirror_token: HumanAddr, - staking_contract: HumanAddr, - oracle_contract: HumanAddr, - mint_contract: HumanAddr, - commission_collector: HumanAddr, - }, - UpdateConfig { - owner: Option, - token_code_id: Option, - distribution_schedule: Option>, // [[start_time, end_time, distribution_amount], [], ...] - }, - UpdateWeight { - asset_token: HumanAddr, - weight: u32, - }, - Whitelist { - /// asset name used to create token contract - name: String, - /// asset symbol used to create token contract - symbol: String, - /// authorized asset oracle feeder - oracle_feeder: HumanAddr, - /// used to create all necessary contract or register asset - params: Params, - }, - /// Internal use - TokenCreationHook { - oracle_feeder: HumanAddr, - }, - /// Internal use except MIR registration - TerraswapCreationHook { - asset_token: HumanAddr, - }, - PassCommand { - contract_addr: HumanAddr, - msg: Binary, - }, - - ////////////////////// - /// Feeder Operations - /// ////////////////// - - /// Revoke asset from MIR rewards pool - /// and register end_price to mint contract - RevokeAsset { - asset_token: HumanAddr, - end_price: Decimal, - }, - /// Migrate asset to new asset by registering - /// end_price to mint contract and add - /// the new asset to MIR rewards pool - MigrateAsset { - name: String, - symbol: String, - from_token: HumanAddr, - end_price: Decimal, - }, - - /////////////////// - /// User Operations - /////////////////// - Distribute {}, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum QueryMsg { - Config {}, - DistributionInfo {}, -} - -// We define a custom struct for each query response -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct ConfigResponse { - pub owner: HumanAddr, - pub mirror_token: HumanAddr, - pub mint_contract: HumanAddr, - pub staking_contract: HumanAddr, - pub commission_collector: HumanAddr, - pub oracle_contract: HumanAddr, - pub terraswap_factory: HumanAddr, - pub token_code_id: u64, - pub base_denom: String, - pub genesis_time: u64, - pub distribution_schedule: Vec<(u64, u64, Uint128)>, -} - -// We define a custom struct for each query response -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct DistributionInfoResponse { - pub weights: Vec<(HumanAddr, u32)>, - pub last_distributed: u64, -} - -/// We currently take no arguments for migrations -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct MigrateMsg {} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub struct Params { - /// Auction discount rate applied to asset mint - pub auction_discount: Decimal, - /// Minium collateral ratio applied to asset mint - pub min_collateral_ratio: Decimal, - /// Distribution weight (default is 30, which is 1/10 of MIR distribution weight) - pub weight: Option, - /// For pre-IPO assets, time period after asset creation in which minting is enabled - pub mint_period: Option, - /// For pre-IPO assets, collateral ratio for the asset after ipo - pub min_collateral_ratio_after_ipo: Option, - /// For pre-IPO assets, fixed price during minting period - pub pre_ipo_price: Option, -} diff --git a/packages/mirror_protocol/src/gov.rs b/packages/mirror_protocol/src/gov.rs deleted file mode 100644 index 3defb3faa..000000000 --- a/packages/mirror_protocol/src/gov.rs +++ /dev/null @@ -1,228 +0,0 @@ -use cosmwasm_std::{Binary, Decimal, HumanAddr, Uint128}; -use cw20::Cw20ReceiveMsg; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use std::fmt; - -use crate::common::OrderBy; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct InitMsg { - pub mirror_token: HumanAddr, - pub quorum: Decimal, - pub threshold: Decimal, - pub voting_period: u64, - pub effective_delay: u64, - pub expiration_period: u64, - pub proposal_deposit: Uint128, - pub voter_weight: Decimal, - pub snapshot_period: u64, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum HandleMsg { - Receive(Cw20ReceiveMsg), - UpdateConfig { - owner: Option, - quorum: Option, - threshold: Option, - voting_period: Option, - effective_delay: Option, - expiration_period: Option, - proposal_deposit: Option, - voter_weight: Option, - snapshot_period: Option, - }, - CastVote { - poll_id: u64, - vote: VoteOption, - amount: Uint128, - }, - WithdrawVotingTokens { - amount: Option, - }, - WithdrawVotingRewards {}, - StakeVotingRewards {}, - EndPoll { - poll_id: u64, - }, - ExecutePoll { - poll_id: u64, - }, - ExpirePoll { - poll_id: u64, - }, - SnapshotPoll { - poll_id: u64, - }, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum Cw20HookMsg { - /// StakeVotingTokens a user can stake their mirror token to receive rewards - /// or do vote on polls - StakeVotingTokens {}, - /// CreatePoll need to receive deposit from a proposer - CreatePoll { - title: String, - description: String, - link: Option, - execute_msg: Option, - }, - /// Deposit rewards to be distributed among stakers and voters - DepositReward {}, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub struct ExecuteMsg { - pub contract: HumanAddr, - pub msg: Binary, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum QueryMsg { - Config {}, - State {}, - Staker { - address: HumanAddr, - }, - Poll { - poll_id: u64, - }, - Polls { - filter: Option, - start_after: Option, - limit: Option, - order_by: Option, - }, - Voters { - poll_id: u64, - start_after: Option, - limit: Option, - order_by: Option, - }, -} - -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema)] -pub struct ConfigResponse { - pub owner: HumanAddr, - pub mirror_token: HumanAddr, - pub quorum: Decimal, - pub threshold: Decimal, - pub voting_period: u64, - pub effective_delay: u64, - pub expiration_period: u64, - pub proposal_deposit: Uint128, - pub voter_weight: Decimal, - pub snapshot_period: u64, -} - -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema)] -pub struct StateResponse { - pub poll_count: u64, - pub total_share: Uint128, - pub total_deposit: Uint128, - pub pending_voting_rewards: Uint128, -} - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema)] -pub struct PollResponse { - pub id: u64, - pub creator: HumanAddr, - pub status: PollStatus, - pub end_height: u64, - pub title: String, - pub description: String, - pub link: Option, - pub deposit_amount: Uint128, - pub execute_data: Option, - pub yes_votes: Uint128, // balance - pub no_votes: Uint128, // balance - pub abstain_votes: Uint128, // balance - pub total_balance_at_end_poll: Option, - pub voters_reward: Uint128, - pub staked_amount: Option, -} - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema)] -pub struct PollsResponse { - pub polls: Vec, -} - -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema)] -pub struct PollCountResponse { - pub poll_count: u64, -} - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema)] -pub struct StakerResponse { - pub balance: Uint128, - pub share: Uint128, - pub locked_balance: Vec<(u64, VoterInfo)>, - pub withdrawable_polls: Vec<(u64, Uint128)>, - pub pending_voting_rewards: Uint128, -} - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema)] -pub struct VotersResponseItem { - pub voter: HumanAddr, - pub vote: VoteOption, - pub balance: Uint128, -} - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema)] -pub struct VotersResponse { - pub voters: Vec, -} - -/// Migrates the contract state, currently taking a state version argument -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct MigrateMsg { - pub version: u64, // current contract migration state version - pub voter_weight: Decimal, - pub snapshot_period: u64, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct VoterInfo { - pub vote: VoteOption, - pub balance: Uint128, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum PollStatus { - InProgress, - Passed, - Rejected, - Executed, - Expired, -} - -impl fmt::Display for PollStatus { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}", self) - } -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum VoteOption { - Yes, - No, - Abstain, -} - -impl fmt::Display for VoteOption { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - VoteOption::Yes => write!(f, "yes"), - VoteOption::No => write!(f, "no"), - VoteOption::Abstain => write!(f, "abstain"), - } - } -} diff --git a/packages/mirror_protocol/src/lib.rs b/packages/mirror_protocol/src/lib.rs deleted file mode 100644 index c74254e62..000000000 --- a/packages/mirror_protocol/src/lib.rs +++ /dev/null @@ -1,11 +0,0 @@ -pub mod collector; -pub mod common; -pub mod community; -pub mod factory; -pub mod gov; -pub mod mint; -pub mod oracle; -pub mod staking; -pub mod limit_order; -pub mod collateral_oracle; -pub mod lock; \ No newline at end of file diff --git a/packages/mirror_protocol/src/limit_order.rs b/packages/mirror_protocol/src/limit_order.rs deleted file mode 100644 index 6e57c7d1b..000000000 --- a/packages/mirror_protocol/src/limit_order.rs +++ /dev/null @@ -1,86 +0,0 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use cosmwasm_std::{HumanAddr, Uint128}; -use cw20::Cw20ReceiveMsg; -use terraswap::asset::Asset; - -use crate::common::OrderBy; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct InitMsg {} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum HandleMsg { - Receive(Cw20ReceiveMsg), - - /////////////////////// - /// User Operations /// - /////////////////////// - SubmitOrder { - offer_asset: Asset, - ask_asset: Asset, - }, - CancelOrder { - order_id: u64, - }, - - /// Arbitrager execute order to get profit - ExecuteOrder { - execute_asset: Asset, - order_id: u64, - }, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum Cw20HookMsg { - SubmitOrder { - ask_asset: Asset, - }, - - /// Arbitrager execute order to get profit - ExecuteOrder { - order_id: u64, - }, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum QueryMsg { - Order { - order_id: u64, - }, - Orders { - bidder_addr: Option, - start_after: Option, - limit: Option, - order_by: Option, - }, - LastOrderId {}, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct OrderResponse { - pub order_id: u64, - pub bidder_addr: HumanAddr, - pub offer_asset: Asset, - pub ask_asset: Asset, - pub filled_offer_amount: Uint128, - pub filled_ask_amount: Uint128, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct OrdersResponse { - pub orders: Vec, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct LastOrderIdResponse { - pub last_order_id: u64, -} - -/// We currently take no arguments for migrations -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct MigrateMsg {} diff --git a/packages/mirror_protocol/src/lock.rs b/packages/mirror_protocol/src/lock.rs deleted file mode 100644 index 3f2e20057..000000000 --- a/packages/mirror_protocol/src/lock.rs +++ /dev/null @@ -1,58 +0,0 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use cosmwasm_std::{HumanAddr, Uint128}; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct InitMsg { - pub owner: HumanAddr, - pub mint_contract: HumanAddr, - pub base_denom: String, - pub lockup_period: u64, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum HandleMsg { - UpdateConfig { - owner: Option, - mint_contract: Option, - base_denom: Option, - lockup_period: Option, - }, - LockPositionFundsHook { - position_idx: Uint128, - receiver: HumanAddr, - }, - UnlockPositionFunds { - position_idx: Uint128, - }, - ReleasePositionFunds { - position_idx: Uint128, - } -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum QueryMsg { - Config {}, - PositionLockInfo { - position_idx: Uint128, - }, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct ConfigResponse { - pub owner: HumanAddr, - pub mint_contract: HumanAddr, - pub base_denom: String, - pub lockup_period: u64, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct PositionLockInfoResponse { - pub idx: Uint128, - pub receiver: HumanAddr, - pub locked_amount: Uint128, - pub unlock_time: u64, -} \ No newline at end of file diff --git a/packages/mirror_protocol/src/mint.rs b/packages/mirror_protocol/src/mint.rs deleted file mode 100644 index 68e78e6db..000000000 --- a/packages/mirror_protocol/src/mint.rs +++ /dev/null @@ -1,195 +0,0 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use cosmwasm_std::{Decimal, HumanAddr, Uint128}; -use cw20::Cw20ReceiveMsg; -use terraswap::asset::{Asset, AssetInfo}; - -use crate::common::OrderBy; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct InitMsg { - pub owner: HumanAddr, - pub oracle: HumanAddr, - pub collector: HumanAddr, - pub collateral_oracle: HumanAddr, - pub staking: HumanAddr, - pub terraswap_factory: HumanAddr, - pub lock: HumanAddr, - pub base_denom: String, - pub token_code_id: u64, - pub protocol_fee_rate: Decimal, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum HandleMsg { - Receive(Cw20ReceiveMsg), - - ////////////////////// - /// Owner Operations - ////////////////////// - - /// Update config; only owner is allowed to execute it - UpdateConfig { - owner: Option, - oracle: Option, - collector: Option, - collateral_oracle: Option, - terraswap_factory: Option, - lock: Option, - token_code_id: Option, - protocol_fee_rate: Option, - }, - /// Update asset related parameters - UpdateAsset { - asset_token: HumanAddr, - auction_discount: Option, - min_collateral_ratio: Option, - ipo_params: Option, - }, - /// Generate asset token initialize msg and register required infos except token address - RegisterAsset { - asset_token: HumanAddr, - auction_discount: Decimal, - min_collateral_ratio: Decimal, - ipo_params: Option, - }, - RegisterMigration { - asset_token: HumanAddr, - end_price: Decimal, - }, - /// Asset feeder is allowed to trigger IPO event on preIPO assets - TriggerIPO { - asset_token: HumanAddr, - }, - - ////////////////////// - /// User Operations - ////////////////////// - // Create position to meet collateral ratio - OpenPosition { - collateral: Asset, - asset_info: AssetInfo, - collateral_ratio: Decimal, - short_params: Option, - }, - /// Deposit more collateral - Deposit { - position_idx: Uint128, - collateral: Asset, - }, - /// Withdraw collateral - Withdraw { - position_idx: Uint128, - collateral: Asset, - }, - /// Convert all deposit collateral to asset - Mint { - position_idx: Uint128, - asset: Asset, - short_params: Option, - }, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct ShortParams { - pub belief_price: Option, - pub max_spread: Option, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct IPOParams { - pub mint_end: u64, - pub pre_ipo_price: Decimal, - pub min_collateral_ratio_after_ipo: Decimal, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum Cw20HookMsg { - // Create position to meet collateral ratio - OpenPosition { - asset_info: AssetInfo, - collateral_ratio: Decimal, - short_params: Option, - }, - /// Deposit more collateral - Deposit { position_idx: Uint128 }, - /// Convert specified asset amount and send back to user - Burn { position_idx: Uint128 }, - /// Buy discounted collateral from the contract with their asset tokens - Auction { position_idx: Uint128 }, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum QueryMsg { - Config {}, - AssetConfig { - asset_token: HumanAddr, - }, - Position { - position_idx: Uint128, - }, - Positions { - owner_addr: Option, - asset_token: Option, - start_after: Option, - limit: Option, - order_by: Option, - }, - NextPositionIdx {}, -} - -// We define a custom struct for each query response -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct ConfigResponse { - pub owner: HumanAddr, - pub oracle: HumanAddr, - pub collector: HumanAddr, - pub collateral_oracle: HumanAddr, - pub staking: HumanAddr, - pub terraswap_factory: HumanAddr, - pub lock: HumanAddr, - pub base_denom: String, - pub token_code_id: u64, - pub protocol_fee_rate: Decimal, -} - -// We define a custom struct for each query response -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct AssetConfigResponse { - pub token: HumanAddr, - pub auction_discount: Decimal, - pub min_collateral_ratio: Decimal, - pub end_price: Option, - pub ipo_params: Option, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct PositionResponse { - pub idx: Uint128, - pub owner: HumanAddr, - pub collateral: Asset, - pub asset: Asset, - pub is_short: bool, -} - -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug, Default)] -pub struct PositionsResponse { - pub positions: Vec, -} - -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug, Default)] -pub struct NextPositionIdxResponse { - pub next_position_idx: Uint128, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct MigrateMsg { - pub collateral_oracle: HumanAddr, - pub staking: HumanAddr, - pub terraswap_factory: HumanAddr, - pub lock: HumanAddr, -} diff --git a/packages/mirror_protocol/src/oracle.rs b/packages/mirror_protocol/src/oracle.rs deleted file mode 100644 index 5df3f3f42..000000000 --- a/packages/mirror_protocol/src/oracle.rs +++ /dev/null @@ -1,85 +0,0 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use crate::common::OrderBy; -use cosmwasm_std::{Decimal, HumanAddr}; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct InitMsg { - pub owner: HumanAddr, - pub base_asset: String, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum HandleMsg { - UpdateConfig { - owner: Option, - }, - /// Used to register new asset or to update feeder - RegisterAsset { - asset_token: HumanAddr, - feeder: HumanAddr, - }, - FeedPrice { - prices: Vec<(HumanAddr, Decimal)>, - }, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum QueryMsg { - Config {}, - Feeder { - asset_token: HumanAddr, - }, - Price { - base_asset: String, - quote_asset: String, - }, - Prices { - start_after: Option, - limit: Option, - order_by: Option, - }, -} - -// We define a custom struct for each query response -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct ConfigResponse { - pub owner: HumanAddr, - pub base_asset: String, -} - -// We define a custom struct for each query response -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct FeederResponse { - pub asset_token: HumanAddr, - pub feeder: HumanAddr, -} - -// We define a custom struct for each query response -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct PriceResponse { - pub rate: Decimal, - pub last_updated_base: u64, - pub last_updated_quote: u64, -} - -// We define a custom struct for each query response -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct PricesResponseElem { - pub asset_token: HumanAddr, - pub price: Decimal, - pub last_updated_time: u64, -} - -// We define a custom struct for each query response -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct PricesResponse { - pub prices: Vec, -} - -/// We currently take no arguments for migrations -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct MigrateMsg {} diff --git a/packages/mirror_protocol/src/staking.rs b/packages/mirror_protocol/src/staking.rs deleted file mode 100644 index 199e74865..000000000 --- a/packages/mirror_protocol/src/staking.rs +++ /dev/null @@ -1,154 +0,0 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use cosmwasm_std::{Decimal, HumanAddr, Uint128}; -use cw20::Cw20ReceiveMsg; -use terraswap::asset::Asset; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct InitMsg { - pub owner: HumanAddr, - pub mirror_token: HumanAddr, - pub mint_contract: HumanAddr, - pub oracle_contract: HumanAddr, - pub terraswap_factory: HumanAddr, - pub base_denom: String, - pub premium_min_update_interval: u64, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum HandleMsg { - Receive(Cw20ReceiveMsg), - - //////////////////////// - /// Owner operations /// - //////////////////////// - UpdateConfig { - owner: Option, - premium_min_update_interval: Option, - }, - RegisterAsset { - asset_token: HumanAddr, - staking_token: HumanAddr, - }, - - //////////////////////// - /// User operations /// - //////////////////////// - Unbond { - asset_token: HumanAddr, - amount: Uint128, - }, - /// Withdraw pending rewards - Withdraw { - // If the asset token is not given, then all rewards are withdrawn - asset_token: Option, - }, - /// Provides liquidity and automatically stakes the LP tokens - AutoStake { - assets: [Asset; 2], - slippage_tolerance: Option, - }, - /// Hook to stake the minted LP tokens - AutoStakeHook { - asset_token: HumanAddr, - staking_token: HumanAddr, - staker_addr: HumanAddr, - prev_staking_token_amount: Uint128, - }, - - ////////////////////////////////// - /// Permission-less operations /// - ////////////////////////////////// - AdjustPremium { - asset_tokens: Vec, - }, - - //////////////////////////////// - /// Mint contract operations /// - //////////////////////////////// - IncreaseShortToken { - asset_token: HumanAddr, - staker_addr: HumanAddr, - amount: Uint128, - }, - DecreaseShortToken { - asset_token: HumanAddr, - staker_addr: HumanAddr, - amount: Uint128, - }, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum Cw20HookMsg { - Bond { asset_token: HumanAddr }, - DepositReward { rewards: Vec<(HumanAddr, Uint128)> }, -} - -/// We currently take no arguments for migrations -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct MigrateMsg { - pub mint_contract: HumanAddr, - pub oracle_contract: HumanAddr, - pub terraswap_factory: HumanAddr, - pub base_denom: String, - pub premium_min_update_interval: u64, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum QueryMsg { - Config {}, - PoolInfo { - asset_token: HumanAddr, - }, - RewardInfo { - staker_addr: HumanAddr, - asset_token: Option, - }, -} - -// We define a custom struct for each query response -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct ConfigResponse { - pub owner: HumanAddr, - pub mirror_token: HumanAddr, - pub mint_contract: HumanAddr, - pub oracle_contract: HumanAddr, - pub terraswap_factory: HumanAddr, - pub base_denom: String, - pub premium_min_update_interval: u64, -} - -// We define a custom struct for each query response -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct PoolInfoResponse { - pub asset_token: HumanAddr, - pub staking_token: HumanAddr, - pub total_bond_amount: Uint128, - pub total_short_amount: Uint128, - pub reward_index: Decimal, - pub short_reward_index: Decimal, - pub pending_reward: Uint128, - pub short_pending_reward: Uint128, - pub premium_rate: Decimal, - pub short_reward_weight: Decimal, - pub premium_updated_time: u64, -} - -// We define a custom struct for each query response -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct RewardInfoResponse { - pub staker_addr: HumanAddr, - pub reward_infos: Vec, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct RewardInfoResponseItem { - pub asset_token: HumanAddr, - pub bond_amount: Uint128, - pub pending_reward: Uint128, - pub is_short: bool, -} diff --git a/contracts/mirror_collector/.cargo/config b/packages/shade_protocol/.cargo/config similarity index 78% rename from contracts/mirror_collector/.cargo/config rename to packages/shade_protocol/.cargo/config index 7c115322a..882fe08f6 100644 --- a/contracts/mirror_collector/.cargo/config +++ b/packages/shade_protocol/.cargo/config @@ -1,6 +1,5 @@ [alias] wasm = "build --release --target wasm32-unknown-unknown" -wasm-debug = "build --target wasm32-unknown-unknown" unit-test = "test --lib --features backtraces" integration-test = "test --test integration" schema = "run --example schema" diff --git a/packages/shade_protocol/Cargo.toml b/packages/shade_protocol/Cargo.toml new file mode 100644 index 000000000..57a0a82f6 --- /dev/null +++ b/packages/shade_protocol/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "shade-protocol" +version = "0.1.0" +authors = ["Guy Garcia "] +edition = "2018" + +[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] +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"} +schemars = "0.7" +serde = { version = "1.0.103", default-features = false, features = ["derive"] } +snafu = { version = "0.6.3" } diff --git a/packages/shade_protocol/src/asset.rs b/packages/shade_protocol/src/asset.rs new file mode 100644 index 000000000..de5ea9bc1 --- /dev/null +++ b/packages/shade_protocol/src/asset.rs @@ -0,0 +1,10 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use cosmwasm_std::HumanAddr; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct Contract { + pub address: HumanAddr, + pub code_hash: String, +} \ No newline at end of file diff --git a/packages/shade_protocol/src/generic_response.rs b/packages/shade_protocol/src/generic_response.rs new file mode 100644 index 000000000..0c7eb5bb7 --- /dev/null +++ b/packages/shade_protocol/src/generic_response.rs @@ -0,0 +1,9 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ResponseStatus { + Success, + Failure, +} \ No newline at end of file diff --git a/packages/shade_protocol/src/lib.rs b/packages/shade_protocol/src/lib.rs new file mode 100644 index 000000000..3a02c20b3 --- /dev/null +++ b/packages/shade_protocol/src/lib.rs @@ -0,0 +1,5 @@ +pub mod asset; +pub mod mint; +pub mod oracle; +pub mod generic_response; +pub mod msg_traits; \ No newline at end of file diff --git a/packages/shade_protocol/src/mint.rs b/packages/shade_protocol/src/mint.rs new file mode 100644 index 000000000..50cded17c --- /dev/null +++ b/packages/shade_protocol/src/mint.rs @@ -0,0 +1,100 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use cosmwasm_std::{HumanAddr, Uint128, Binary, CosmosMsg}; +use crate::asset::Contract; +use crate::generic_response::ResponseStatus; +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 contract: Contract, + pub burned_tokens: Uint128, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct InitMsg { + pub admin: Option, + pub silk: Contract, + pub oracle: Contract, + pub initial_assets: Option>, +} + +impl Init<'_> for InitMsg {} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum HandleMsg { + Migrate { + label: String, + code_id: u64, + code_hash: String, + }, + UpdateConfig { + owner: Option, + silk: Option, + oracle: Option, + }, + RegisterAsset { + contract: Contract, + }, + UpdateAsset { + asset: HumanAddr, + contract: Contract, + }, + Receive { + sender: HumanAddr, + from: HumanAddr, + amount: Uint128, + memo: Option, + msg: Option, + }, +} + +impl Handle<'_> for HandleMsg{} + +#[derive(Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum HandleAnswer { + Init { status: ResponseStatus, address: HumanAddr }, + Migrate { status: ResponseStatus }, + UpdateConfig { status: ResponseStatus}, + RegisterAsset { status: ResponseStatus}, + UpdateAsset { status: ResponseStatus}, + Burn { status: ResponseStatus, mint_amount: Uint128 } +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum QueryMsg { + GetSupportedAssets {}, + GetAsset { + contract: String, + }, + GetConfig {}, +} + +impl Query for QueryMsg {} + +#[derive(Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum QueryAnswer { + SupportedAssets { assets: Vec, }, + Asset { asset: BurnableAsset }, + 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/msg_traits.rs b/packages/shade_protocol/src/msg_traits.rs new file mode 100644 index 000000000..b453c1303 --- /dev/null +++ b/packages/shade_protocol/src/msg_traits.rs @@ -0,0 +1,86 @@ +use serde::{Deserialize, Serialize}; +use cosmwasm_std::{CosmosMsg, StdResult, to_binary, WasmMsg, HumanAddr, Uint128, Coin, Querier, QueryRequest, WasmQuery, StdError}; +use secret_toolkit::utils::space_pad; +use serde::de::DeserializeOwned; + +pub trait Init<'a>: Serialize + Deserialize<'a> + Clone + PartialEq { + fn to_cosmos_msg( + &self, + mut block_size: usize, + code_id: u64, + callback_code_hash: String, + label: String, + ) -> StdResult { + // can not have block size of 0 + if block_size == 0 { + block_size = 1; + } + let mut msg = to_binary(self)?; + space_pad(&mut msg.0, block_size); + let execute = WasmMsg::Instantiate { + code_id, + callback_code_hash, + msg, + send: vec![], + label + }; + Ok(execute.into()) + } +} + +pub trait Handle<'a>: Serialize + Deserialize<'a> + Clone + PartialEq { + fn to_cosmos_msg( + &self, + mut block_size: usize, + callback_code_hash: String, + contract_addr: HumanAddr, + send_amount: Option, + ) -> StdResult { + // can not have block size of 0 + if block_size == 0 { + block_size = 1; + } + let mut msg = to_binary(self)?; + space_pad(&mut msg.0, block_size); + let mut send = Vec::new(); + if let Some(amount) = send_amount { + send.push(Coin { + amount, + denom: String::from("uscrt"), + }); + } + let execute = WasmMsg::Execute { + msg, + contract_addr, + callback_code_hash, + send, + }; + Ok(execute.into()) + } +} + +pub trait Query: Serialize + Clone { + fn query( + &self, + querier: &Q, + mut block_size: usize, + callback_code_hash: String, + contract_addr: HumanAddr, + ) -> StdResult { + // can not have block size of 0 + if block_size == 0 { + block_size = 1; + } + let mut msg = to_binary(self)?; + space_pad(&mut msg.0, block_size); + querier + .query(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr, + callback_code_hash, + msg, + })) + .map_err(|_err| { + StdError::generic_err(format!("Error performing query")) + }) + } +} diff --git a/packages/mirror_protocol/src/community.rs b/packages/shade_protocol/src/oracle.rs similarity index 51% rename from packages/mirror_protocol/src/community.rs rename to packages/shade_protocol/src/oracle.rs index 95f983de5..f8bf7b917 100644 --- a/packages/mirror_protocol/src/community.rs +++ b/packages/shade_protocol/src/oracle.rs @@ -1,44 +1,36 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; - use cosmwasm_std::{HumanAddr, Uint128}; +use crate::msg_traits::{Init, Handle, Query}; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct InitMsg { - pub owner: HumanAddr, // mirror gov contract - pub mirror_token: HumanAddr, // mirror token address - pub spend_limit: Uint128, // spend limit per each `spend` request +pub struct OracleConfig { + pub owner: HumanAddr, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum HandleMsg { - UpdateConfig { - owner: Option, - spend_limit: Option, - }, - Spend { - recipient: HumanAddr, - amount: Uint128, - }, +pub struct InitMsg { + pub admin: Option, } +impl Init<'_> for InitMsg {} -/// We currently take no arguments for migrations #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct MigrateMsg {} +#[serde(rename_all = "snake_case")] +pub enum HandleMsg { +} +impl Handle<'_> for HandleMsg{} #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum QueryMsg { - Config {}, + GetScrtPrice {} } -// We define a custom struct for each query response +impl Query for QueryMsg {} + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct ConfigResponse { - pub owner: HumanAddr, - pub mirror_token: HumanAddr, - pub spend_limit: Uint128, -} +pub struct PriceResponse { + pub price: Uint128, +} \ No newline at end of file diff --git a/shade-contracts/README.md b/shade-contracts/README.md deleted file mode 100644 index 6b6b709ab..000000000 --- a/shade-contracts/README.md +++ /dev/null @@ -1 +0,0 @@ -This is where shade-contracts will live.