diff --git a/Cargo.lock b/Cargo.lock index 50ca7d4..5bd3d37 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,81 +3,118 @@ version = 3 [[package]] -name = "addr2line" -version = "0.17.0" +name = "anyhow" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" + +[[package]] +name = "astroport" +version = "2.0.0" +source = "git+https://github.com/astroport-fi/astroport-core.git#fc3198846edfdb454d2aa4552661530a8316ec14" dependencies = [ - "gimli 0.26.1", + "cosmwasm-std", + "cw-storage-plus 0.13.4", + "cw20 0.13.4", + "schemars", + "serde", + "uint", ] [[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +name = "astroport-dca" +version = "2.0.0" +dependencies = [ + "astroport", + "cosmwasm-std", + "cw20 0.14.0", + "schemars", + "serde", +] [[package]] -name = "ahash" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +name = "astroport-dca-module" +version = "2.0.0" dependencies = [ - "getrandom 0.2.5", - "once_cell", - "version_check", + "astroport", + "astroport-dca", + "astroport-factory", + "astroport-pair", + "astroport-pair-stable", + "astroport-router", + "cosmwasm-schema", + "cosmwasm-std", + "cw-multi-test", + "cw-storage-plus 0.14.0", + "cw1-whitelist", + "cw2 0.14.0", + "cw20 0.14.0", + "cw20-base", + "schemars", + "serde", + "thiserror", ] [[package]] -name = "aho-corasick" -version = "0.7.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +name = "astroport-factory" +version = "1.2.0" +source = "git+https://github.com/astroport-fi/astroport-core.git#fc3198846edfdb454d2aa4552661530a8316ec14" dependencies = [ - "memchr", + "astroport", + "cosmwasm-std", + "cw-storage-plus 0.13.4", + "cw2 0.13.4", + "protobuf", + "schemars", + "serde", + "thiserror", ] [[package]] -name = "astroport" -version = "1.2.0" -source = "git+https://github.com/astroport-fi/astroport-core.git?branch=main#d852f218cb4d119ea3ae68c386e31e6a3bca1b34" +name = "astroport-pair" +version = "1.0.0" +source = "git+https://github.com/astroport-fi/astroport-core.git#fc3198846edfdb454d2aa4552661530a8316ec14" dependencies = [ + "astroport", "cosmwasm-std", - "cw-storage-plus", - "cw20", + "cw-storage-plus 0.13.4", + "cw2 0.13.4", + "cw20 0.13.4", + "integer-sqrt", + "protobuf", "schemars", "serde", - "terra-cosmwasm", - "uint", + "thiserror", ] [[package]] -name = "astroport-dca" +name = "astroport-pair-stable" version = "1.0.0" +source = "git+https://github.com/astroport-fi/astroport-core.git#fc3198846edfdb454d2aa4552661530a8316ec14" dependencies = [ "astroport", - "cosmwasm-schema", "cosmwasm-std", - "cw20", - "regex", + "cw-storage-plus 0.13.4", + "cw2 0.13.4", + "cw20 0.13.4", + "integer-sqrt", + "protobuf", "schemars", "serde", - "terra-cosmwasm", - "uint", + "thiserror", ] [[package]] -name = "astroport-dca-module" +name = "astroport-router" version = "1.0.0" +source = "git+https://github.com/astroport-fi/astroport-core.git#fc3198846edfdb454d2aa4552661530a8316ec14" dependencies = [ "astroport", - "astroport-dca", - "cosmwasm-schema", "cosmwasm-std", - "cosmwasm-vm", - "cw-storage-plus", - "cw2", - "cw20", + "cw-storage-plus 0.13.4", + "cw2 0.13.4", + "cw20 0.13.4", + "integer-sqrt", "schemars", "serde", "thiserror", @@ -90,19 +127,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] -name = "backtrace" -version = "0.3.64" +name = "base16ct" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e121dee8023ce33ab248d9ce1493df03c3b38a659b240096fcbd7048ff9c31f" -dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" [[package]] name = "base64" @@ -111,10 +139,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] -name = "bitflags" -version = "1.3.2" +name = "base64ct" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "3bdca834647821e0b13d9539a8634eb62d3501b6b6c2cec1722786ee6671b851" [[package]] name = "block-buffer" @@ -125,33 +153,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "bumpalo" -version = "3.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" - -[[package]] -name = "bytecheck" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "314889ea31cda264cb7c3d6e6e5c9415a987ecb0e72c17c00d36fbb881d34abe" -dependencies = [ - "bytecheck_derive", - "ptr_meta", -] - -[[package]] -name = "bytecheck_derive" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a2b3b92c135dae665a6f760205b89187638e83bed17ef3e44e83c712cf30600" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "byteorder" version = "1.4.3" @@ -159,10 +160,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] -name = "cc" -version = "1.0.73" +name = "bytes" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +checksum = "f0b3de4a0c5e67e16066a0715723abd91edc2f9001d09c46e1dca929351e130e" [[package]] name = "cfg-if" @@ -170,45 +171,39 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "clru" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "591ff76ca0691bd91c1b0b5b987e5cf93b21ec810ad96665c5a569c60846dd93" - [[package]] name = "const-oid" -version = "0.6.2" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d6f2aa4d0537bcc1c74df8755072bd31c1ef1a3a1b85a68e8404a8c353b7b8b" +checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" [[package]] name = "cosmwasm-crypto" -version = "0.16.4" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfee62162cc81a332ec61e9131d3515a557adadf512bfa607011b2d80e1c5494" +checksum = "5eb0afef2325df81aadbf9be1233f522ed8f6e91df870c764bc44cca2b1415bd" dependencies = [ "digest", "ed25519-zebra", "k256", - "rand_core 0.5.1", + "rand_core 0.6.3", "thiserror", ] [[package]] name = "cosmwasm-derive" -version = "0.16.4" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85e230bdfbef8e0339cceaffed92589a41b6d82ff53c0430dfa4fc8ec47fe4e1" +checksum = "4b36e527620a2a3e00e46b6e731ab6c9b68d11069c986f7d7be8eba79ef081a4" dependencies = [ "syn", ] [[package]] name = "cosmwasm-schema" -version = "0.16.4" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bddd1d15bc717dd58ff3f7a716f53c83eddc677084f395df3d9b8310583a36f1" +checksum = "772e80bbad231a47a2068812b723a1ff81dd4a0d56c9391ac748177bea3a61da" dependencies = [ "schemars", "serde_json", @@ -216,13 +211,14 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "0.16.4" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf76c428d7ca29ad72d78b7b93f4658d88b2ae91d850bc0060b434680fbbf7c0" +checksum = "875994993c2082a6fcd406937bf0fca21c349e4a624f3810253a14fa83a3a195" dependencies = [ "base64", "cosmwasm-crypto", "cosmwasm-derive", + "forward_ref", "schemars", "serde", "serde-json-wasm", @@ -231,24 +227,13 @@ dependencies = [ ] [[package]] -name = "cosmwasm-vm" -version = "0.16.4" +name = "cosmwasm-storage" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a02d0b8402ef3c7eda01fe710b6c2080f154944ced18f6393ca36e15a2635efe" +checksum = "d18403b07304d15d304dad11040d45bbcaf78d603b4be3fb5e2685c16f9229b5" dependencies = [ - "clru", - "cosmwasm-crypto", "cosmwasm-std", - "hex", - "loupe", - "parity-wasm", - "schemars", "serde", - "serde_json", - "sha2", - "thiserror", - "wasmer", - "wasmer-middlewares", ] [[package]] @@ -261,250 +246,225 @@ dependencies = [ ] [[package]] -name = "cranelift-bforest" -version = "0.76.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e6bea67967505247f54fa2c85cf4f6e0e31c4e5692c9b70e4ae58e339067333" -dependencies = [ - "cranelift-entity", -] - -[[package]] -name = "cranelift-codegen" -version = "0.76.0" +name = "crunchy" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48194035d2752bdd5bdae429e3ab88676e95f52a2b1355a5d4e809f9e39b1d74" -dependencies = [ - "cranelift-bforest", - "cranelift-codegen-meta", - "cranelift-codegen-shared", - "cranelift-entity", - "gimli 0.25.0", - "log", - "regalloc", - "smallvec", - "target-lexicon", -] +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] -name = "cranelift-codegen-meta" -version = "0.76.0" +name = "crypto-bigint" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976efb22fcab4f2cd6bd4e9913764616a54d895c1a23530128d04e03633c555f" +checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" dependencies = [ - "cranelift-codegen-shared", - "cranelift-entity", + "generic-array", + "rand_core 0.6.3", + "subtle", + "zeroize", ] [[package]] -name = "cranelift-codegen-shared" -version = "0.76.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dabb5fe66e04d4652e434195b45ae65b5c8172d520247b8f66d8df42b2b45dc" - -[[package]] -name = "cranelift-entity" -version = "0.76.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3329733e4d4b8e91c809efcaa4faee80bf66f20164e3dd16d707346bd3494799" - -[[package]] -name = "cranelift-frontend" -version = "0.76.0" +name = "crypto-mac" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "279afcc0d3e651b773f94837c3d581177b348c8d69e928104b2e9fccb226f921" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" dependencies = [ - "cranelift-codegen", - "log", - "smallvec", - "target-lexicon", + "generic-array", + "subtle", ] [[package]] -name = "crc32fast" -version = "1.3.2" +name = "curve25519-dalek" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" dependencies = [ - "cfg-if", + "byteorder", + "digest", + "rand_core 0.5.1", + "subtle", + "zeroize", ] [[package]] -name = "crossbeam-channel" -version = "0.5.2" +name = "cw-multi-test" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa" +checksum = "ca153120cf5b91af88be106b0c6c0263423d959bc813b1592982c02c4691a4ae" dependencies = [ - "cfg-if", - "crossbeam-utils", + "anyhow", + "cosmwasm-std", + "cosmwasm-storage", + "cw-storage-plus 0.14.0", + "cw-utils 0.14.0", + "derivative", + "itertools", + "prost", + "schemars", + "serde", + "thiserror", ] [[package]] -name = "crossbeam-deque" -version = "0.8.1" +name = "cw-storage-plus" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +checksum = "648b1507290bbc03a8d88463d7cd9b04b1fa0155e5eef366c4fa052b9caaac7a" dependencies = [ - "cfg-if", - "crossbeam-epoch", - "crossbeam-utils", + "cosmwasm-std", + "schemars", + "serde", ] [[package]] -name = "crossbeam-epoch" -version = "0.9.7" +name = "cw-storage-plus" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c00d6d2ea26e8b151d99093005cb442fb9a37aeaca582a03ec70946f49ab5ed9" +checksum = "1c8b264257c4f44c49b7ce09377af63aa040768ecd3fd7bdd2d48a09323a1e90" dependencies = [ - "cfg-if", - "crossbeam-utils", - "lazy_static", - "memoffset", - "scopeguard", + "cosmwasm-std", + "schemars", + "serde", ] [[package]] -name = "crossbeam-utils" -version = "0.8.7" +name = "cw-utils" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6" +checksum = "9dbaecb78c8e8abfd6b4258c7f4fbeb5c49a5e45ee4d910d3240ee8e1d714e1b" dependencies = [ - "cfg-if", - "lazy_static", + "cosmwasm-std", + "schemars", + "serde", + "thiserror", ] [[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - -[[package]] -name = "crypto-bigint" -version = "0.2.11" +name = "cw-utils" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83bd3bb4314701c568e340cd8cf78c975aa0ca79e03d3f6d1677d5b0c9c0c03" +checksum = "414b91f3d7a619bb26c835119d7095804596a1382ddc1d184c33c1d2c17f6c5e" dependencies = [ - "generic-array", - "rand_core 0.6.3", - "subtle", - "zeroize", + "cosmwasm-std", + "cw2 0.14.0", + "schemars", + "semver", + "serde", + "thiserror", ] [[package]] -name = "crypto-mac" -version = "0.11.1" +name = "cw1" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +checksum = "c69ee5119e5cea350b266b43c64a76ac96fc2047d93f44701ecd44907f70a3a3" dependencies = [ - "generic-array", - "subtle", + "cosmwasm-std", + "schemars", + "serde", ] [[package]] -name = "curve25519-dalek" -version = "3.2.0" +name = "cw1-whitelist" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +checksum = "42e133842a1c15e3f27c81bb3e9a4bb3b2cf8d0c80b2d00e19d82aa55b5d037e" dependencies = [ - "byteorder", - "digest", - "rand_core 0.5.1", - "subtle", - "zeroize", + "cosmwasm-std", + "cw-storage-plus 0.14.0", + "cw-utils 0.14.0", + "cw1", + "cw2 0.14.0", + "schemars", + "serde", + "thiserror", ] [[package]] -name = "cw-storage-plus" -version = "0.8.1" +name = "cw2" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1e867b9972b83b32e00e878dfbff48299ba26618dabeb19b9c56fae176dc225" +checksum = "04cf4639517490dd36b333bbd6c4fbd92e325fd0acf4683b41753bc5eb63bfc1" dependencies = [ "cosmwasm-std", + "cw-storage-plus 0.13.4", "schemars", "serde", ] [[package]] -name = "cw0" -version = "0.8.1" +name = "cw2" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c497f885a40918a02df7d938c81809965fa05cfc21b3dc591e9950237b5de0a9" +checksum = "aa74c324af8e3506fd8d50759a265bead3f87402e413c840042af5d2808463d6" dependencies = [ "cosmwasm-std", + "cw-storage-plus 0.14.0", "schemars", "serde", - "thiserror", ] [[package]] -name = "cw2" -version = "0.8.1" +name = "cw20" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d48454f96494aa1018556cd457977375cc8c57ef3e5c767cfa2ea5ec24b0258" +checksum = "4cb782b8f110819a4eb5dbbcfed25ffba49ec16bbe32b4ad8da50a5ce68fec05" dependencies = [ "cosmwasm-std", - "cw-storage-plus", + "cw-utils 0.13.4", "schemars", "serde", ] [[package]] name = "cw20" -version = "0.8.1" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a11a2adbd52258f5b4ed5323f62bc6e559f2cefbe52ef0e58290016fde5bb083" +checksum = "4f446f59c519fbac5ab8b9f6c7f8dcaa05ee761703971406b28221ea778bb737" dependencies = [ "cosmwasm-std", - "cw0", + "cw-utils 0.14.0", "schemars", "serde", ] [[package]] -name = "darling" -version = "0.13.1" +name = "cw20-base" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0d720b8683f8dd83c65155f0530560cba68cd2bf395f6513a483caee57ff7f4" +checksum = "e39bf97c985a50f2e340833137b3f14999f58708c4ca9928cd8f87d530c4109c" dependencies = [ - "darling_core", - "darling_macro", + "cosmwasm-std", + "cw-storage-plus 0.14.0", + "cw-utils 0.14.0", + "cw2 0.14.0", + "cw20 0.14.0", + "schemars", + "semver", + "serde", + "thiserror", ] [[package]] -name = "darling_core" -version = "0.13.1" +name = "der" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a340f241d2ceed1deb47ae36c4144b2707ec7dd0b649f894cb39bb595986324" +checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn", + "const-oid", ] [[package]] -name = "darling_macro" -version = "0.13.1" +name = "derivative" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c41b3b7352feb3211a0d743dc5700a4e3b60f51bd2b368892d1e0f9a95f44b" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ - "darling_core", + "proc-macro2", "quote", "syn", ] -[[package]] -name = "der" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79b71cca7d95d7681a4b3b9cdf63c8dbc3730d0584c2c74e31416d64a90493f4" -dependencies = [ - "const-oid", -] - [[package]] name = "digest" version = "0.9.0" @@ -520,131 +480,72 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf" -[[package]] -name = "dynasm" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b1801e630bd336d0bbbdbf814de6cc749c9a400c7e3d995e6adfd455d0c83c" -dependencies = [ - "bitflags", - "byteorder", - "lazy_static", - "proc-macro-error", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "dynasmrt" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d428afc93ad288f6dffc1fa5f4a78201ad2eec33c5a522e51c181009eb09061" -dependencies = [ - "byteorder", - "dynasm", - "memmap2", -] - [[package]] name = "ecdsa" -version = "0.12.4" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43ee23aa5b4f68c7a092b5c3beb25f50c406adc75e2363634f242f28ab255372" +checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" dependencies = [ "der", "elliptic-curve", - "hmac", + "rfc6979", "signature", ] [[package]] name = "ed25519-zebra" -version = "2.2.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a128b76af6dd4b427e34a6fd43dc78dbfe73672ec41ff615a2414c1a0ad0409" +checksum = "403ef3e961ab98f0ba902771d29f842058578bb1ce7e3c59dad5a6a93e784c69" dependencies = [ "curve25519-dalek", "hex", - "rand_core 0.5.1", + "rand_core 0.6.3", "serde", "sha2", "thiserror", + "zeroize", ] [[package]] name = "either" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" [[package]] name = "elliptic-curve" -version = "0.10.6" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beca177dcb8eb540133e7680baff45e7cc4d93bf22002676cec549f82343721b" +checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6" dependencies = [ + "base16ct", "crypto-bigint", + "der", "ff", "generic-array", "group", - "pkcs8", "rand_core 0.6.3", + "sec1", "subtle", "zeroize", ] -[[package]] -name = "enumset" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6216d2c19a6fb5f29d1ada1dc7bc4367a8cbf0fa4af5cf12e07b5bbdde6b5b2c" -dependencies = [ - "enumset_derive", -] - -[[package]] -name = "enumset_derive" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6451128aa6655d880755345d085494cf7561a6bee7c8dc821e5d77e6d267ecd4" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "fallible-iterator" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" - -[[package]] -name = "fastrand" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" -dependencies = [ - "instant", -] - [[package]] name = "ff" -version = "0.10.1" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0f40b2dcd8bc322217a5f6559ae5f9e9d1de202a2ecee2e9eafcbece7562a4f" +checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" dependencies = [ "rand_core 0.6.3", "subtle", ] [[package]] -name = "fnv" -version = "1.0.7" +name = "forward_ref" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e" [[package]] name = "generic-array" @@ -679,55 +580,14 @@ dependencies = [ ] [[package]] -name = "gimli" -version = "0.25.0" +name = "group" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7" +checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" dependencies = [ - "fallible-iterator", - "indexmap", - "stable_deref_trait", -] - -[[package]] -name = "gimli" -version = "0.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" - -[[package]] -name = "group" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c363a5301b8f153d80747126a04b3c82073b9fe3130571a9d170cacdeaf7912" -dependencies = [ - "ff", - "rand_core 0.6.3", - "subtle", -] - -[[package]] -name = "hashbrown" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" - -[[package]] -name = "hashbrown" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c21d40587b92fa6a6c6e3c1bdbf87d75511db5672f9c93175574b3a00df1758" -dependencies = [ - "ahash", -] - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", + "ff", + "rand_core 0.6.3", + "subtle", ] [[package]] @@ -747,29 +607,21 @@ dependencies = [ ] [[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "indexmap" -version = "1.8.0" +name = "integer-sqrt" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" +checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770" dependencies = [ - "autocfg", - "hashbrown 0.11.2", - "serde", + "num-traits", ] [[package]] -name = "instant" -version = "0.1.12" +name = "itertools" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" dependencies = [ - "cfg-if", + "either", ] [[package]] @@ -778,39 +630,19 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" -[[package]] -name = "js-sys" -version = "0.3.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" -dependencies = [ - "wasm-bindgen", -] - [[package]] name = "k256" -version = "0.9.6" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "903ae2481bcdfdb7b68e0a9baa4b7c9aff600b9ae2e8e5bb5833b8c91ab851ea" +checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d" dependencies = [ "cfg-if", "ecdsa", "elliptic-curve", + "sec1", "sha2", ] -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "leb128" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" - [[package]] name = "libc" version = "0.2.119" @@ -818,171 +650,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" [[package]] -name = "libloading" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" -dependencies = [ - "cfg-if", - "winapi", -] - -[[package]] -name = "log" -version = "0.4.14" +name = "num-traits" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "loupe" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6a72dfa44fe15b5e76b94307eeb2ff995a8c5b283b55008940c02e0c5b634d" -dependencies = [ - "indexmap", - "loupe-derive", - "rustversion", -] - -[[package]] -name = "loupe-derive" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fbfc88337168279f2e9ae06e157cfed4efd3316e14dc96ed074d4f2e6c5952" -dependencies = [ - "quote", - "syn", -] - -[[package]] -name = "mach" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" -dependencies = [ - "libc", -] - -[[package]] -name = "memchr" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" - -[[package]] -name = "memmap2" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057a3db23999c867821a7a59feb06a578fcb03685e983dff90daf9e7d24ac08f" -dependencies = [ - "libc", -] - -[[package]] -name = "memoffset" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", ] -[[package]] -name = "miniz_oxide" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" -dependencies = [ - "adler", - "autocfg", -] - -[[package]] -name = "more-asserts" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" - -[[package]] -name = "num_cpus" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "object" -version = "0.27.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9" -dependencies = [ - "crc32fast", - "indexmap", - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" - [[package]] name = "opaque-debug" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" -[[package]] -name = "parity-wasm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" - -[[package]] -name = "pin-project-lite" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" - [[package]] name = "pkcs8" -version = "0.7.6" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee3ef9b64d26bad0536099c816c6734379e45bbd5f14798def6809e5cc350447" +checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0" dependencies = [ "der", "spki", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", + "zeroize", ] [[package]] @@ -995,25 +685,37 @@ dependencies = [ ] [[package]] -name = "ptr_meta" -version = "0.1.4" +name = "prost" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" dependencies = [ - "ptr_meta_derive", + "bytes", + "prost-derive", ] [[package]] -name = "ptr_meta_derive" -version = "0.1.4" +name = "prost-derive" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" dependencies = [ + "anyhow", + "itertools", "proc-macro2", "quote", "syn", ] +[[package]] +name = "protobuf" +version = "2.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf7e6d18738ecd0902d30d1ad232c9125985a3422929b16c65517b38adc14f96" +dependencies = [ + "bytes", +] + [[package]] name = "quote" version = "1.0.15" @@ -1042,140 +744,16 @@ dependencies = [ ] [[package]] -name = "rayon" -version = "1.5.1" +name = "rfc6979" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" +checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" dependencies = [ - "autocfg", - "crossbeam-deque", - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" -dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-utils", - "lazy_static", - "num_cpus", -] - -[[package]] -name = "redox_syscall" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" -dependencies = [ - "bitflags", -] - -[[package]] -name = "regalloc" -version = "0.0.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "571f7f397d61c4755285cd37853fe8e03271c243424a907415909379659381c5" -dependencies = [ - "log", - "rustc-hash", - "smallvec", -] - -[[package]] -name = "regex" -version = "1.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.6.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" - -[[package]] -name = "region" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76e189c2369884dce920945e2ddf79b3dff49e071a167dd1817fa9c4c00d512e" -dependencies = [ - "bitflags", - "libc", - "mach", - "winapi", -] - -[[package]] -name = "remove_dir_all" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] - -[[package]] -name = "rend" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79af64b4b6362ffba04eef3a4e10829718a4896dac19daa741851c86781edf95" -dependencies = [ - "bytecheck", -] - -[[package]] -name = "rkyv" -version = "0.7.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf98e3e6c7ed44e474b454b1ebded3193ee5aba3428e29c55d59b1d65e49945e" -dependencies = [ - "bytecheck", - "hashbrown 0.12.0", - "ptr_meta", - "rend", - "rkyv_derive", - "seahash", -] - -[[package]] -name = "rkyv_derive" -version = "0.7.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc9940ec6a7c62b1d1f476f607c6caf0d7fbf74e43f77bc022143b878fcd3266" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "crypto-bigint", + "hmac", + "zeroize", ] -[[package]] -name = "rustc-demangle" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" - -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustversion" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" - [[package]] name = "ryu" version = "1.0.9" @@ -1207,16 +785,23 @@ dependencies = [ ] [[package]] -name = "scopeguard" -version = "1.1.0" +name = "sec1" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" +dependencies = [ + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] [[package]] -name = "seahash" -version = "4.1.0" +name = "semver" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" +checksum = "a2333e6df6d6598f2b1974829f853c2b4c5f4a6e503c10af918081aa6f8564e1" [[package]] name = "serde" @@ -1229,18 +814,9 @@ dependencies = [ [[package]] name = "serde-json-wasm" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "042ac496d97e5885149d34139bad1d617192770d7eb8f1866da2317ff4501853" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_bytes" -version = "0.11.5" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9" +checksum = "479b4dbc401ca13ee8ce902851b834893251404c4f3c65370a49e047a6be09a5" dependencies = [ "serde", ] @@ -1301,39 +877,22 @@ dependencies = [ "rand_core 0.6.3", ] -[[package]] -name = "smallvec" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" - [[package]] name = "spki" -version = "0.4.1" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c01a0c15da1b0b0e1494112e7af814a678fec9bd157881b49beac661e9b6f32" +checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27" dependencies = [ + "base64ct", "der", ] -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - [[package]] name = "subtle" version = "2.4.1" @@ -1351,37 +910,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "target-lexicon" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fa7e55043acb85fca6b3c01485a2eeb6b69c5d21002e273c79e465f43b7ac1" - -[[package]] -name = "tempfile" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" -dependencies = [ - "cfg-if", - "fastrand", - "libc", - "redox_syscall", - "remove_dir_all", - "winapi", -] - -[[package]] -name = "terra-cosmwasm" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "552f18cba2b535d1f8c0e3b3f37696820b954bc7535d2e33909f2a6342302718" -dependencies = [ - "cosmwasm-std", - "schemars", - "serde", -] - [[package]] name = "thiserror" version = "1.0.30" @@ -1402,39 +930,6 @@ dependencies = [ "syn", ] -[[package]] -name = "tracing" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6c650a8ef0cd2dd93736f033d21cbd1224c5a967aa0c258d00fcf7dafef9b9f" -dependencies = [ - "cfg-if", - "log", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8276d9a4a3a558d7b7ad5303ad50b53d58264641b82914b7ada36bd762e7a716" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23" -dependencies = [ - "lazy_static", -] - [[package]] name = "typenum" version = "1.15.0" @@ -1443,9 +938,9 @@ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "uint" -version = "0.9.1" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6470ab50f482bde894a037a57064480a246dbfdd5960bd65a44824693f08da5f" +checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" dependencies = [ "byteorder", "crunchy", @@ -1477,319 +972,6 @@ version = "0.10.2+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" -[[package]] -name = "wasm-bindgen" -version = "0.2.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" -dependencies = [ - "bumpalo", - "lazy_static", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" - -[[package]] -name = "wasmer" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23f0188c23fc1b7de9bd7f8b834d0b1cd5edbe66e287452e8ce36d24418114f7" -dependencies = [ - "cfg-if", - "indexmap", - "js-sys", - "loupe", - "more-asserts", - "target-lexicon", - "thiserror", - "wasm-bindgen", - "wasmer-compiler", - "wasmer-compiler-cranelift", - "wasmer-compiler-singlepass", - "wasmer-derive", - "wasmer-engine", - "wasmer-engine-dylib", - "wasmer-engine-universal", - "wasmer-types", - "wasmer-vm", - "winapi", -] - -[[package]] -name = "wasmer-compiler" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88c51cc589772c5f90bd329244c2416976d6cb2ee00d59429aaa8f421d9fe447" -dependencies = [ - "enumset", - "loupe", - "rkyv", - "serde", - "serde_bytes", - "smallvec", - "target-lexicon", - "thiserror", - "wasmer-types", - "wasmer-vm", - "wasmparser", -] - -[[package]] -name = "wasmer-compiler-cranelift" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09691e3e323b4e1128d2127f60f9cd988b66ce49afc8184b071c2b5ab16793f2" -dependencies = [ - "cranelift-codegen", - "cranelift-entity", - "cranelift-frontend", - "gimli 0.25.0", - "loupe", - "more-asserts", - "rayon", - "smallvec", - "target-lexicon", - "tracing", - "wasmer-compiler", - "wasmer-types", - "wasmer-vm", -] - -[[package]] -name = "wasmer-compiler-singlepass" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271d3da24c5d1a8bb3f9fc3944ba96d2588b6fa16a0bcef91765db853aeccac4" -dependencies = [ - "byteorder", - "dynasm", - "dynasmrt", - "lazy_static", - "loupe", - "more-asserts", - "rayon", - "smallvec", - "wasmer-compiler", - "wasmer-types", - "wasmer-vm", -] - -[[package]] -name = "wasmer-derive" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f5cb7b09640e09f1215da95d6fb7477d2db572f064b803ff705f39ff079cc5" -dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "wasmer-engine" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab20311c354fe2c12bc766417e0a1a45f399c1cd8ff262127d1dc86d0588971a" -dependencies = [ - "backtrace", - "enumset", - "lazy_static", - "loupe", - "memmap2", - "more-asserts", - "rustc-demangle", - "serde", - "serde_bytes", - "target-lexicon", - "thiserror", - "wasmer-compiler", - "wasmer-types", - "wasmer-vm", -] - -[[package]] -name = "wasmer-engine-dylib" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dd5b7a74731e1dcccaf10a8ff5f72216c82f12972ce17cc81c6caa1afff75ea" -dependencies = [ - "cfg-if", - "enumset", - "leb128", - "libloading", - "loupe", - "rkyv", - "serde", - "tempfile", - "tracing", - "wasmer-compiler", - "wasmer-engine", - "wasmer-object", - "wasmer-types", - "wasmer-vm", - "which", -] - -[[package]] -name = "wasmer-engine-universal" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfeae8d5b825ad7abcf9a34e66eb11e1507b21020efe7bbf9897e3dd8d7869e2" -dependencies = [ - "cfg-if", - "enumset", - "leb128", - "loupe", - "region", - "rkyv", - "wasmer-compiler", - "wasmer-engine", - "wasmer-types", - "wasmer-vm", - "winapi", -] - -[[package]] -name = "wasmer-middlewares" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61a73bda8608a4ca56142b7849ccf4847cda566267d0071664ca06c6f4fbff1" -dependencies = [ - "loupe", - "wasmer", - "wasmer-types", - "wasmer-vm", -] - -[[package]] -name = "wasmer-object" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d4714e4f3bdc3b2157c24284417d19cd99de036da31d00ec5664712dcb72f7" -dependencies = [ - "object", - "thiserror", - "wasmer-compiler", - "wasmer-types", -] - -[[package]] -name = "wasmer-types" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "434e1c0177da0a74ecca90b2aa7d5e86198260f07e8ba83be89feb5f0a4aeead" -dependencies = [ - "indexmap", - "loupe", - "rkyv", - "serde", - "thiserror", -] - -[[package]] -name = "wasmer-vm" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc8f964ebba70d9f81340228b98a164782591f00239fc7f01e1b67afcf0e0156" -dependencies = [ - "backtrace", - "cc", - "cfg-if", - "indexmap", - "libc", - "loupe", - "memoffset", - "more-asserts", - "region", - "rkyv", - "serde", - "thiserror", - "wasmer-types", - "winapi", -] - -[[package]] -name = "wasmparser" -version = "0.78.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52144d4c78e5cf8b055ceab8e5fa22814ce4315d6002ad32cfd914f37c12fd65" - -[[package]] -name = "which" -version = "4.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a5a7e487e921cf220206864a94a89b6c6905bfc19f1057fa26a4cb360e5c1d2" -dependencies = [ - "either", - "lazy_static", - "libc", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "zeroize" version = "1.4.3" diff --git a/contracts/dca/Cargo.toml b/contracts/dca/Cargo.toml index 776dfe6..b77c171 100644 --- a/contracts/dca/Cargo.toml +++ b/contracts/dca/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "astroport-dca-module" -version = "1.0.0" -authors = ["Astroport", "Kaimen Sano"] +version = "2.0.0" +authors = ["Astroport", "Kaimen Sano", "yoisha <48324733+y-pakorn@users.noreply.github.com>"] edition = "2021" description = "The Astroport DCA module contract implementation" license = "MIT" @@ -20,19 +20,28 @@ crate-type = ["cdylib", "rlib"] [features] # for quicker tests, cargo test --lib # for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces", "cosmwasm-vm/backtraces"] +backtraces = ["cosmwasm-std/backtraces"] [dependencies] -astroport = { git = "https://github.com/astroport-fi/astroport-core.git", branch = "main" } -astroport-dca = { path = "../../packages/astroport-dca", version = "1.0.0" } -cw2 = { version = "0.8" } -cw20 = { version = "0.8" } -cosmwasm-std = { version = "0.16.2" } -cw-storage-plus = {version = "0.8.0"} +astroport = { version = "2.0.0", git = "https://github.com/astroport-fi/astroport-core.git" } +astroport-dca = { version = "2.0.0", path = "../../packages/astroport-dca" } + +cw-storage-plus = "0.14.0" +cw2 = "0.14.0" +cw20 = "0.14.0" +cosmwasm-std = "1.0.0" + schemars = "0.8.1" serde = { version = "1.0.103", default-features = false, features = ["derive"] } thiserror = { version = "1.0.20" } [dev-dependencies] -cosmwasm-vm = { version = "0.16.2", default-features = false, features = ["iterator"] } -cosmwasm-schema = "0.16.2" +cosmwasm-schema = "1.0.0" +cw-multi-test = "0.14.0" +cw20-base = "0.14.0" +cw1-whitelist = "0.14.0" + +astroport-router = { version = "1.0.0", git = "https://github.com/astroport-fi/astroport-core.git" } +astroport-factory = { version = "1.2.0", git = "https://github.com/astroport-fi/astroport-core.git" } +astroport-pair = { version = "1.0.0", git = "https://github.com/astroport-fi/astroport-core.git" } +astroport-pair-stable = { version = "1.0.0", git = "https://github.com/astroport-fi/astroport-core.git" } diff --git a/contracts/dca/examples/dca_schema.rs b/contracts/dca/examples/dca_schema.rs index 17f0938..2532427 100644 --- a/contracts/dca/examples/dca_schema.rs +++ b/contracts/dca/examples/dca_schema.rs @@ -3,7 +3,7 @@ use std::fs::create_dir_all; use cosmwasm_schema::{export_schema_with_title, remove_schemas, schema_for}; -use astroport_dca::dca::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; +use astroport_dca::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; fn main() { let mut out_dir = current_dir().unwrap(); diff --git a/contracts/dca/src/contract.rs b/contracts/dca/src/contract.rs index e6f2320..f82ee6d 100644 --- a/contracts/dca/src/contract.rs +++ b/contracts/dca/src/contract.rs @@ -1,21 +1,23 @@ use std::str::FromStr; -use crate::error::ContractError; -use crate::handlers::{ - add_bot_tip, cancel_dca_order, create_dca_order, modify_dca_order, perform_dca_purchase, - update_config, update_user_config, withdraw, ModifyDcaOrderParameters, -}; -use crate::queries::{get_config, get_user_config, get_user_dca_orders}; -use crate::state::{Config, CONFIG}; - -use astroport::asset::addr_validate_to_lower; +use astroport::asset::{Asset, AssetInfo}; +use astroport_dca::{Cw20HookMsg, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; use cosmwasm_std::{ - entry_point, to_binary, Binary, Decimal, Deps, DepsMut, Env, MessageInfo, Response, StdResult, + entry_point, from_binary, to_binary, Binary, Decimal, Deps, DepsMut, Env, MessageInfo, + Response, StdResult, Uint128, }; - -use astroport_dca::dca::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; use cw2::set_contract_version; +use crate::{ + error::ContractError, + handlers::{ + add_bot_tip, cancel_dca_order, create_dca_order, modify_dca_order, perform_dca_purchase, + update_config, update_user_config, withdraw, + }, + queries::{get_all_dca_orders, get_config, get_tips, get_user_config, get_user_dca_orders}, + state::{Config, CONFIG, DCA_ID, TIPS}, +}; + /// Contract name that is used for migration. const CONTRACT_NAME: &str = "astroport-dca"; /// Contract version that is used for migration. @@ -46,14 +48,29 @@ pub fn instantiate( let max_spread = Decimal::from_str(&msg.max_spread)?; // validate that factory_addr and router_addr is an address - let factory_addr = addr_validate_to_lower(deps.api, &msg.factory_addr)?; - let router_addr = addr_validate_to_lower(deps.api, &msg.router_addr)?; + let factory_addr = deps.api.addr_validate(&msg.factory_addr)?; + let router_addr = deps.api.addr_validate(&msg.router_addr)?; set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + msg.whitelisted_tokens + .iter() + .try_for_each(|e| e.check(deps.api))?; + + msg.tips + .iter() + .try_for_each(|tip| -> Result<_, ContractError> { + (tip.amount > Uint128::zero()) + .then(|| ()) + .ok_or(ContractError::InvalidTipAmount {})?; + + tip.info.check(deps.api)?; + + Ok(()) + })?; + let config = Config { max_hops: msg.max_hops, - per_hop_fee: msg.per_hop_fee, whitelisted_tokens: msg.whitelisted_tokens, max_spread, factory_addr, @@ -61,6 +78,8 @@ pub fn instantiate( }; CONFIG.save(deps.storage, &config)?; + DCA_ID.save(deps.storage, &0)?; + TIPS.save(deps.storage, &msg.tips)?; Ok(Response::new()) } @@ -74,7 +93,9 @@ pub fn instantiate( /// /// * `_msg` - The [`MigrateMsg`] to migrate the contract. #[cfg_attr(not(feature = "library"), entry_point)] -pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult { +pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + Ok(Response::default()) } @@ -134,19 +155,30 @@ pub fn execute( msg: ExecuteMsg, ) -> Result { match msg { + ExecuteMsg::Receive(receive_msg) => { + let sender = deps.api.addr_validate(&receive_msg.sender)?; + match from_binary(&receive_msg.msg)? { + Cw20HookMsg::AddBotTips {} => add_bot_tip( + deps, + MessageInfo { + sender, + funds: vec![], + }, + vec![Asset { + info: AssetInfo::Token { + contract_addr: info.sender, + }, + amount: receive_msg.amount, + }], + ), + } + } ExecuteMsg::UpdateConfig { max_hops, - per_hop_fee, whitelisted_tokens, max_spread, - } => update_config( - deps, - info, - max_hops, - per_hop_fee, - whitelisted_tokens, - max_spread, - ), + tips, + } => update_config(deps, info, max_hops, whitelisted_tokens, max_spread, tips), ExecuteMsg::UpdateUserConfig { max_hops, max_spread, @@ -156,6 +188,8 @@ pub fn execute( target_asset, interval, dca_amount, + start_at, + config_override, } => create_dca_order( deps, env, @@ -164,32 +198,40 @@ pub fn execute( target_asset, interval, dca_amount, + start_at, + config_override, ), - ExecuteMsg::AddBotTip {} => add_bot_tip(deps, info), - ExecuteMsg::Withdraw { tip: amount } => withdraw(deps, info, amount), - ExecuteMsg::PerformDcaPurchase { user, hops } => { - perform_dca_purchase(deps, env, info, user, hops) + ExecuteMsg::AddTips {} => add_bot_tip( + deps, + info.clone(), + info.funds + .into_iter() + .map(|e| Asset { + info: AssetInfo::NativeToken { denom: e.denom }, + amount: e.amount, + }) + .collect::>(), + ), + ExecuteMsg::WithdrawTips { tips } => withdraw(deps, info, tips), + ExecuteMsg::PerformDcaPurchase { id, hops } => { + perform_dca_purchase(deps, env, info, id, hops) } - ExecuteMsg::CancelDcaOrder { initial_asset } => cancel_dca_order(deps, info, initial_asset), + ExecuteMsg::CancelDcaOrder { id } => cancel_dca_order(deps, info, id), ExecuteMsg::ModifyDcaOrder { - old_initial_asset, - new_initial_asset, - new_target_asset, - new_interval, - new_dca_amount, - should_reset_purchase_time, + id, + interval, + dca_amount, + initial_amount, + config_override, } => modify_dca_order( deps, env, info, - ModifyDcaOrderParameters { - old_initial_asset, - new_initial_asset, - new_target_asset, - new_interval, - new_dca_amount, - should_reset_purchase_time, - }, + id, + initial_amount, + interval, + dca_amount, + config_override, ), } } @@ -216,7 +258,13 @@ pub fn execute( pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { match msg { QueryMsg::Config {} => to_binary(&get_config(deps)?), + QueryMsg::Tips {} => to_binary(&get_tips(deps)?), QueryMsg::UserConfig { user } => to_binary(&get_user_config(deps, user)?), QueryMsg::UserDcaOrders { user } => to_binary(&get_user_dca_orders(deps, env, user)?), + QueryMsg::AllDcaOrders { + start_after, + limit, + is_ascending, + } => to_binary(&get_all_dca_orders(deps, start_after, limit, is_ascending)?), } } diff --git a/contracts/dca/src/error.rs b/contracts/dca/src/error.rs index e0d4e5d..b805298 100644 --- a/contracts/dca/src/error.rs +++ b/contracts/dca/src/error.rs @@ -26,6 +26,9 @@ pub enum ContractError { #[error("Token has already been used to DCA")] AlreadyDeposited {}, + #[error("DCA amount is not equal to fund sent")] + InvalidNativeTokenDeposit {}, + #[error("DCA amount is not equal to allowance set by token")] InvalidTokenDeposit {}, @@ -50,6 +53,9 @@ pub enum ContractError { #[error("Hop route does not end up at target_asset")] TargetAssetAssertion {}, + #[error("Hop route does not start at initial_asset")] + InitialAssetAssertion {}, + #[error("Asset balance is less than DCA purchase amount")] InsufficientBalance {}, @@ -61,4 +67,19 @@ pub enum ContractError { #[error("Initial asset deposited is not divisible by the DCA amount")] IndivisibleDeposit {}, + + #[error("Native swap is not allowed")] + InvalidNativeSwap {}, + + #[error("New initial amount must be greater than old initial amount")] + InvalidNewInitialAmount {}, + + #[error("Tip amount must be greater than zero")] + InvalidTipAmount {}, + + #[error("Tip asset info must be whitelisted")] + InvalidTipAssetInfo {}, + + #[error("Tip balance is insufficient to withdraw")] + InsufficientTipWithdrawBalance {}, } diff --git a/contracts/dca/src/handlers/add_bot_tip.rs b/contracts/dca/src/handlers/add_bot_tip.rs index efac328..aa73f05 100644 --- a/contracts/dca/src/handlers/add_bot_tip.rs +++ b/contracts/dca/src/handlers/add_bot_tip.rs @@ -1,9 +1,9 @@ -use astroport::asset::UUSD_DENOM; -use cosmwasm_std::{attr, DepsMut, MessageInfo, Response, StdResult}; +use astroport::asset::Asset; +use cosmwasm_std::{attr, DepsMut, MessageInfo, Response}; use crate::{ error::ContractError, - state::{UserConfig, USER_CONFIG}, + state::{TIPS, USER_CONFIG}, }; /// ## Description @@ -15,65 +15,88 @@ use crate::{ /// * `deps` - A [`DepsMut`] that contains the dependencies. /// /// * `info` - A [`MessageInfo`] which contains a uusd tip to add to a users tip balance. -pub fn add_bot_tip(deps: DepsMut, info: MessageInfo) -> Result { - let amount = info - .funds - .iter() - .find(|coin| coin.denom == UUSD_DENOM) - .ok_or(ContractError::InvalidZeroAmount {})? - .amount; +pub fn add_bot_tip( + deps: DepsMut, + info: MessageInfo, + tips: Vec, +) -> Result { + (!tips.is_empty()) + .then(|| ()) + .ok_or(ContractError::InvalidZeroAmount {})?; + + let tips_denom = TIPS + .load(deps.storage)? + .into_iter() + .map(|e| e.info) + .collect::>(); // update user tip in contract USER_CONFIG.update( deps.storage, &info.sender, - |config| -> StdResult { + |config| -> Result<_, ContractError> { let mut config = config.unwrap_or_default(); - config.tip_balance = config.tip_balance.checked_add(amount)?; + for tip in tips { + match tips_denom.contains(&tip.info) { + true => match config.tips_balance.iter_mut().find(|e| e.info == tip.info) { + Some(balance) => { + balance.amount += tip.amount; + } + None => config.tips_balance.push(tip), + }, + false => Err(ContractError::InvalidTipAssetInfo {})?, + }; + } Ok(config) }, )?; - Ok(Response::new().add_attributes(vec![ - attr("action", "add_bot_tip"), - attr("tip_amount", amount), - ])) + Ok(Response::new().add_attributes(vec![attr("action", "add_bot_tip")])) } #[cfg(test)] mod tests { - use astroport_dca::dca::ExecuteMsg; + use astroport::asset::{Asset, AssetInfo}; + use astroport_dca::{ExecuteMsg, UserConfig}; use cosmwasm_std::{ attr, coin, testing::{mock_dependencies, mock_env, mock_info}, - Addr, Response, + Addr, Response, Uint128, }; use crate::{ contract::execute, error::ContractError, - state::{UserConfig, USER_CONFIG}, + state::{TIPS, USER_CONFIG}, }; #[test] fn does_add_bot_tip() { - let mut deps = mock_dependencies(&[]); + let mut deps = mock_dependencies(); let tip_sent = coin(10000, "uusd"); + TIPS.save( + &mut deps.storage, + &vec![Asset { + info: AssetInfo::NativeToken { + denom: "uusd".to_string(), + }, + amount: Uint128::new(100), + }], + ) + .unwrap(); + let info = mock_info("creator", &[tip_sent.clone()]); - let msg = ExecuteMsg::AddBotTip {}; + let msg = ExecuteMsg::AddTips {}; // check that we got the expected response let res = execute(deps.as_mut(), mock_env(), info, msg).unwrap(); assert_eq!( res, - Response::new().add_attributes(vec![ - attr("action", "add_bot_tip"), - attr("tip_amount", tip_sent.amount) - ]) + Response::new().add_attributes(vec![attr("action", "add_bot_tip"),]) ); // check that user tip balance was added @@ -83,7 +106,12 @@ mod tests { assert_eq!( config, UserConfig { - tip_balance: tip_sent.amount, + tips_balance: vec![Asset { + info: AssetInfo::NativeToken { + denom: "uusd".to_string() + }, + amount: tip_sent.amount + }], ..UserConfig::default() } ) @@ -91,10 +119,10 @@ mod tests { #[test] fn does_require_funds() { - let mut deps = mock_dependencies(&[]); + let mut deps = mock_dependencies(); let info = mock_info("creator", &[]); - let msg = ExecuteMsg::AddBotTip {}; + let msg = ExecuteMsg::AddTips {}; // should error with InvalidZeroAmount failure let res = execute(deps.as_mut(), mock_env(), info, msg).unwrap_err(); @@ -103,13 +131,24 @@ mod tests { #[test] fn does_require_uusd_funds() { - let mut deps = mock_dependencies(&[]); + let mut deps = mock_dependencies(); + + TIPS.save( + &mut deps.storage, + &vec![Asset { + info: AssetInfo::NativeToken { + denom: "uusd".to_string(), + }, + amount: Uint128::new(100), + }], + ) + .unwrap(); let info = mock_info("creator", &[coin(20000, "ukrw")]); - let msg = ExecuteMsg::AddBotTip {}; + let msg = ExecuteMsg::AddTips {}; // should error with InvalidZeroAmount let res = execute(deps.as_mut(), mock_env(), info, msg).unwrap_err(); - assert_eq!(res, ContractError::InvalidZeroAmount {}); + assert_eq!(res, ContractError::InvalidTipAssetInfo {}); } } diff --git a/contracts/dca/src/handlers/cancel_dca_order.rs b/contracts/dca/src/handlers/cancel_dca_order.rs index 2c55778..8f3ed6f 100644 --- a/contracts/dca/src/handlers/cancel_dca_order.rs +++ b/contracts/dca/src/handlers/cancel_dca_order.rs @@ -1,8 +1,10 @@ use astroport::asset::AssetInfo; -use astroport_dca::dca::DcaInfo; -use cosmwasm_std::{attr, BankMsg, Coin, DepsMut, MessageInfo, Response}; +use cosmwasm_std::{attr, BankMsg, Coin, DepsMut, MessageInfo, Response, Uint128}; -use crate::{error::ContractError, state::USER_DCA}; +use crate::{ + error::ContractError, + state::{DCA, DCA_OWNER}, +}; /// ## Description /// Cancels a users DCA purchase so that it will no longer be fulfilled. @@ -20,40 +22,33 @@ use crate::{error::ContractError, state::USER_DCA}; pub fn cancel_dca_order( deps: DepsMut, info: MessageInfo, - initial_asset: AssetInfo, + id: u64, ) -> Result { let mut funds = Vec::new(); + let order = DCA.load(deps.storage, id)?; - // remove order from user dca's, and add any native token funds for `initial_asset` into the `funds`. - USER_DCA.update( - deps.storage, - &info.sender, - |orders| -> Result, ContractError> { - let mut orders = orders.ok_or(ContractError::NonexistentDca {})?; - - let order_position = orders - .iter() - .position(|order| order.initial_asset.info == initial_asset) - .ok_or(ContractError::NonexistentDca {})?; - - let removed_order = &orders[order_position]; - if let AssetInfo::NativeToken { denom } = &removed_order.initial_asset.info { - funds.push(BankMsg::Send { - to_address: info.sender.to_string(), - amount: vec![Coin { - amount: removed_order.initial_asset.amount, - denom: denom.clone(), - }], - }) - } + (order.owner == info.sender) + .then(|| ()) + .ok_or(ContractError::Unauthorized {})?; - orders.remove(order_position); - - Ok(orders) - }, - )?; - - Ok(Response::new() - .add_messages(funds) - .add_attributes(vec![attr("action", "cancel_dca_order")])) + // remove order from user dca's, and add any native token funds for `initial_asset` into the `funds`. + if let AssetInfo::NativeToken { denom } = order.initial_asset.info { + if order.initial_asset.amount > Uint128::zero() { + funds.push(BankMsg::Send { + to_address: order.owner.to_string(), + amount: vec![Coin { + denom, + amount: order.initial_asset.amount, + }], + }) + } + } + + DCA.remove(deps.storage, id); + DCA_OWNER.remove(deps.storage, (&order.owner, id)); + + Ok(Response::new().add_messages(funds).add_attributes(vec![ + attr("action", "cancel_dca_order"), + attr("id", id.to_string()), + ])) } diff --git a/contracts/dca/src/handlers/create_dca_order.rs b/contracts/dca/src/handlers/create_dca_order.rs index 6cef016..ef26344 100644 --- a/contracts/dca/src/handlers/create_dca_order.rs +++ b/contracts/dca/src/handlers/create_dca_order.rs @@ -1,8 +1,12 @@ use astroport::asset::{Asset, AssetInfo}; -use astroport_dca::dca::DcaInfo; -use cosmwasm_std::{attr, DepsMut, Env, MessageInfo, Response, StdError, Uint128}; +use astroport_dca::{ConfigOverride, DcaInfo}; +use cosmwasm_std::{attr, DepsMut, Empty, Env, MessageInfo, Response, StdError, Uint128}; -use crate::{error::ContractError, get_token_allowance::get_token_allowance, state::USER_DCA}; +use crate::{ + error::ContractError, + get_token_allowance::get_token_allowance, + state::{DCA, DCA_ID, DCA_OWNER}, +}; /// ## Description /// Creates a new DCA order for a user where the `target_asset` will be purchased with `dca_amount` @@ -28,6 +32,7 @@ use crate::{error::ContractError, get_token_allowance::get_token_allowance, stat /// /// * `dca_amount` - A [`Uint128`] representing the amount of `initial_asset` to spend each DCA /// purchase. +#[allow(clippy::too_many_arguments)] pub fn create_dca_order( deps: DepsMut, env: Env, @@ -36,18 +41,13 @@ pub fn create_dca_order( target_asset: AssetInfo, interval: u64, dca_amount: Uint128, + start_at: Option, + config_override: Option, ) -> Result { - // check that user has not previously created dca strategy with this initial_asset - let mut orders = USER_DCA - .may_load(deps.storage, &info.sender)? - .unwrap_or_default(); + let id = DCA_ID.load(deps.storage)?; - if orders - .iter() - .any(|order| order.initial_asset.info == initial_asset.info) - { - return Err(ContractError::AlreadyDeposited {}); - } + initial_asset.info.check(deps.api)?; + target_asset.check(deps.api)?; // check that assets are not duplicate if initial_asset.info == target_asset { @@ -76,28 +76,41 @@ pub fn create_dca_order( AssetInfo::NativeToken { .. } => initial_asset.assert_sent_native_token_balance(&info)?, AssetInfo::Token { contract_addr } => { let allowance = get_token_allowance(&deps.as_ref(), &env, &info.sender, contract_addr)?; - if allowance != initial_asset.amount { + if allowance < initial_asset.amount { return Err(ContractError::InvalidTokenDeposit {}); } } } - // store dca order - orders.push(DcaInfo { - initial_asset: initial_asset.clone(), - target_asset: target_asset.clone(), + let now = env.block.time.seconds(); + let dca_info = DcaInfo { + id, + owner: info.sender, + initial_asset, + target_asset, interval, - last_purchase: 0, + last_purchase: match start_at { + // if start_at is in future -> calculate last_purchase to match start_at time + Some(start_at) if start_at > now => start_at - interval, + // else will default to start from now + interval + _ => now, + }, dca_amount, - }); + config_override: config_override.unwrap_or_default(), + }; - USER_DCA.save(deps.storage, &info.sender, &orders)?; + DCA_ID.save(deps.storage, &(id + 1))?; + DCA.save(deps.storage, id, &dca_info)?; + DCA_OWNER.save(deps.storage, (&dca_info.owner, id), &Empty {})?; Ok(Response::new().add_attributes(vec![ attr("action", "create_dca_order"), - attr("initial_asset", initial_asset.to_string()), - attr("target_asset", target_asset.to_string()), + attr("id", id.to_string()), + attr("initial_asset", dca_info.initial_asset.to_string()), + attr("target_asset", dca_info.target_asset.to_string()), attr("interval", interval.to_string()), attr("dca_amount", dca_amount), + attr("start_at", dca_info.last_purchase.to_string()), + attr("config_override", dca_info.config_override.to_string()), ])) } diff --git a/contracts/dca/src/handlers/mod.rs b/contracts/dca/src/handlers/mod.rs index 9033047..a539be0 100644 --- a/contracts/dca/src/handlers/mod.rs +++ b/contracts/dca/src/handlers/mod.rs @@ -10,7 +10,7 @@ mod withdraw; pub use add_bot_tip::add_bot_tip; pub use cancel_dca_order::cancel_dca_order; pub use create_dca_order::create_dca_order; -pub use modify_dca_order::{modify_dca_order, ModifyDcaOrderParameters}; +pub use modify_dca_order::modify_dca_order; pub use perform_dca_purchase::perform_dca_purchase; pub use update_config::update_config; pub use update_user_config::update_user_config; diff --git a/contracts/dca/src/handlers/modify_dca_order.rs b/contracts/dca/src/handlers/modify_dca_order.rs index 78807dc..fd39bec 100644 --- a/contracts/dca/src/handlers/modify_dca_order.rs +++ b/contracts/dca/src/handlers/modify_dca_order.rs @@ -1,23 +1,10 @@ -use astroport::asset::{Asset, AssetInfo}; -use cosmwasm_std::{attr, coins, BankMsg, DepsMut, Env, MessageInfo, Response, Uint128}; +use astroport::asset::AssetInfo; +use astroport_dca::ConfigOverride; +use cosmwasm_std::{attr, DepsMut, Env, MessageInfo, Response, StdError, Uint128}; -use crate::{error::ContractError, get_token_allowance::get_token_allowance, state::USER_DCA}; - -/// Stores a modified dca order new parameters -pub struct ModifyDcaOrderParameters { - /// The old [`AssetInfo`] that was used to purchase DCA orders. - pub old_initial_asset: AssetInfo, - /// The new [`Asset`] that is being spent to create DCA orders. - pub new_initial_asset: Asset, - /// The [`AssetInfo`] that is being purchased with `new_initial_asset`. - pub new_target_asset: AssetInfo, - /// The time in seconds between DCA purchases. - pub new_interval: u64, - /// a [`Uint128`] amount of `new_initial_asset` to spend each DCA purchase. - pub new_dca_amount: Uint128, - /// A bool flag that determines if the order's last purchase time should be reset. - pub should_reset_purchase_time: bool, -} +use crate::{ + error::ContractError, get_token_allowance::get_token_allowance, helpers::ots, state::DCA, +}; /// ## Description /// Modifies an existing DCA order for a user such that the new parameters will apply to the @@ -40,117 +27,96 @@ pub struct ModifyDcaOrderParameters { /// /// * `order_details` - The [`ModifyDcaOrderParameters`] details about the old and new DCA order /// parameters. +#[allow(clippy::too_many_arguments)] pub fn modify_dca_order( deps: DepsMut, env: Env, info: MessageInfo, - order_details: ModifyDcaOrderParameters, + id: u64, + initial_amount: Option, + interval: Option, + dca_amount: Option, + config_override: Option, ) -> Result { - let ModifyDcaOrderParameters { - old_initial_asset, - new_initial_asset, - new_target_asset, - new_interval, - new_dca_amount, - should_reset_purchase_time, - } = order_details; - - let mut orders = USER_DCA - .may_load(deps.storage, &info.sender)? - .unwrap_or_default(); - - // check that old_initial_asset.info exists - let order = orders - .iter_mut() - .find(|order| order.initial_asset.info == old_initial_asset) - .ok_or(ContractError::NonexistentDca {})?; - - let should_refund = order.initial_asset.amount > new_initial_asset.amount; - let asset_difference = Asset { - info: new_initial_asset.info.clone(), - amount: match should_refund { - true => order - .initial_asset - .amount - .checked_sub(new_initial_asset.amount)?, - false => new_initial_asset - .amount - .checked_sub(order.initial_asset.amount)?, - }, - }; - - let mut messages = Vec::new(); + let mut order = DCA.load(deps.storage, id)?; - if old_initial_asset == new_initial_asset.info { - if !should_refund { - // if the user needs to have deposited more, check that we have the correct funds/allowance sent - // this is the case only when the old_initial_asset and new_initial_asset are the same + (order.owner == info.sender) + .then(|| ()) + .ok_or(ContractError::Unauthorized {})?; - // if native token, they should have included it in the message - // otherwise, if cw20 token, they should have provided the correct allowance - match &old_initial_asset { - AssetInfo::NativeToken { .. } => { - asset_difference.assert_sent_native_token_balance(&info)? - } - AssetInfo::Token { contract_addr } => { - let allowance = - get_token_allowance(&deps.as_ref(), &env, &info.sender, contract_addr)?; - if allowance != new_initial_asset.amount { - return Err(ContractError::InvalidTokenDeposit {}); - } - } - } - } else { - // we need to refund the user with the difference if it is a native token - if let AssetInfo::NativeToken { denom } = &new_initial_asset.info { - messages.push(BankMsg::Send { - to_address: info.sender.to_string(), - amount: coins(asset_difference.amount.u128(), denom), - }) - } - } - } else { - // they are different assets, so we will return the old_initial_asset if it is a native token - if let AssetInfo::NativeToken { denom } = &new_initial_asset.info { - messages.push(BankMsg::Send { - to_address: info.sender.to_string(), - amount: coins(order.initial_asset.amount.u128(), denom), - }) - } + if let Some(initial_amount) = initial_amount { + // check if new amount is greater than old amount + (initial_amount > order.initial_asset.amount) + .then(|| ()) + .ok_or(ContractError::InvalidNewInitialAmount {})?; - // validate that user sent either native tokens or has set allowance for the new token - match &new_initial_asset.info { - AssetInfo::NativeToken { .. } => { - new_initial_asset.assert_sent_native_token_balance(&info)? + match &order.initial_asset.info { + AssetInfo::NativeToken { denom } => { + match info.funds.iter().find(|e| &e.denom == denom) { + Some(amt) => (amt.amount >= (initial_amount - order.initial_asset.amount)) + .then(|| ()) + .ok_or(ContractError::InvalidNativeTokenDeposit {}), + None => Err(ContractError::InvalidNativeTokenDeposit {}), + }?; } AssetInfo::Token { contract_addr } => { let allowance = get_token_allowance(&deps.as_ref(), &env, &info.sender, contract_addr)?; - if allowance != new_initial_asset.amount { + if allowance < initial_amount { return Err(ContractError::InvalidTokenDeposit {}); } } } + + order.initial_asset.amount = initial_amount; + + // check that initial_asset.amount is divisible by dca_amount + if !order + .initial_asset + .amount + .checked_rem(order.dca_amount) + .map_err(StdError::divide_by_zero)? + .is_zero() + { + return Err(ContractError::IndivisibleDeposit {}); + } + } + + if let Some(interval) = interval { + order.interval = interval; } - // update order - order.initial_asset = new_initial_asset.clone(); - order.target_asset = new_target_asset.clone(); - order.interval = new_interval; - order.dca_amount = new_dca_amount; + if let Some(dca_amount) = dca_amount { + if dca_amount > order.initial_asset.amount { + return Err(ContractError::DepositTooSmall {}); + } + + // check that initial_asset.amount is divisible by dca_amount + if !order + .initial_asset + .amount + .checked_rem(dca_amount) + .map_err(StdError::divide_by_zero)? + .is_zero() + { + return Err(ContractError::IndivisibleDeposit {}); + } + + order.dca_amount = dca_amount; + } - if should_reset_purchase_time { - order.last_purchase = 0; + if let Some(config) = config_override { + order.config_override = config; } - USER_DCA.save(deps.storage, &info.sender, &orders)?; + DCA.save(deps.storage, id, &order)?; Ok(Response::new().add_attributes(vec![ attr("action", "modify_dca_order"), - attr("old_initial_asset", old_initial_asset.to_string()), - attr("new_initial_asset", new_initial_asset.to_string()), - attr("new_target_asset", new_target_asset.to_string()), - attr("new_interval", new_interval.to_string()), - attr("new_dca_amount", new_dca_amount), + attr("id", id.to_string()), + attr("new_initial_asset_amount", ots(&initial_amount)), + attr("new_interval", ots(&interval)), + attr("new_dca_amount", ots(&dca_amount)), + attr("new_config_override", ots(&config_override)), ])) } diff --git a/contracts/dca/src/handlers/perform_dca_purchase.rs b/contracts/dca/src/handlers/perform_dca_purchase.rs index 4115f2a..96065fa 100644 --- a/contracts/dca/src/handlers/perform_dca_purchase.rs +++ b/contracts/dca/src/handlers/perform_dca_purchase.rs @@ -1,17 +1,16 @@ use astroport::{ - asset::{addr_validate_to_lower, AssetInfo, UUSD_DENOM}, + asset::AssetInfo, router::{ExecuteMsg as RouterExecuteMsg, SwapOperation}, }; -use astroport_dca::dca::DcaInfo; use cosmwasm_std::{ - attr, to_binary, BankMsg, Coin, CosmosMsg, DepsMut, Env, MessageInfo, Response, Uint128, - WasmMsg, + attr, to_binary, Coin, CosmosMsg, DepsMut, Env, MessageInfo, Response, Uint128, WasmMsg, }; use cw20::Cw20ExecuteMsg; use crate::{ error::ContractError, - state::{UserConfig, CONFIG, USER_CONFIG, USER_DCA}, + helpers::asset_transfer, + state::{CONFIG, DCA, TIPS, USER_CONFIG}, }; /// ## Description @@ -35,15 +34,15 @@ pub fn perform_dca_purchase( deps: DepsMut, env: Env, info: MessageInfo, - user: String, + id: u64, hops: Vec, ) -> Result { // validate user address - let user_address = addr_validate_to_lower(deps.api, &user)?; + let mut order = DCA.load(deps.storage, id)?; // retrieve configs - let user_config = USER_CONFIG - .may_load(deps.storage, &user_address)? + let mut user_config = USER_CONFIG + .may_load(deps.storage, &order.owner)? .unwrap_or_default(); let contract_config = CONFIG.load(deps.storage)?; @@ -53,173 +52,152 @@ pub fn perform_dca_purchase( } // validate hops does not exceed max_hops + let max_hops = order + .config_override + .max_hops + .unwrap_or_else(|| user_config.max_hops.unwrap_or(contract_config.max_hops)); let hops_len = hops.len() as u32; - if hops_len > user_config.max_hops.unwrap_or(contract_config.max_hops) { - return Err(ContractError::MaxHopsAssertion { hops: hops_len }); - } - - // validate that all middle hops (last hop excluded) are whitelisted tokens for the ask_denom or ask_asset - let middle_hops = &hops[..hops.len() - 1]; - for swap in middle_hops { - match swap { - SwapOperation::NativeSwap { ask_denom, .. } => { - if !contract_config - .whitelisted_tokens - .iter() - .any(|token| match token { - AssetInfo::NativeToken { denom } => ask_denom == denom, - AssetInfo::Token { .. } => false, - }) - { - // not a whitelisted native token - return Err(ContractError::InvalidHopRoute { - token: ask_denom.to_string(), - }); - } - } - SwapOperation::AstroSwap { ask_asset_info, .. } => { - if !contract_config.is_whitelisted_asset(ask_asset_info) { - return Err(ContractError::InvalidHopRoute { - token: ask_asset_info.to_string(), - }); - } - } - } - } - - // validate purchaser has enough funds to pay the sender - let tip_cost = contract_config - .per_hop_fee - .checked_mul(Uint128::from(hops_len))?; - if tip_cost > user_config.tip_balance { - return Err(ContractError::InsufficientTipBalance {}); + if hops_len > max_hops { + return Err(ContractError::MaxHopsAssertion { hops: max_hops }); } // retrieve max_spread from user config, or default to contract set max_spread - let max_spread = user_config.max_spread.unwrap_or(contract_config.max_spread); + let max_spread = order + .config_override + .max_spread + .unwrap_or_else(|| user_config.max_spread.unwrap_or(contract_config.max_spread)); // store messages to send in response let mut messages: Vec = Vec::new(); - // load user dca orders and update the relevant one - USER_DCA.update( - deps.storage, - &user_address, - |orders| -> Result, ContractError> { - let mut orders = orders.ok_or(ContractError::NonexistentDca {})?; - - let order = orders - .iter_mut() - .find(|order| match &hops[0] { - SwapOperation::NativeSwap { ask_denom, .. } => { - match &order.initial_asset.info { - AssetInfo::NativeToken { denom } => ask_denom == denom, - _ => false, - } - } - SwapOperation::AstroSwap { - offer_asset_info, .. - } => offer_asset_info == &order.initial_asset.info, - }) - .ok_or(ContractError::NonexistentDca {})?; - - // check that it has been long enough between dca purchases - if order.last_purchase + order.interval > env.block.time.seconds() { - return Err(ContractError::PurchaseTooEarly {}); - } + // validate all swap operation + for (idx, hop) in hops.iter().enumerate() { + match hop { + SwapOperation::NativeSwap { .. } => Err(ContractError::InvalidNativeSwap {})?, + SwapOperation::AstroSwap { + offer_asset_info, + ask_asset_info, + } => { + // validate the first offer asset info + if idx == 0 { + (offer_asset_info == &order.initial_asset.info) + .then(|| ()) + .ok_or(ContractError::InitialAssetAssertion {})?; + } - // check that last hop is target asset - let last_hop = &hops - .last() - .ok_or(ContractError::EmptyHopRoute {})? - .get_target_asset_info(); - if last_hop != &order.target_asset { - return Err(ContractError::TargetAssetAssertion {}); - } + // validate the last ask asset info + if idx == hops.len() - 1 { + (ask_asset_info == &order.target_asset) + .then(|| ()) + .ok_or(ContractError::TargetAssetAssertion {})?; + } - // subtract dca_amount from order and update last_purchase time - order.initial_asset.amount = order - .initial_asset - .amount - .checked_sub(order.dca_amount) - .map_err(|_| ContractError::InsufficientBalance {})?; - order.last_purchase = env.block.time.seconds(); - - // add funds and router message to response - if let AssetInfo::Token { contract_addr } = &order.initial_asset.info { - // send a TransferFrom request to the token to the router - messages.push( - WasmMsg::Execute { - contract_addr: contract_addr.to_string(), - funds: vec![], - msg: to_binary(&Cw20ExecuteMsg::TransferFrom { - owner: user_address.to_string(), - recipient: contract_config.router_addr.to_string(), - amount: order.dca_amount, - })?, - } - .into(), - ); + // validate that all middle hops (last hop excluded) are whitelisted tokens for the ask_denom or ask_asset + if hops.len() > 1 && idx < hops.len() - 1 { + (contract_config.is_whitelisted_asset(ask_asset_info)) + .then(|| ()) + .ok_or(ContractError::InvalidHopRoute { + token: ask_asset_info.to_string(), + })?; + } } + }; + } - // if it is a native token, we need to send the funds - let funds = match &order.initial_asset.info { - AssetInfo::NativeToken { denom } => vec![Coin { - amount: order.dca_amount, - denom: denom.clone(), - }], - AssetInfo::Token { .. } => vec![], - }; + // check that it has been long enough between dca purchases + if order.last_purchase + order.interval > env.block.time.seconds() { + return Err(ContractError::PurchaseTooEarly {}); + } - // tell the router to perform swap operations + // subtract dca_amount from order and update last_purchase time + order.initial_asset.amount = order + .initial_asset + .amount + .checked_sub(order.dca_amount) + .map_err(|_| ContractError::InsufficientBalance {})?; + order.last_purchase = env.block.time.seconds(); + + let funds = match &order.initial_asset.info { + // if its a native token, we need to send the funds + AssetInfo::NativeToken { denom } => vec![Coin { + amount: order.dca_amount, + denom: denom.clone(), + }], + //if its a token, send a TransferFrom request to the token to the router + AssetInfo::Token { contract_addr } => { messages.push( WasmMsg::Execute { - contract_addr: contract_config.router_addr.to_string(), - funds, - msg: to_binary(&RouterExecuteMsg::ExecuteSwapOperations { - operations: hops, - minimum_receive: None, - to: Some(user_address.clone()), - max_spread: Some(max_spread), + contract_addr: contract_addr.to_string(), + funds: vec![], + msg: to_binary(&Cw20ExecuteMsg::TransferFrom { + owner: order.owner.to_string(), + recipient: contract_config.router_addr.to_string(), + amount: order.dca_amount, })?, } .into(), ); - Ok(orders) - }, - )?; - - // remove tip from purchaser - USER_CONFIG.update( - deps.storage, - &user_address, - |user_config| -> Result { - let mut user_config = user_config.unwrap_or_default(); - - user_config.tip_balance = user_config - .tip_balance - .checked_sub(tip_cost) - .map_err(|_| ContractError::InsufficientTipBalance {})?; - - Ok(user_config) - }, - )?; + vec![] + } + }; - // add tip payment to messages + // tell the router to perform swap operations messages.push( - BankMsg::Send { - to_address: info.sender.to_string(), - amount: vec![Coin { - amount: tip_cost, - denom: UUSD_DENOM.to_string(), - }], + WasmMsg::Execute { + contract_addr: contract_config.router_addr.to_string(), + funds, + msg: to_binary(&RouterExecuteMsg::ExecuteSwapOperations { + operations: hops, + minimum_receive: None, + to: Some(order.owner.to_string()), + max_spread: Some(max_spread), + })?, } .into(), ); + // validate purchaser has enough funds to pay the sender + let tip_costs = TIPS.load(deps.storage)?; + let mut is_paid = false; + 'check_bal: for (idx, balance) in user_config.tips_balance.iter_mut().enumerate() { + let this_cost = tip_costs.iter().find(|e| e.info == balance.info); + if let Some(cost_per_hop) = this_cost { + let total_cost = cost_per_hop.amount * Uint128::from(hops_len); + if let Ok(new_balance) = balance.amount.checked_sub(total_cost) { + match new_balance == Uint128::zero() { + true => { + user_config.tips_balance.remove(idx); + } + false => { + balance.amount = new_balance; + } + } + is_paid = true; + + messages.push(asset_transfer( + &cost_per_hop.info, + total_cost, + &info.sender, + )?); + + break 'check_bal; + } + } + } + + is_paid + .then(|| ()) + .ok_or(ContractError::InsufficientTipBalance {})?; + + // update user tip balance + USER_CONFIG.save(deps.storage, &order.owner, &user_config)?; + + // update order + DCA.save(deps.storage, id, &order)?; + Ok(Response::new().add_messages(messages).add_attributes(vec![ attr("action", "perform_dca_purchase"), - attr("tip_cost", tip_cost), + attr("id", id.to_string()), ])) } diff --git a/contracts/dca/src/handlers/update_config.rs b/contracts/dca/src/handlers/update_config.rs index 348fe03..58b5fd2 100644 --- a/contracts/dca/src/handlers/update_config.rs +++ b/contracts/dca/src/handlers/update_config.rs @@ -1,7 +1,13 @@ -use astroport::{asset::AssetInfo, querier::query_factory_config}; +use astroport::{ + asset::{Asset, AssetInfo}, + querier::query_factory_config, +}; use cosmwasm_std::{attr, Decimal, DepsMut, MessageInfo, Response, StdError, Uint128}; -use crate::{error::ContractError, state::CONFIG}; +use crate::{ + error::ContractError, + state::{CONFIG, TIPS}, +}; /// ## Description /// Updates the contract configuration with the specified optional parameters. @@ -32,9 +38,9 @@ pub fn update_config( deps: DepsMut, info: MessageInfo, max_hops: Option, - per_hop_fee: Option, whitelisted_tokens: Option>, max_spread: Option, + tips: Option>, ) -> Result { let config = CONFIG.load(deps.storage)?; let factory_config = query_factory_config(&deps.querier, config.factory_addr)?; @@ -49,11 +55,11 @@ pub fn update_config( config.max_hops = new_max_hops; } - if let Some(new_per_hop_fee) = per_hop_fee { - config.per_hop_fee = new_per_hop_fee; - } - if let Some(new_whitelisted_tokens) = whitelisted_tokens { + new_whitelisted_tokens + .iter() + .try_for_each(|e| e.check(deps.api))?; + config.whitelisted_tokens = new_whitelisted_tokens; } @@ -64,5 +70,21 @@ pub fn update_config( Ok(config) })?; + if let Some(new_tips) = tips { + new_tips + .iter() + .try_for_each(|tip| -> Result<_, ContractError> { + (tip.amount > Uint128::zero()) + .then(|| ()) + .ok_or(ContractError::InvalidTipAmount {})?; + + tip.info.check(deps.api)?; + + Ok(()) + })?; + + TIPS.save(deps.storage, &new_tips)?; + } + Ok(Response::default().add_attributes(vec![attr("action", "update_config")])) } diff --git a/contracts/dca/src/handlers/update_user_config.rs b/contracts/dca/src/handlers/update_user_config.rs index 45b0674..a512f0f 100644 --- a/contracts/dca/src/handlers/update_user_config.rs +++ b/contracts/dca/src/handlers/update_user_config.rs @@ -1,19 +1,7 @@ +use astroport_dca::UserConfig; use cosmwasm_std::{attr, Decimal, DepsMut, MessageInfo, Response}; -use crate::{ - error::ContractError, - state::{UserConfig, USER_CONFIG}, -}; - -fn serde_option(option: Option) -> String -where - T: ToString, -{ - match option { - Some(v) => v.to_string(), - None => "none".to_string(), - } -} +use crate::{error::ContractError, helpers::ots, state::USER_CONFIG}; /// ## Description /// Updates a users configuration with the specified parameters. @@ -50,8 +38,8 @@ pub fn update_user_config( Ok(Response::new().add_attributes(vec![ attr("action", "update_user_config"), - attr("max_hops", serde_option(max_hops)), - attr("max_spread", serde_option(max_spread)), + attr("max_hops", ots(&max_hops)), + attr("max_spread", ots(&max_spread)), ])) } @@ -59,7 +47,8 @@ pub fn update_user_config( mod tests { use std::str::FromStr; - use astroport_dca::dca::ExecuteMsg; + use astroport::asset::{Asset, AssetInfo}; + use astroport_dca::{ExecuteMsg, UserConfig}; use cosmwasm_std::{ attr, coin, testing::{mock_dependencies, mock_env, mock_info}, @@ -68,12 +57,12 @@ mod tests { use crate::{ contract::execute, - state::{UserConfig, USER_CONFIG}, + state::{TIPS, USER_CONFIG}, }; #[test] fn does_update_user_config() { - let mut deps = mock_dependencies(&[]); + let mut deps = mock_dependencies(); let info = mock_info("creator", &[]); let msg = ExecuteMsg::UpdateUserConfig { @@ -99,14 +88,25 @@ mod tests { UserConfig { max_hops: Some(6), max_spread: Some(Decimal::from_str("0.025").unwrap()), - tip_balance: Uint128::zero() + tips_balance: vec![], } ) } #[test] fn does_not_change_tip_balance() { - let mut deps = mock_dependencies(&[]); + let mut deps = mock_dependencies(); + + TIPS.save( + &mut deps.storage, + &vec![Asset { + info: AssetInfo::NativeToken { + denom: "uusd".to_string(), + }, + amount: Uint128::new(100), + }], + ) + .unwrap(); let info = mock_info("creator", &[]); let msg = ExecuteMsg::UpdateUserConfig { @@ -116,19 +116,27 @@ mod tests { // add tip let send_info = mock_info("creator", &[coin(10_000, "uusd")]); - let send_tip_msg = ExecuteMsg::AddBotTip {}; + let send_tip_msg = ExecuteMsg::AddTips {}; execute(deps.as_mut(), mock_env(), send_info.clone(), send_tip_msg).unwrap(); // does not modify the tip balance execute(deps.as_mut(), mock_env(), info, msg).unwrap(); let config = USER_CONFIG.load(&deps.storage, &send_info.sender).unwrap(); - assert_eq!(config.tip_balance, send_info.funds[0].amount); + assert_eq!( + config.tips_balance, + vec![Asset { + info: AssetInfo::NativeToken { + denom: "uusd".to_string(), + }, + amount: Uint128::new(10_000), + }] + ); } #[test] fn does_reset_config() { - let mut deps = mock_dependencies(&[]); + let mut deps = mock_dependencies(); let info = mock_info("creator", &[]); let update_msg = ExecuteMsg::UpdateUserConfig { @@ -151,7 +159,7 @@ mod tests { UserConfig { max_hops: Some(6), max_spread: None, - tip_balance: Uint128::zero() + tips_balance: vec![], } ) } diff --git a/contracts/dca/src/handlers/withdraw.rs b/contracts/dca/src/handlers/withdraw.rs index b512771..63c7874 100644 --- a/contracts/dca/src/handlers/withdraw.rs +++ b/contracts/dca/src/handlers/withdraw.rs @@ -1,7 +1,7 @@ -use astroport::asset::UUSD_DENOM; -use cosmwasm_std::{attr, coins, BankMsg, DepsMut, MessageInfo, Response, Uint128}; +use astroport::asset::Asset; +use cosmwasm_std::{attr, CosmosMsg, DepsMut, MessageInfo, Response}; -use crate::{error::ContractError, state::USER_CONFIG}; +use crate::{error::ContractError, helpers::asset_transfer, state::USER_CONFIG}; /// ## Description /// Withdraws a users bot tip from the contract. @@ -17,138 +17,45 @@ use crate::{error::ContractError, state::USER_CONFIG}; pub fn withdraw( deps: DepsMut, info: MessageInfo, - amount: Uint128, + tips: Vec, ) -> Result { let mut config = USER_CONFIG .may_load(deps.storage, &info.sender)? .unwrap_or_default(); - - config.tip_balance = config.tip_balance.checked_sub(amount)?; - - // optimization: if the user is withdrawing all their tip, they are probably never going to - // interact with the contract again. In this case, we can delete their config to save space - // otherwise, we save their new configuration - match config.tip_balance.is_zero() { - true => { - USER_CONFIG.remove(deps.storage, &info.sender); - Ok(()) - } - false => USER_CONFIG.save(deps.storage, &info.sender, &config), - }?; - - Ok(Response::new() - .add_attributes(vec![ - attr("action", "withdraw"), - attr("tip_removed", amount), - ]) - .add_message(BankMsg::Send { - to_address: info.sender.to_string(), - amount: coins(amount.u128(), UUSD_DENOM), - })) -} - -#[cfg(test)] -mod tests { - use astroport_dca::dca::ExecuteMsg; - use cosmwasm_std::{ - attr, coin, - testing::{mock_dependencies, mock_env, mock_info}, - Addr, BankMsg, DepsMut, MessageInfo, OverflowError, OverflowOperation, Response, Uint128, - }; - - use crate::{ - contract::execute, - error::ContractError, - state::{UserConfig, USER_CONFIG}, - }; - - fn add_tip(deps: DepsMut, info: MessageInfo) { - execute(deps, mock_env(), info, ExecuteMsg::AddBotTip {}).unwrap(); - } - - #[test] - fn will_withdraw_tip() { - let mut deps = mock_dependencies(&[]); - - let tip_sent = coin(10_000, "uusd"); - - let info = mock_info("creator", &[]); - let msg = ExecuteMsg::Withdraw { - tip: tip_sent.amount, - }; - - // add bot tip - add_tip(deps.as_mut(), mock_info("creator", &[tip_sent.clone()])); - - // attempt bot withdraw and check that we got the expected response - let res = execute(deps.as_mut(), mock_env(), info, msg).unwrap(); - assert_eq!( - res, - Response::new() - .add_attributes(vec![ - attr("action", "withdraw"), - attr("tip_removed", tip_sent.amount) - ]) - .add_message(BankMsg::Send { - to_address: "creator".to_string(), - amount: vec![tip_sent] - }) - ) - } - - #[test] - fn does_update_config() { - let mut deps = mock_dependencies(&[]); - - let tip_sent = coin(10_000, "uusd"); - let tip_withdraw = coin(5_000, "uusd"); - - let info = mock_info("creator", &[]); - let msg = ExecuteMsg::Withdraw { - tip: tip_withdraw.amount, + let mut msgs: Vec = vec![]; + + for asset in tips { + match config + .tips_balance + .iter_mut() + .enumerate() + .find(|e| e.1.info == asset.info) + { + Some((idx, bal)) => { + match bal.amount == asset.amount { + // withdraw all + true => { + config.tips_balance.remove(idx); + } + false => { + bal.amount = bal + .amount + .checked_sub(asset.amount) + .map_err(|_| ContractError::InsufficientTipWithdrawBalance {})?; + } + }; + } + None => { + Err(ContractError::InsufficientTipWithdrawBalance {})?; + } }; - // add bot tip - add_tip(deps.as_mut(), mock_info("creator", &[tip_sent])); - - // attempt bot withdraw and check that config was updated - execute(deps.as_mut(), mock_env(), info, msg).unwrap(); - let config = USER_CONFIG - .load(&deps.storage, &Addr::unchecked("creator")) - .unwrap(); - assert_eq!( - config, - UserConfig { - tip_balance: Uint128::from(5_000u64), - ..UserConfig::default() - } - ) + msgs.push(asset_transfer(&asset.info, asset.amount, &info.sender)?); } - #[test] - fn wont_excess_withdraw() { - let mut deps = mock_dependencies(&[]); - - let tip_sent = coin(10_000, "uusd"); - let tip_withdraw = coin(15_000, "uusd"); - - let info = mock_info("creator", &[]); - let msg = ExecuteMsg::Withdraw { - tip: tip_withdraw.amount, - }; + USER_CONFIG.save(deps.storage, &info.sender, &config)?; - // add bot tip - add_tip(deps.as_mut(), mock_info("creator", &[tip_sent.clone()])); - - // attempt bot withdraw and check that it failed - let err = execute(deps.as_mut(), mock_env(), info, msg).unwrap_err(); - assert_eq!( - err, - ContractError::OverflowError(OverflowError::new( - OverflowOperation::Sub, - tip_sent.amount, - tip_withdraw.amount - )) - ); - } + Ok(Response::new() + .add_messages(msgs) + .add_attributes(vec![attr("action", "withdraw")])) } diff --git a/contracts/dca/src/helpers.rs b/contracts/dca/src/helpers.rs new file mode 100644 index 0000000..bbd08a5 --- /dev/null +++ b/contracts/dca/src/helpers.rs @@ -0,0 +1,30 @@ +use astroport::asset::AssetInfo; +use cosmwasm_std::{to_binary, Addr, BankMsg, Coin, CosmosMsg, StdResult, Uint128, WasmMsg}; +use cw20::Cw20ExecuteMsg; + +pub fn asset_transfer(info: &AssetInfo, amount: Uint128, to: &Addr) -> StdResult { + Ok(match &info { + AssetInfo::Token { contract_addr } => CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: contract_addr.to_string(), + msg: to_binary(&Cw20ExecuteMsg::Transfer { + recipient: to.to_string(), + amount, + })?, + funds: vec![], + }), + AssetInfo::NativeToken { denom } => CosmosMsg::Bank(BankMsg::Send { + to_address: to.to_string(), + amount: vec![Coin { + denom: denom.to_string(), + amount, + }], + }), + }) +} + +pub fn ots(v: &Option) -> String { + match v { + Some(val) => val.to_string(), + None => "None".to_string(), + } +} diff --git a/contracts/dca/src/lib.rs b/contracts/dca/src/lib.rs index 80ec461..ba4abcc 100644 --- a/contracts/dca/src/lib.rs +++ b/contracts/dca/src/lib.rs @@ -6,3 +6,7 @@ mod handlers; mod queries; mod get_token_allowance; +mod helpers; + +#[cfg(test)] +mod tests; diff --git a/contracts/dca/src/queries/get_all_dca_orders.rs b/contracts/dca/src/queries/get_all_dca_orders.rs new file mode 100644 index 0000000..cf5e23e --- /dev/null +++ b/contracts/dca/src/queries/get_all_dca_orders.rs @@ -0,0 +1,28 @@ +use astroport_dca::DcaInfo; +use cosmwasm_std::{Deps, Order, StdResult}; +use cw_storage_plus::Bound; + +use crate::state::DCA; + +const ORDER_LIMIT: u64 = 50; + +/// ## Description +/// Returns all DCA orders currently set. +/// +/// The result is returned in a [`Vec`] object. +pub fn get_all_dca_orders( + deps: Deps, + start_after: Option, + limit: Option, + is_ascending: Option, +) -> StdResult> { + let bound = match is_ascending.unwrap_or(false) { + true => (start_after.map(Bound::exclusive), None, Order::Ascending), + false => (None, start_after.map(Bound::exclusive), Order::Descending), + }; + + DCA.range(deps.storage, bound.0, bound.1, bound.2) + .map(|e| -> StdResult<_> { Ok(e?.1) }) + .take(limit.unwrap_or(ORDER_LIMIT) as usize) + .collect::>>() +} diff --git a/contracts/dca/src/queries/get_tips.rs b/contracts/dca/src/queries/get_tips.rs new file mode 100644 index 0000000..22f0ebd --- /dev/null +++ b/contracts/dca/src/queries/get_tips.rs @@ -0,0 +1,13 @@ +use astroport::asset::Asset; +use cosmwasm_std::{Deps, StdResult}; + +use crate::state::TIPS; + +/// ## Description +/// Returns the current tips denom and amount configuration as a [`Vec`] object. +/// +/// ## Arguments +/// * `deps` - A [`Deps`] that contains the dependencies. +pub fn get_tips(deps: Deps) -> StdResult> { + TIPS.load(deps.storage) +} diff --git a/contracts/dca/src/queries/get_user_config.rs b/contracts/dca/src/queries/get_user_config.rs index 3926fff..8b517e3 100644 --- a/contracts/dca/src/queries/get_user_config.rs +++ b/contracts/dca/src/queries/get_user_config.rs @@ -1,7 +1,8 @@ use astroport::asset::addr_validate_to_lower; +use astroport_dca::UserConfig; use cosmwasm_std::{Deps, StdResult}; -use crate::state::{UserConfig, USER_CONFIG}; +use crate::state::USER_CONFIG; /// ## Description /// Returns the configuration set for a user to override the default contract configuration. @@ -15,5 +16,7 @@ use crate::state::{UserConfig, USER_CONFIG}; pub fn get_user_config(deps: Deps, user: String) -> StdResult { let user_address = addr_validate_to_lower(deps.api, &user)?; - USER_CONFIG.load(deps.storage, &user_address) + Ok(USER_CONFIG + .may_load(deps.storage, &user_address)? + .unwrap_or_default()) } diff --git a/contracts/dca/src/queries/get_user_dca_orders.rs b/contracts/dca/src/queries/get_user_dca_orders.rs index 9ca8a77..e613e32 100644 --- a/contracts/dca/src/queries/get_user_dca_orders.rs +++ b/contracts/dca/src/queries/get_user_dca_orders.rs @@ -1,13 +1,16 @@ use astroport::asset::{addr_validate_to_lower, AssetInfo}; -use astroport_dca::dca::DcaQueryInfo; -use cosmwasm_std::{Deps, Env, StdResult}; +use astroport_dca::UserDcaInfo; +use cosmwasm_std::{Deps, Env, Order, StdResult}; -use crate::{get_token_allowance::get_token_allowance, state::USER_DCA}; +use crate::{ + get_token_allowance::get_token_allowance, + state::{DCA, DCA_OWNER}, +}; /// ## Description /// Returns a users DCA orders currently set. /// -/// The result is returned in a [`Vec`] object of the users current DCA orders with the /// `amount` of each order set to the native token amount that can be spent, or the token allowance. /// /// ## Arguments @@ -16,15 +19,15 @@ use crate::{get_token_allowance::get_token_allowance, state::USER_DCA}; /// * `env` - The [`Env`] of the blockchain. /// /// * `user` - The users lowercase address as a [`String`]. -pub fn get_user_dca_orders(deps: Deps, env: Env, user: String) -> StdResult> { +pub fn get_user_dca_orders(deps: Deps, env: Env, user: String) -> StdResult> { let user_address = addr_validate_to_lower(deps.api, &user)?; - USER_DCA - .load(deps.storage, &user_address)? - .into_iter() - .map(|order| { - Ok(DcaQueryInfo { - info: order, + DCA_OWNER + .prefix(&user_address) + .keys(deps.storage, None, None, Order::Descending) + .map(|e| -> StdResult<_> { + let order = DCA.load(deps.storage, e?)?; + Ok(UserDcaInfo { token_allowance: match &order.initial_asset.info { AssetInfo::NativeToken { .. } => order.initial_asset.amount, AssetInfo::Token { contract_addr } => { @@ -32,6 +35,7 @@ pub fn get_user_dca_orders(deps: Deps, env: Env, user: String) -> StdResult>>() diff --git a/contracts/dca/src/queries/mod.rs b/contracts/dca/src/queries/mod.rs index 98d268f..3b948e0 100644 --- a/contracts/dca/src/queries/mod.rs +++ b/contracts/dca/src/queries/mod.rs @@ -1,7 +1,11 @@ +mod get_all_dca_orders; mod get_config; +mod get_tips; mod get_user_config; mod get_user_dca_orders; +pub use get_all_dca_orders::get_all_dca_orders; pub use get_config::get_config; +pub use get_tips::get_tips; pub use get_user_config::get_user_config; pub use get_user_dca_orders::get_user_dca_orders; diff --git a/contracts/dca/src/state.rs b/contracts/dca/src/state.rs index 689c31b..32b8a97 100644 --- a/contracts/dca/src/state.rs +++ b/contracts/dca/src/state.rs @@ -1,10 +1,10 @@ -use astroport::asset::AssetInfo; -use cosmwasm_std::{Addr, Decimal, Uint128}; +use astroport::asset::{Asset, AssetInfo}; +use cosmwasm_std::{Addr, Decimal, Empty}; use cw_storage_plus::{Item, Map}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use astroport_dca::dca::DcaInfo; +use astroport_dca::{DcaInfo, UserConfig}; /// Stores the main dca module parameters. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -13,8 +13,6 @@ pub struct Config { pub max_hops: u32, /// The maximum amount of spread when performing a swap from `initial_asset` to `target_asset` when DCAing if the user does not specify pub max_spread: Decimal, - /// The fee a user must pay per hop performed in a DCA purchase - pub per_hop_fee: Uint128, /// The whitelisted tokens that can be used in a DCA purchase route pub whitelisted_tokens: Vec, /// The address of the Astroport factory contract @@ -29,30 +27,13 @@ impl Config { } } -/// Stores the users custom configuration -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct UserConfig { - /// An override for the maximum amount of hops to perform from `initial_asset` to `target_asset` when DCAing - pub max_hops: Option, - /// An override for the maximum amount of spread when performing a swap from `initial_asset` to `target_asset` when DCAing - pub max_spread: Option, - /// The amount of uusd the user has deposited for their tips when performing DCA purchases - pub tip_balance: Uint128, -} - -impl Default for UserConfig { - fn default() -> Self { - UserConfig { - max_hops: None, - max_spread: None, - tip_balance: Uint128::zero(), - } - } -} - /// The contract configuration pub const CONFIG: Item = Item::new("config"); +pub const TIPS: Item> = Item::new("tips"); + /// The configuration set by each user pub const USER_CONFIG: Map<&Addr, UserConfig> = Map::new("user_config"); -/// The DCA orders for a user -pub const USER_DCA: Map<&Addr, Vec> = Map::new("user_dca"); + +pub const DCA_ID: Item = Item::new("dca_id"); +pub const DCA: Map = Map::new("dca"); +pub const DCA_OWNER: Map<(&Addr, u64), Empty> = Map::new("dca_o"); diff --git a/contracts/dca/src/tests/common.rs b/contracts/dca/src/tests/common.rs new file mode 100644 index 0000000..6a34ce2 --- /dev/null +++ b/contracts/dca/src/tests/common.rs @@ -0,0 +1,395 @@ +#![allow(dead_code)] + +use std::{error::Error, str::FromStr}; + +use astroport::{ + asset::{Asset, AssetInfo}, + factory::{PairConfig, PairType}, +}; +use astroport_dca::QueryMsg; +use cosmwasm_std::{to_binary, Addr, BlockInfo, Coin, Decimal, Timestamp, Uint128}; +use cw20::Cw20Coin; +use cw_multi_test::{App, AppBuilder, ContractWrapper, Executor}; + +use crate::state::Config; + +pub const ADMIN: &str = "admin"; +pub const FEE: &str = "fee"; +pub const USER_ONE: &str = "userone"; +pub const USER_TWO: &str = "usertwo"; +pub const USER_THREE: &str = "userthree"; +pub const LUNA: &str = "uluna"; +pub const USDC: &str = "uusdc"; +pub const USDT: &str = "uusdt"; +pub const OSMO: &str = "uosmo"; + +pub const CW20_CODE: u64 = 1; + +pub fn proceed(app: &mut App, block: u64) { + app.update_block(|b| { + b.height += block; + b.time = b.time.plus_seconds(6 * block); + }) +} + +pub fn new_cw20(app: &mut App, owner: &str) -> Addr { + app.instantiate_contract( + CW20_CODE, + Addr::unchecked(owner), + &cw20_base::msg::InstantiateMsg { + name: "testcw".to_string(), + symbol: "tcw".to_string(), + decimals: 6, + initial_balances: vec![Cw20Coin { + address: owner.to_string(), + amount: Uint128::new(2_000_000_000), + }], + mint: None, + marketing: None, + }, + &[], + "cw20", + None, + ) + .unwrap() +} + +pub fn native_info(denom: impl Into) -> AssetInfo { + AssetInfo::NativeToken { + denom: denom.into(), + } +} + +pub fn token_info(addr: impl Into) -> AssetInfo { + AssetInfo::Token { + contract_addr: Addr::unchecked(addr.into()), + } +} + +pub fn native_asset(denom: impl Into, amount: u128) -> Asset { + Asset { + info: AssetInfo::NativeToken { + denom: denom.into(), + }, + amount: Uint128::new(amount), + } +} + +pub fn token_asset(addr: impl Into, amount: u128) -> Asset { + Asset { + info: AssetInfo::Token { + contract_addr: Addr::unchecked(addr.into()), + }, + amount: amount.into(), + } +} + +pub fn instantiate() -> (App, Addr) { + let mut app = AppBuilder::new().build(|r, _, storage| { + r.bank + .init_balance( + storage, + &Addr::unchecked(ADMIN), + vec![ + Coin::new(3_200_000_001, LUNA), + Coin::new(3_000_000_001, USDC), + Coin::new(3_000_000_001, USDT), + Coin::new(1_000_000_001, OSMO), + ], + ) + .unwrap(); + + r.bank + .init_balance( + storage, + &Addr::unchecked(USER_ONE), + vec![ + Coin::new(1_000_000_000, LUNA), + Coin::new(1_000_000_000, USDC), + Coin::new(2_000_000_000, USDT), + Coin::new(1_000_000_000, OSMO), + ], + ) + .unwrap(); + + r.bank + .init_balance( + storage, + &Addr::unchecked(USER_THREE), + vec![ + Coin::new(1_000_000_000, LUNA), + Coin::new(1_000_000_000, USDC), + Coin::new(1_000_000_000, USDT), + Coin::new(1_000_000_000, OSMO), + ], + ) + .unwrap(); + }); + + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds(1), + chain_id: "test-1".to_string(), + }); + + let cw20_code = app.store_code(Box::new(ContractWrapper::new( + cw20_base::contract::execute, + cw20_base::contract::instantiate, + cw20_base::contract::query, + ))); + + let cw1_code = app.store_code(Box::new(ContractWrapper::new( + cw1_whitelist::contract::execute, + cw1_whitelist::contract::instantiate, + cw1_whitelist::contract::query, + ))); + + let xyk_pair_code = app.store_code(Box::new( + ContractWrapper::new( + astroport_pair::contract::execute, + astroport_pair::contract::instantiate, + astroport_pair::contract::query, + ) + .with_reply(astroport_pair::contract::reply), + )); + + let stable_pair_code = app.store_code(Box::new( + ContractWrapper::new( + astroport_pair_stable::contract::execute, + astroport_pair_stable::contract::instantiate, + astroport_pair_stable::contract::query, + ) + .with_reply(astroport_pair_stable::contract::reply), + )); + + let factory_code = app.store_code(Box::new( + ContractWrapper::new( + astroport_factory::contract::execute, + astroport_factory::contract::instantiate, + astroport_factory::contract::query, + ) + .with_reply(astroport_factory::contract::reply), + )); + + let router_code = app.store_code(Box::new(ContractWrapper::new( + astroport_router::contract::execute, + astroport_router::contract::instantiate, + astroport_router::contract::query, + ))); + + let dca_code = app.store_code(Box::new(ContractWrapper::new( + crate::contract::execute, + crate::contract::instantiate, + crate::contract::query, + ))); + + let factory = app + .instantiate_contract( + factory_code, + Addr::unchecked(ADMIN), + &astroport::factory::InstantiateMsg { + pair_configs: vec![ + PairConfig { + code_id: xyk_pair_code, + pair_type: PairType::Xyk {}, + total_fee_bps: 30, + maker_fee_bps: 3333, + is_disabled: false, + is_generator_disabled: true, + }, + PairConfig { + code_id: stable_pair_code, + pair_type: PairType::Stable {}, + total_fee_bps: 5, + maker_fee_bps: 5000, + is_disabled: false, + is_generator_disabled: true, + }, + ], + token_code_id: cw20_code, + fee_address: Some(FEE.to_string()), + generator_address: None, + owner: ADMIN.to_string(), + whitelist_code_id: cw1_code, + }, + &[ + Coin::new(1, LUNA), + Coin::new(1, USDC), + Coin::new(1, USDT), + Coin::new(1, OSMO), + ], + "factory", + None, + ) + .unwrap(); + + let luna_usdc_pair_addr = app + .execute_contract( + Addr::unchecked(ADMIN), + factory.clone(), + &astroport::factory::ExecuteMsg::CreatePair { + pair_type: PairType::Xyk {}, + asset_infos: [native_info(LUNA), native_info(USDC)], + init_params: None, + }, + &[], + ) + .unwrap() + .events + .into_iter() + .flat_map(|e| e.attributes) + .find(|e| e.key == "pair_contract_addr") + .unwrap() + .value; + + app.execute_contract( + Addr::unchecked(ADMIN), + Addr::unchecked(&luna_usdc_pair_addr), + &astroport::pair::ExecuteMsg::ProvideLiquidity { + assets: [ + native_asset(LUNA, 500_000_000), + native_asset(USDC, 1_000_000_000), + ], + slippage_tolerance: None, + auto_stake: None, + receiver: None, + }, + &[Coin::new(500_000_000, LUNA), Coin::new(1_000_000_000, USDC)], + ) + .unwrap(); + + let luna_usdt_pair_addr = app + .execute_contract( + Addr::unchecked(ADMIN), + factory.clone(), + &astroport::factory::ExecuteMsg::CreatePair { + pair_type: PairType::Xyk {}, + asset_infos: [native_info(LUNA), native_info(USDT)], + init_params: None, + }, + &[], + ) + .unwrap() + .events + .into_iter() + .flat_map(|e| e.attributes) + .find(|e| e.key == "pair_contract_addr") + .unwrap() + .value; + + app.execute_contract( + Addr::unchecked(ADMIN), + Addr::unchecked(&luna_usdt_pair_addr), + &astroport::pair::ExecuteMsg::ProvideLiquidity { + assets: [ + native_asset(LUNA, 700_000_000), + native_asset(USDT, 1_000_000_000), + ], + slippage_tolerance: None, + auto_stake: None, + receiver: None, + }, + &[Coin::new(700_000_000, LUNA), Coin::new(1_000_000_000, USDT)], + ) + .unwrap(); + + let usdt_usdc_pair_addr = app + .execute_contract( + Addr::unchecked(ADMIN), + factory.clone(), + &astroport::factory::ExecuteMsg::CreatePair { + pair_type: PairType::Stable {}, + asset_infos: [native_info(USDT), native_info(USDC)], + init_params: Some( + to_binary(&astroport::pair::StablePoolParams { amp: 10 }).unwrap(), + ), + }, + &[], + ) + .unwrap() + .events + .into_iter() + .flat_map(|e| e.attributes) + .find(|e| e.key == "pair_contract_addr") + .unwrap() + .value; + + app.execute_contract( + Addr::unchecked(ADMIN), + Addr::unchecked(&usdt_usdc_pair_addr), + &astroport::pair::ExecuteMsg::ProvideLiquidity { + assets: [ + native_asset(USDT, 1_000_000_000), + native_asset(USDC, 1_000_000_000), + ], + slippage_tolerance: None, + auto_stake: None, + receiver: None, + }, + &[ + Coin::new(1_000_000_000, USDT), + Coin::new(1_000_000_000, USDC), + ], + ) + .unwrap(); + + let router = app + .instantiate_contract( + router_code, + Addr::unchecked(ADMIN), + &astroport::router::InstantiateMsg { + astroport_factory: factory.to_string(), + }, + &[], + "router", + None, + ) + .unwrap(); + + let dca = app + .instantiate_contract( + dca_code, + Addr::unchecked(ADMIN), + &astroport_dca::InstantiateMsg { + max_hops: 3, + whitelisted_tokens: vec![native_info(LUNA), native_info(USDC), native_info(USDT)], + max_spread: "0.005".to_string(), // 0.5% + factory_addr: factory.to_string(), + router_addr: router.to_string(), + tips: vec![native_asset(USDC, 1_000_000), native_asset(USDT, 1_000_000)], + }, + &[], + "dca", + None, + ) + .unwrap(); + + (app, dca) +} + +#[test] +fn proper_instantiate() -> Result<(), Box> { + let (app, dca) = instantiate(); + + let Config { + max_hops, + max_spread, + whitelisted_tokens, + .. + } = app.wrap().query_wasm_smart(&dca, &QueryMsg::Config {})?; + + assert_eq!(max_hops, 3); + assert_eq!(max_spread, Decimal::from_str("0.005")?); + assert_eq!( + whitelisted_tokens, + vec![native_info(LUNA), native_info(USDC), native_info(USDT)] + ); + + let tips: Vec = app.wrap().query_wasm_smart(&dca, &QueryMsg::Tips {})?; + assert_eq!( + tips, + vec![native_asset(USDC, 1_000_000), native_asset(USDT, 1_000_000)] + ); + + Ok(()) +} diff --git a/contracts/dca/src/tests/mod.rs b/contracts/dca/src/tests/mod.rs new file mode 100644 index 0000000..54ae84d --- /dev/null +++ b/contracts/dca/src/tests/mod.rs @@ -0,0 +1,6 @@ +mod common; +mod order; +mod purchase; +mod purchase_cw20; +mod tips; +mod user_config; diff --git a/contracts/dca/src/tests/order.rs b/contracts/dca/src/tests/order.rs new file mode 100644 index 0000000..23f47f4 --- /dev/null +++ b/contracts/dca/src/tests/order.rs @@ -0,0 +1,331 @@ +use std::error::Error; + +use astroport_dca::{DcaInfo, ExecuteMsg, QueryMsg, UserDcaInfo}; +use cosmwasm_std::{Addr, Coin, Uint128}; +use cw_multi_test::Executor; + +use crate::error::ContractError; + +use super::common::*; + +#[test] +fn create_order_not_duplicate() -> Result<(), Box> { + let (mut app, dca) = instantiate(); + + let err = app + .execute_contract( + Addr::unchecked(USER_ONE), + dca, + &ExecuteMsg::CreateDcaOrder { + initial_asset: native_asset(USDC, 50_000_000), + target_asset: native_info(USDC), + interval: 600, + dca_amount: Uint128::new(10_000_000), + start_at: None, + config_override: None, + }, + &[Coin::new(50_000_000, USDC)], + ) + .unwrap_err(); + + assert_eq!( + err.downcast::()?, + ContractError::DuplicateAsset {} + ); + + Ok(()) +} + +#[test] +fn create_order_dca_divisible() -> Result<(), Box> { + let (mut app, dca) = instantiate(); + + let err = app + .execute_contract( + Addr::unchecked(USER_ONE), + dca, + &ExecuteMsg::CreateDcaOrder { + initial_asset: native_asset(USDC, 50_000_000), + target_asset: native_info(USDT), + interval: 600, + dca_amount: Uint128::new(15_000_000), + start_at: None, + config_override: None, + }, + &[Coin::new(50_000_000, USDC)], + ) + .unwrap_err(); + + assert_eq!( + err.downcast::()?, + ContractError::IndivisibleDeposit {} + ); + + Ok(()) +} + +#[test] +fn create_order_not_too_small() -> Result<(), Box> { + let (mut app, dca) = instantiate(); + + let err = app + .execute_contract( + Addr::unchecked(USER_ONE), + dca, + &ExecuteMsg::CreateDcaOrder { + initial_asset: native_asset(USDC, 50_000_000), + target_asset: native_info(USDT), + interval: 600, + dca_amount: Uint128::new(60_000_000), + start_at: None, + config_override: None, + }, + &[Coin::new(50_000_000, USDC)], + ) + .unwrap_err(); + + assert_eq!( + err.downcast::()?, + ContractError::DepositTooSmall {} + ); + + Ok(()) +} + +#[test] +fn create_order() -> Result<(), Box> { + let (mut app, dca) = instantiate(); + + app.execute_contract( + Addr::unchecked(USER_ONE), + dca.clone(), + &ExecuteMsg::CreateDcaOrder { + initial_asset: native_asset(USDC, 50_000_000), + target_asset: native_info(LUNA), + interval: 600, + dca_amount: Uint128::new(10_000_000), + start_at: None, + config_override: None, + }, + &[Coin::new(50_000_000, USDC)], + )?; + + let infos: Vec = app.wrap().query_wasm_smart( + &dca, + &QueryMsg::UserDcaOrders { + user: USER_ONE.to_string(), + }, + )?; + + assert_eq!(infos.len(), 1); + + let UserDcaInfo { + info: + DcaInfo { + id, + owner, + initial_asset, + target_asset, + interval, + last_purchase, + dca_amount, + .. + }, + .. + } = infos.into_iter().next().unwrap(); + assert_eq!(id, 0); + assert_eq!(owner, USER_ONE); + assert_eq!(initial_asset, native_asset(USDC, 50_000_000)); + assert_eq!(target_asset, native_info(LUNA)); + assert_eq!(interval, 600); + assert_eq!(last_purchase, 1); + assert_eq!(dca_amount.u128(), 10_000_000); + + Ok(()) +} + +#[test] +fn create_multiple_orders_same_asset() -> Result<(), Box> { + let (mut app, dca) = instantiate(); + + app.execute_contract( + Addr::unchecked(USER_ONE), + dca.clone(), + &ExecuteMsg::CreateDcaOrder { + initial_asset: native_asset(USDC, 50_000_000), + target_asset: native_info(LUNA), + interval: 600, + dca_amount: Uint128::new(10_000_000), + start_at: None, + config_override: None, + }, + &[Coin::new(50_000_000, USDC)], + )?; + + app.execute_contract( + Addr::unchecked(USER_ONE), + dca.clone(), + &ExecuteMsg::CreateDcaOrder { + initial_asset: native_asset(USDC, 50_000_000), + target_asset: native_info(USDT), + interval: 600, + dca_amount: Uint128::new(10_000_000), + start_at: None, + config_override: None, + }, + &[Coin::new(50_000_000, USDC)], + )?; + + let infos: Vec = app.wrap().query_wasm_smart( + &dca, + &QueryMsg::UserDcaOrders { + user: USER_ONE.to_string(), + }, + )?; + + assert_eq!(infos.len(), 2); + + let mut infos_iter = infos.into_iter(); + let second_order = infos_iter.next().unwrap(); + assert_eq!(second_order.info.id, 1); + assert_eq!( + second_order.info.initial_asset, + native_asset(USDC, 50_000_000) + ); + let first_order = infos_iter.next().unwrap(); + assert_eq!(first_order.info.id, 0); + assert_eq!( + first_order.info.initial_asset, + native_asset(USDC, 50_000_000) + ); + + Ok(()) +} + +#[test] +fn create_multiple_orders_diff_asset() -> Result<(), Box> { + let (mut app, dca) = instantiate(); + + app.execute_contract( + Addr::unchecked(USER_ONE), + dca.clone(), + &ExecuteMsg::CreateDcaOrder { + initial_asset: native_asset(USDC, 50_000_000), + target_asset: native_info(LUNA), + interval: 600, + dca_amount: Uint128::new(10_000_000), + start_at: None, + config_override: None, + }, + &[Coin::new(50_000_000, USDC)], + )?; + + app.execute_contract( + Addr::unchecked(USER_ONE), + dca.clone(), + &ExecuteMsg::CreateDcaOrder { + initial_asset: native_asset(USDT, 50_000_000), + target_asset: native_info(LUNA), + interval: 600, + dca_amount: Uint128::new(10_000_000), + start_at: None, + config_override: None, + }, + &[Coin::new(50_000_000, USDT)], + )?; + + let infos: Vec = app.wrap().query_wasm_smart( + &dca, + &QueryMsg::UserDcaOrders { + user: USER_ONE.to_string(), + }, + )?; + + assert_eq!(infos.len(), 2); + + let mut infos_iter = infos.into_iter(); + let second_order = infos_iter.next().unwrap(); + assert_eq!(second_order.info.id, 1); + assert_eq!( + second_order.info.initial_asset, + native_asset(USDT, 50_000_000) + ); + let first_order = infos_iter.next().unwrap(); + assert_eq!(first_order.info.id, 0); + assert_eq!( + first_order.info.initial_asset, + native_asset(USDC, 50_000_000) + ); + + Ok(()) +} + +#[test] +fn create_multiple_orders_multiple_users() -> Result<(), Box> { + let (mut app, dca) = instantiate(); + + app.execute_contract( + Addr::unchecked(USER_ONE), + dca.clone(), + &ExecuteMsg::CreateDcaOrder { + initial_asset: native_asset(USDC, 50_000_000), + target_asset: native_info(LUNA), + interval: 600, + dca_amount: Uint128::new(10_000_000), + start_at: None, + config_override: None, + }, + &[Coin::new(50_000_000, USDC)], + )?; + + app.execute_contract( + Addr::unchecked(USER_ONE), + dca.clone(), + &ExecuteMsg::CreateDcaOrder { + initial_asset: native_asset(USDT, 50_000_000), + target_asset: native_info(LUNA), + interval: 600, + dca_amount: Uint128::new(10_000_000), + start_at: None, + config_override: None, + }, + &[Coin::new(50_000_000, USDT)], + )?; + + app.execute_contract( + Addr::unchecked(USER_THREE), + dca.clone(), + &ExecuteMsg::CreateDcaOrder { + initial_asset: native_asset(USDT, 50_000_000), + target_asset: native_info(LUNA), + interval: 600, + dca_amount: Uint128::new(10_000_000), + start_at: None, + config_override: None, + }, + &[Coin::new(50_000_000, USDT)], + )?; + + let infos: Vec = app.wrap().query_wasm_smart( + &dca, + &QueryMsg::AllDcaOrders { + start_after: None, + limit: None, + is_ascending: None, + }, + )?; + assert_eq!(infos.len(), 3); + assert_eq!( + infos + .into_iter() + .map(|e| (e.id, e.owner.to_string())) + .collect::>(), + vec![ + (2u64, USER_THREE.to_string()), + (1u64, USER_ONE.to_string()), + (0u64, USER_ONE.to_string()) + ], + ); + + Ok(()) +} diff --git a/contracts/dca/src/tests/purchase.rs b/contracts/dca/src/tests/purchase.rs new file mode 100644 index 0000000..653913f --- /dev/null +++ b/contracts/dca/src/tests/purchase.rs @@ -0,0 +1,595 @@ +use std::error::Error; + +use astroport::router::SwapOperation; +use astroport_dca::{ConfigOverride, DcaInfo, ExecuteMsg, QueryMsg, UserConfig, UserDcaInfo}; +use cosmwasm_std::{Addr, Coin, Uint128}; +use cw_multi_test::Executor; + +use crate::error::ContractError; + +use super::common::*; + +#[test] +fn purchase_not_too_early() -> Result<(), Box> { + let (mut app, dca) = instantiate(); + + app.execute_contract( + Addr::unchecked(USER_ONE), + dca.clone(), + &ExecuteMsg::CreateDcaOrder { + initial_asset: native_asset(USDC, 50_000_000), + target_asset: native_info(LUNA), + interval: 600, + dca_amount: Uint128::new(10_000_000), + start_at: None, + config_override: None, + }, + &[Coin::new(50_000_000, USDC)], + )?; + + let err = app + .execute_contract( + Addr::unchecked(USER_TWO), + dca, + &ExecuteMsg::PerformDcaPurchase { + id: 0, + hops: vec![SwapOperation::AstroSwap { + offer_asset_info: native_info(USDC), + ask_asset_info: native_info(LUNA), + }], + }, + &[], + ) + .unwrap_err(); + + assert_eq!( + err.downcast::()?, + ContractError::PurchaseTooEarly {} + ); + + Ok(()) +} + +#[test] +fn purchase_hops_not_empty() -> Result<(), Box> { + let (mut app, dca) = instantiate(); + + app.execute_contract( + Addr::unchecked(USER_ONE), + dca.clone(), + &ExecuteMsg::CreateDcaOrder { + initial_asset: native_asset(USDC, 50_000_000), + target_asset: native_info(LUNA), + interval: 600, + dca_amount: Uint128::new(10_000_000), + start_at: None, + config_override: None, + }, + &[Coin::new(50_000_000, USDC)], + )?; + + let err = app + .execute_contract( + Addr::unchecked(USER_TWO), + dca, + &ExecuteMsg::PerformDcaPurchase { + id: 0, + hops: vec![], + }, + &[], + ) + .unwrap_err(); + + assert_eq!( + err.downcast::()?, + ContractError::EmptyHopRoute {} + ); + + Ok(()) +} + +#[test] +fn purchase_not_exceed_max_hops() -> Result<(), Box> { + let (mut app, dca) = instantiate(); + + app.execute_contract( + Addr::unchecked(USER_ONE), + dca.clone(), + &ExecuteMsg::CreateDcaOrder { + initial_asset: native_asset(USDC, 50_000_000), + target_asset: native_info(LUNA), + interval: 600, + dca_amount: Uint128::new(10_000_000), + start_at: None, + config_override: None, + }, + &[Coin::new(50_000_000, USDC)], + )?; + + let err = app + .execute_contract( + Addr::unchecked(USER_TWO), + dca, + &ExecuteMsg::PerformDcaPurchase { + id: 0, + hops: vec![ + SwapOperation::AstroSwap { + offer_asset_info: native_info(USDC), + ask_asset_info: native_info(LUNA), + }, + SwapOperation::AstroSwap { + offer_asset_info: native_info(LUNA), + ask_asset_info: native_info(USDT), + }, + SwapOperation::AstroSwap { + offer_asset_info: native_info(USDT), + ask_asset_info: native_info(USDC), + }, + SwapOperation::AstroSwap { + offer_asset_info: native_info(USDC), + ask_asset_info: native_info(LUNA), + }, + ], + }, + &[], + ) + .unwrap_err(); + + assert!(matches!( + err.downcast::()?, + ContractError::MaxHopsAssertion { .. } + )); + + Ok(()) +} + +#[test] +fn purchase_correct_target_info() -> Result<(), Box> { + let (mut app, dca) = instantiate(); + + app.execute_contract( + Addr::unchecked(USER_ONE), + dca.clone(), + &ExecuteMsg::CreateDcaOrder { + initial_asset: native_asset(USDC, 50_000_000), + target_asset: native_info(LUNA), + interval: 600, + dca_amount: Uint128::new(10_000_000), + start_at: None, + config_override: None, + }, + &[Coin::new(50_000_000, USDC)], + )?; + + let err = app + .execute_contract( + Addr::unchecked(USER_TWO), + dca, + &ExecuteMsg::PerformDcaPurchase { + id: 0, + hops: vec![SwapOperation::AstroSwap { + offer_asset_info: native_info(OSMO), + ask_asset_info: native_info(LUNA), + }], + }, + &[], + ) + .unwrap_err(); + + assert_eq!( + err.downcast::()?, + ContractError::InitialAssetAssertion {} + ); + + Ok(()) +} + +#[test] +fn purchase_correct_initial_info() -> Result<(), Box> { + let (mut app, dca) = instantiate(); + + app.execute_contract( + Addr::unchecked(USER_ONE), + dca.clone(), + &ExecuteMsg::CreateDcaOrder { + initial_asset: native_asset(USDC, 50_000_000), + target_asset: native_info(LUNA), + interval: 600, + dca_amount: Uint128::new(10_000_000), + start_at: None, + config_override: None, + }, + &[Coin::new(50_000_000, USDC)], + )?; + + let err = app + .execute_contract( + Addr::unchecked(USER_TWO), + dca.clone(), + &ExecuteMsg::PerformDcaPurchase { + id: 0, + hops: vec![SwapOperation::AstroSwap { + offer_asset_info: native_info(USDC), + ask_asset_info: native_info(OSMO), + }], + }, + &[], + ) + .unwrap_err(); + + assert_eq!( + err.downcast::()?, + ContractError::TargetAssetAssertion {} + ); + + let err = app + .execute_contract( + Addr::unchecked(USER_TWO), + dca, + &ExecuteMsg::PerformDcaPurchase { + id: 0, + hops: vec![ + SwapOperation::AstroSwap { + offer_asset_info: native_info(USDC), + ask_asset_info: native_info(USDT), + }, + SwapOperation::AstroSwap { + offer_asset_info: native_info(USDT), + ask_asset_info: native_info(OSMO), + }, + ], + }, + &[], + ) + .unwrap_err(); + + assert_eq!( + err.downcast::()?, + ContractError::TargetAssetAssertion {} + ); + + Ok(()) +} + +#[test] +fn purchase_whitelisted_hop_route() -> Result<(), Box> { + let (mut app, dca) = instantiate(); + + app.execute_contract( + Addr::unchecked(USER_ONE), + dca.clone(), + &ExecuteMsg::CreateDcaOrder { + initial_asset: native_asset(USDC, 50_000_000), + target_asset: native_info(LUNA), + interval: 600, + dca_amount: Uint128::new(10_000_000), + start_at: None, + config_override: None, + }, + &[Coin::new(50_000_000, USDC)], + )?; + + let err = app + .execute_contract( + Addr::unchecked(USER_TWO), + dca, + &ExecuteMsg::PerformDcaPurchase { + id: 0, + hops: vec![ + SwapOperation::AstroSwap { + offer_asset_info: native_info(USDC), + ask_asset_info: native_info(OSMO), + }, + SwapOperation::AstroSwap { + offer_asset_info: native_info(OSMO), + ask_asset_info: native_info(LUNA), + }, + ], + }, + &[], + ) + .unwrap_err(); + + assert!(matches!( + err.downcast::()?, + ContractError::InvalidHopRoute { .. } + )); + + Ok(()) +} + +#[test] +fn purchase_insuf_tips_bal() -> Result<(), Box> { + let (mut app, dca) = instantiate(); + + app.execute_contract( + Addr::unchecked(USER_ONE), + dca.clone(), + &ExecuteMsg::CreateDcaOrder { + initial_asset: native_asset(USDC, 1_000_000), + target_asset: native_info(LUNA), + interval: 600, + dca_amount: Uint128::new(1_000_000), + start_at: None, + config_override: None, + }, + &[Coin::new(1_000_000, USDC)], + )?; + + proceed(&mut app, 100); + + let err = app + .execute_contract( + Addr::unchecked(USER_TWO), + dca, + &ExecuteMsg::PerformDcaPurchase { + id: 0, + hops: vec![SwapOperation::AstroSwap { + offer_asset_info: native_info(USDC), + ask_asset_info: native_info(LUNA), + }], + }, + &[], + ) + .unwrap_err(); + + assert_eq!( + err.downcast::()?, + ContractError::InsufficientTipBalance {} + ); + + Ok(()) +} + +#[test] +fn purchase_insuf_bal() -> Result<(), Box> { + let (mut app, dca) = instantiate(); + + app.execute_contract( + Addr::unchecked(USER_ONE), + dca.clone(), + &ExecuteMsg::AddTips {}, + &[Coin::new(10_000_000, USDC)], + )?; + + app.execute_contract( + Addr::unchecked(USER_ONE), + dca.clone(), + &ExecuteMsg::CreateDcaOrder { + initial_asset: native_asset(USDC, 1_000_000), + target_asset: native_info(LUNA), + interval: 600, + dca_amount: Uint128::new(1_000_000), + start_at: None, + config_override: None, + }, + &[Coin::new(1_000_000, USDC)], + )?; + + proceed(&mut app, 100); + + app.execute_contract( + Addr::unchecked(USER_TWO), + dca.clone(), + &ExecuteMsg::PerformDcaPurchase { + id: 0, + hops: vec![SwapOperation::AstroSwap { + offer_asset_info: native_info(USDC), + ask_asset_info: native_info(LUNA), + }], + }, + &[], + )?; + + proceed(&mut app, 100); + + let err = app + .execute_contract( + Addr::unchecked(USER_TWO), + dca, + &ExecuteMsg::PerformDcaPurchase { + id: 0, + hops: vec![SwapOperation::AstroSwap { + offer_asset_info: native_info(USDC), + ask_asset_info: native_info(LUNA), + }], + }, + &[], + ) + .unwrap_err(); + + assert_eq!( + err.downcast::()?, + ContractError::InsufficientBalance {} + ); + + Ok(()) +} + +#[test] +fn purchase_works() -> Result<(), Box> { + let (mut app, dca) = instantiate(); + + app.execute_contract( + Addr::unchecked(USER_ONE), + dca.clone(), + &ExecuteMsg::AddTips {}, + &[Coin::new(1_000_000, USDC)], + )?; + + app.execute_contract( + Addr::unchecked(USER_ONE), + dca.clone(), + &ExecuteMsg::CreateDcaOrder { + initial_asset: native_asset(USDC, 1_000_000), + target_asset: native_info(LUNA), + interval: 600, + dca_amount: Uint128::new(1_000_000), + start_at: None, + config_override: None, + }, + &[Coin::new(1_000_000, USDC)], + )?; + + proceed(&mut app, 100); + + app.execute_contract( + Addr::unchecked(USER_TWO), + dca.clone(), + &ExecuteMsg::PerformDcaPurchase { + id: 0, + hops: vec![SwapOperation::AstroSwap { + offer_asset_info: native_info(USDC), + ask_asset_info: native_info(LUNA), + }], + }, + &[], + )?; + + let balance = app.wrap().query_balance(USER_TWO, USDC)?.amount.u128(); + assert_eq!(balance, 1_000_000); + + let UserConfig { tips_balance, .. } = app.wrap().query_wasm_smart( + &dca, + &QueryMsg::UserConfig { + user: USER_ONE.to_owned(), + }, + )?; + assert_eq!(tips_balance, vec![]); + + let orders: Vec = app.wrap().query_wasm_smart( + &dca, + &QueryMsg::UserDcaOrders { + user: USER_ONE.to_owned(), + }, + )?; + let UserDcaInfo { + info: + DcaInfo { + ref initial_asset, + last_purchase, + .. + }, + .. + } = orders[0]; + assert_eq!(initial_asset, &native_asset(USDC, 0)); + assert_eq!(last_purchase, 601); + + Ok(()) +} + +#[test] +fn purchase_multiple_tips_first_insuf_works() -> Result<(), Box> { + let (mut app, dca) = instantiate(); + + app.execute_contract( + Addr::unchecked(USER_ONE), + dca.clone(), + &ExecuteMsg::AddTips {}, + &[Coin::new(500_000, USDC), Coin::new(2_000_000, USDT)], + )?; + + app.execute_contract( + Addr::unchecked(USER_ONE), + dca.clone(), + &ExecuteMsg::CreateDcaOrder { + initial_asset: native_asset(USDC, 1_000_000), + target_asset: native_info(LUNA), + interval: 600, + dca_amount: Uint128::new(1_000_000), + start_at: None, + config_override: None, + }, + &[Coin::new(1_000_000, USDC)], + )?; + + proceed(&mut app, 100); + + app.execute_contract( + Addr::unchecked(USER_TWO), + dca.clone(), + &ExecuteMsg::PerformDcaPurchase { + id: 0, + hops: vec![SwapOperation::AstroSwap { + offer_asset_info: native_info(USDC), + ask_asset_info: native_info(LUNA), + }], + }, + &[], + )?; + + let balance = app.wrap().query_balance(USER_TWO, USDT)?.amount.u128(); + assert_eq!(balance, 1_000_000); + + let UserConfig { tips_balance, .. } = app.wrap().query_wasm_smart( + &dca, + &QueryMsg::UserConfig { + user: USER_ONE.to_owned(), + }, + )?; + assert_eq!( + tips_balance, + vec![native_asset(USDC, 500_000), native_asset(USDT, 1_000_000)] + ); + + Ok(()) +} + +#[test] +fn purchase_respect_config_override() -> Result<(), Box> { + let (mut app, dca) = instantiate(); + + app.execute_contract( + Addr::unchecked(USER_ONE), + dca.clone(), + &ExecuteMsg::AddTips {}, + &[Coin::new(500_000, USDC), Coin::new(2_000_000, USDT)], + )?; + + app.execute_contract( + Addr::unchecked(USER_ONE), + dca.clone(), + &ExecuteMsg::CreateDcaOrder { + initial_asset: native_asset(USDC, 1_000_000), + target_asset: native_info(LUNA), + interval: 600, + dca_amount: Uint128::new(1_000_000), + start_at: None, + config_override: Some(ConfigOverride { + max_hops: Some(1), + max_spread: None, + }), + }, + &[Coin::new(1_000_000, USDC)], + )?; + + proceed(&mut app, 100); + + let err = app + .execute_contract( + Addr::unchecked(USER_TWO), + dca, + &ExecuteMsg::PerformDcaPurchase { + id: 0, + hops: vec![ + SwapOperation::AstroSwap { + offer_asset_info: native_info(USDC), + ask_asset_info: native_info(USDT), + }, + SwapOperation::AstroSwap { + offer_asset_info: native_info(USDT), + ask_asset_info: native_info(LUNA), + }, + ], + }, + &[], + ) + .unwrap_err(); + + assert_eq!( + err.downcast::()?, + ContractError::MaxHopsAssertion { hops: 1 } + ); + + Ok(()) +} diff --git a/contracts/dca/src/tests/purchase_cw20.rs b/contracts/dca/src/tests/purchase_cw20.rs new file mode 100644 index 0000000..6f47dfb --- /dev/null +++ b/contracts/dca/src/tests/purchase_cw20.rs @@ -0,0 +1,167 @@ +use std::error::Error; + +use astroport::{asset::PairInfo, router::SwapOperation}; +use astroport_dca::{DcaInfo, ExecuteMsg, QueryMsg, UserConfig, UserDcaInfo}; +use cosmwasm_std::{Addr, Coin, Uint128}; +use cw20::{BalanceResponse, Cw20ExecuteMsg, Cw20QueryMsg}; +use cw_multi_test::{App, Executor}; + +use crate::state::Config; + +use super::common::*; + +fn instantiate_cw20() -> (App, Addr, Addr) { + let (mut app, core) = instantiate(); + let token = new_cw20(&mut app, USER_ONE); + + let Config { factory_addr, .. } = app + .wrap() + .query_wasm_smart(&core, &QueryMsg::Config {}) + .unwrap(); + + app.execute_contract( + Addr::unchecked(USER_ONE), + factory_addr.clone(), + &astroport::factory::ExecuteMsg::CreatePair { + pair_type: astroport::factory::PairType::Xyk {}, + asset_infos: [token_info(&token), native_info(USDT)], + init_params: None, + }, + &[], + ) + .unwrap(); + + let PairInfo { contract_addr, .. } = app + .wrap() + .query_wasm_smart( + &factory_addr, + &astroport::factory::QueryMsg::Pair { + asset_infos: [token_info(&token), native_info(USDT)], + }, + ) + .unwrap(); + + app.execute_contract( + Addr::unchecked(USER_ONE), + token.clone(), + &Cw20ExecuteMsg::IncreaseAllowance { + spender: contract_addr.to_string(), + amount: Uint128::new(1_000_000_000), + expires: None, + }, + &[], + ) + .unwrap(); + + app.execute_contract( + Addr::unchecked(USER_ONE), + contract_addr, + &astroport::pair::ExecuteMsg::ProvideLiquidity { + assets: [ + token_asset(&token, 1_000_000_000), + native_asset(USDT, 1_000_000_000), + ], + slippage_tolerance: None, + auto_stake: None, + receiver: None, + }, + &[Coin::new(1_000_000_000, USDT)], + ) + .unwrap(); + + (app, core, token) +} + +#[test] +fn purchase_cw20_works() -> Result<(), Box> { + let (mut app, dca, token) = instantiate_cw20(); + + app.execute_contract( + Addr::unchecked(USER_ONE), + dca.clone(), + &ExecuteMsg::AddTips {}, + &[Coin::new(2_000_000, USDC)], + )?; + + app.execute_contract( + Addr::unchecked(USER_ONE), + dca.clone(), + &ExecuteMsg::CreateDcaOrder { + initial_asset: native_asset(USDC, 1_000_000), + target_asset: token_info(&token), + interval: 600, + dca_amount: Uint128::new(1_000_000), + start_at: None, + config_override: None, + }, + &[Coin::new(1_000_000, USDC)], + )?; + + proceed(&mut app, 100); + + let BalanceResponse { + balance: balance_before, + } = app.wrap().query_wasm_smart( + &token, + &Cw20QueryMsg::Balance { + address: USER_ONE.to_string(), + }, + )?; + + app.execute_contract( + Addr::unchecked(USER_TWO), + dca.clone(), + &ExecuteMsg::PerformDcaPurchase { + id: 0, + hops: vec![ + SwapOperation::AstroSwap { + offer_asset_info: native_info(USDC), + ask_asset_info: native_info(USDT), + }, + SwapOperation::AstroSwap { + offer_asset_info: native_info(USDT), + ask_asset_info: token_info(&token), + }, + ], + }, + &[], + )?; + + let BalanceResponse { + balance: balance_after, + } = app.wrap().query_wasm_smart( + &token, + &Cw20QueryMsg::Balance { + address: USER_ONE.to_string(), + }, + )?; + assert_eq!((balance_after - balance_before).u128(), 995418); + + let UserConfig { tips_balance, .. } = app.wrap().query_wasm_smart( + &dca, + &QueryMsg::UserConfig { + user: USER_ONE.to_owned(), + }, + )?; + assert_eq!(tips_balance, vec![]); + + let orders: Vec = app.wrap().query_wasm_smart( + &dca, + &QueryMsg::UserDcaOrders { + user: USER_ONE.to_owned(), + }, + )?; + let UserDcaInfo { + info: + DcaInfo { + ref initial_asset, + last_purchase, + .. + }, + .. + } = orders[0]; + assert_eq!(initial_asset, &native_asset(USDC, 0)); + assert_eq!(last_purchase, 601); + + Ok(()) +} diff --git a/contracts/dca/src/tests/tips.rs b/contracts/dca/src/tests/tips.rs new file mode 100644 index 0000000..86cac8f --- /dev/null +++ b/contracts/dca/src/tests/tips.rs @@ -0,0 +1,175 @@ +use std::error::Error; + +use astroport_dca::{ExecuteMsg, QueryMsg, UserConfig}; +use cosmwasm_std::{Addr, Coin}; +use cw_multi_test::Executor; + +use crate::error::ContractError; + +use super::common::*; + +#[test] +fn empty_tips_balance() -> Result<(), Box> { + let (app, dca) = instantiate(); + + let config: UserConfig = app.wrap().query_wasm_smart( + &dca, + &QueryMsg::UserConfig { + user: USER_ONE.to_string(), + }, + )?; + + assert_eq!(config, UserConfig::default()); + assert_eq!(config.tips_balance, vec![]); + + Ok(()) +} + +#[test] +fn add_tips_not_empty() -> Result<(), Box> { + let (mut app, dca) = instantiate(); + + let err = app + .execute_contract(Addr::unchecked(USER_ONE), dca, &ExecuteMsg::AddTips {}, &[]) + .unwrap_err(); + + assert_eq!( + err.downcast::()?, + ContractError::InvalidZeroAmount {} + ); + + Ok(()) +} + +#[test] +fn only_add_tips_denom() -> Result<(), Box> { + let (mut app, dca) = instantiate(); + + let err = app + .execute_contract( + Addr::unchecked(USER_ONE), + dca, + &ExecuteMsg::AddTips {}, + &[Coin::new(1_000, OSMO)], + ) + .unwrap_err(); + + assert_eq!( + err.downcast::()?, + ContractError::InvalidTipAssetInfo {} + ); + + Ok(()) +} + +#[test] +fn add_tips_works() -> Result<(), Box> { + let (mut app, dca) = instantiate(); + + app.execute_contract( + Addr::unchecked(USER_ONE), + dca.clone(), + &ExecuteMsg::AddTips {}, + &[Coin::new(1_000, USDT)], + )?; + + let config: UserConfig = app.wrap().query_wasm_smart( + &dca, + &QueryMsg::UserConfig { + user: USER_ONE.to_string(), + }, + )?; + + assert_eq!(config.tips_balance, vec![native_asset(USDT, 1_000)]); + + Ok(()) +} + +#[test] +fn withdraw_tips_works() -> Result<(), Box> { + let (mut app, dca) = instantiate(); + + app.execute_contract( + Addr::unchecked(USER_ONE), + dca.clone(), + &ExecuteMsg::AddTips {}, + &[Coin::new(1_000, USDT), Coin::new(1_000, USDC)], + )?; + + app.execute_contract( + Addr::unchecked(USER_ONE), + dca.clone(), + &ExecuteMsg::WithdrawTips { tips: vec![] }, + &[], + )?; + + let bal_before = app + .wrap() + .query_all_balances(USER_ONE)? + .iter() + .filter(|e| e.denom == USDT || e.denom == USDC) + .map(|e| e.amount.u128()) + .collect::>(); + + assert_eq!(bal_before, vec![999_999_000, 1_999_999_000]); + + app.execute_contract( + Addr::unchecked(USER_ONE), + dca.clone(), + &ExecuteMsg::WithdrawTips { + tips: vec![native_asset(USDT, 500), native_asset(USDC, 1_000)], + }, + &[], + )?; + + let bal_after = app + .wrap() + .query_all_balances(USER_ONE)? + .iter() + .filter(|e| e.denom == USDT || e.denom == USDC) + .map(|e| e.amount.u128()) + .collect::>(); + + assert_eq!(bal_after, vec![1_000_000_000, 1_999_999_500]); + + let config: UserConfig = app.wrap().query_wasm_smart( + &dca, + &QueryMsg::UserConfig { + user: USER_ONE.to_string(), + }, + )?; + + assert_eq!(config.tips_balance, vec![native_asset(USDT, 500)]); + + Ok(()) +} + +#[test] +fn withdraw_tips_insuff_bal() -> Result<(), Box> { + let (mut app, dca) = instantiate(); + + app.execute_contract( + Addr::unchecked(USER_ONE), + dca.clone(), + &ExecuteMsg::AddTips {}, + &[Coin::new(1_000, USDT)], + )?; + + let err = app + .execute_contract( + Addr::unchecked(USER_ONE), + dca, + &ExecuteMsg::WithdrawTips { + tips: vec![native_asset(USDT, 2_000)], + }, + &[], + ) + .unwrap_err(); + + assert_eq!( + err.downcast::()?, + ContractError::InsufficientTipWithdrawBalance {} + ); + + Ok(()) +} diff --git a/contracts/dca/src/tests/user_config.rs b/contracts/dca/src/tests/user_config.rs new file mode 100644 index 0000000..9058d17 --- /dev/null +++ b/contracts/dca/src/tests/user_config.rs @@ -0,0 +1,38 @@ +use std::error::Error; + +use astroport_dca::{ExecuteMsg, QueryMsg, UserConfig}; +use cosmwasm_std::{Addr, Decimal}; +use cw_multi_test::Executor; + +use super::common::*; + +#[test] +fn update_user_config_works() -> Result<(), Box> { + let (mut app, dca) = instantiate(); + + app.execute_contract( + Addr::unchecked(USER_ONE), + dca.clone(), + &ExecuteMsg::UpdateUserConfig { + max_hops: Some(1), + max_spread: Some(Decimal::percent(2)), + }, + &[], + )?; + + let UserConfig { + max_hops, + max_spread, + .. + } = app.wrap().query_wasm_smart( + &dca, + &QueryMsg::UserConfig { + user: USER_ONE.to_string(), + }, + )?; + + assert_eq!(max_hops, Some(1)); + assert_eq!(max_spread, Some(Decimal::percent(2))); + + Ok(()) +} diff --git a/packages/astroport-dca/Cargo.toml b/packages/astroport-dca/Cargo.toml index 9443123..917e27e 100644 --- a/packages/astroport-dca/Cargo.toml +++ b/packages/astroport-dca/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "astroport-dca" -version = "1.0.0" -authors = ["Astroport", "Kaimen Sano"] +version = "2.0.0" +authors = ["Astroport", "Kaimen Sano", "yoisha <48324733+y-pakorn@users.noreply.github.com>"] edition = "2018" repository = "https://github.com/astroport-fi/astroport-dca" homepage = "https://astroport.fi" @@ -14,14 +14,8 @@ homepage = "https://astroport.fi" backtraces = ["cosmwasm-std/backtraces"] [dependencies] -cw20 = { version = "0.8" } -terra-cosmwasm = { version = "2.2.0" } -cosmwasm-std = { version = "0.16.0", features = ["iterator"] } +cw20 = "0.14.0" +cosmwasm-std = "1.0.0" schemars = "0.8.1" serde = { version = "1.0.103", default-features = false, features = ["derive"] } -uint = "0.9.1" -regex = "1.5" -astroport = {git = "https://github.com/astroport-fi/astroport-core.git", branch = "main", package = "astroport"} - -[dev-dependencies] -cosmwasm-schema = "0.16.0" +astroport = { version = "2.0.0", git = "https://github.com/astroport-fi/astroport-core.git" } diff --git a/packages/astroport-dca/src/dca.rs b/packages/astroport-dca/src/dca.rs index 46ef7cd..40da296 100644 --- a/packages/astroport-dca/src/dca.rs +++ b/packages/astroport-dca/src/dca.rs @@ -1,16 +1,18 @@ +use core::fmt; + +use astroport::asset::{Asset, AssetInfo}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use astroport::{ - asset::{Asset, AssetInfo}, - router::SwapOperation, -}; - -use cosmwasm_std::{Decimal, Uint128}; +use cosmwasm_std::{Addr, Decimal, Uint128}; /// Describes information about a DCA order #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct DcaInfo { + /// Unique id of this DCA purchases + pub id: u64, + /// Owner of this DCA purchases + pub owner: Addr, /// The starting asset deposited by the user, with the amount representing the users deposited /// amount of the token pub initial_asset: Asset, @@ -22,107 +24,30 @@ pub struct DcaInfo { pub last_purchase: u64, /// The amount of `initial_asset` to spend each DCA purchase pub dca_amount: Uint128, + /// Config to override user's `max_hops` and `max_spread`, if this is [None], will use global user config instead + pub config_override: ConfigOverride, } -/// Describes the parameters used for creating a contract -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct InstantiateMsg { - /// The maximum amount of hops to perform from `initial_asset` to `target_asset` when DCAing if - /// the user does not specify a custom max hop amount - pub max_hops: u32, - /// The fee a user must pay per hop performed in a DCA purchase - pub per_hop_fee: Uint128, - /// The whitelisted tokens that can be used in a DCA hop route - pub whitelisted_tokens: Vec, - /// The maximum amount of spread - pub max_spread: String, - /// The address of the Astroport factory contract - pub factory_addr: String, - /// The address of the Astroport router contract - pub router_addr: String, +#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, JsonSchema, Default)] +pub struct ConfigOverride { + /// Maximum hops to perform, if this is [None], will use global user config instead + pub max_hops: Option, + /// Maximum spread to perform, if this is [None], will use global user config instead + pub max_spread: Option, } -/// This structure describes the execute messages available in the contract -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum ExecuteMsg { - /// Add uusd top-up for bots to perform DCA requests - AddBotTip {}, - /// Cancels a DCA order, returning any native asset back to the user - CancelDcaOrder { initial_asset: AssetInfo }, - /// Creates a new DCA order where `dca_amount` of token `initial_asset` will purchase - /// `target_asset` every `interval` - /// - /// If `initial_asset` is a Cw20 token, the user needs to have increased the allowance prior to - /// calling this execution - CreateDcaOrder { - initial_asset: Asset, - target_asset: AssetInfo, - interval: u64, - dca_amount: Uint128, - }, - /// Modifies an existing DCA order, allowing the user to change certain parameters - ModifyDcaOrder { - old_initial_asset: AssetInfo, - new_initial_asset: Asset, - new_target_asset: AssetInfo, - new_interval: u64, - new_dca_amount: Uint128, - should_reset_purchase_time: bool, - }, - /// Performs a DCA purchase for a specified user given a hop route - PerformDcaPurchase { - user: String, - hops: Vec, - }, - /// Updates the configuration of the contract - UpdateConfig { - /// The new maximum amount of hops to perform from `initial_asset` to `target_asset` when - /// performing DCA purchases if the user does not specify a custom max hop amount - max_hops: Option, - /// The new fee a user must pay per hop performed in a DCA purchase - per_hop_fee: Option, - /// The new whitelisted tokens that can be used in a DCA hop route - whitelisted_tokens: Option>, - /// The new maximum spread for DCA purchases - max_spread: Option, - }, - /// Update the configuration for a user - UpdateUserConfig { - /// The maximum amount of hops per swap - max_hops: Option, - /// The maximum spread per token when performing DCA purchases - max_spread: Option, - }, - /// Withdraws a users bot tip from the contract. - Withdraw { tip: Uint128 }, +impl fmt::Display for ConfigOverride { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}/{:?}", self.max_hops, self.max_spread) + } } -/// This structure describes the query messages available in the contract -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum QueryMsg { - /// Returns information about the users current active DCA orders in a [`Vec`] object. - UserDcaOrders { user: String }, - /// Returns information about the contract configuration in a [`Config`] object. - Config {}, - /// Returns the users current configuration as a [`UserConfig`] object. - UserConfig { user: String }, -} - -/// This structure describes a migration message. -/// We currently take no arguments for migrations. -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct MigrateMsg {} +//#[test] +//fn test() { +//let g = ConfigOverride { +//max_spread: None, +//max_hops: Some(3), +//}; -/// Describes information for a UserDcaOrders query -/// -/// Contains both the user DCA order and the cw20 token allowance, or, if the initial asset is a -/// native token, the balance. -/// -/// This is useful for bots and front-end to distinguish between a users token allowance (which may -/// have changed) for the DCA contract, and the created DCA order size. -pub struct DcaQueryInfo { - token_allowance: Uint128, - info: DcaInfo, -} +//println!("{}", g); +//} diff --git a/packages/astroport-dca/src/lib.rs b/packages/astroport-dca/src/lib.rs index 5c4b1e6..d864868 100644 --- a/packages/astroport-dca/src/lib.rs +++ b/packages/astroport-dca/src/lib.rs @@ -1 +1,7 @@ -pub mod dca; +mod dca; +mod msg; +mod user_config; + +pub use dca::{ConfigOverride, DcaInfo}; +pub use msg::*; +pub use user_config::UserConfig; diff --git a/packages/astroport-dca/src/msg.rs b/packages/astroport-dca/src/msg.rs new file mode 100644 index 0000000..d3ce953 --- /dev/null +++ b/packages/astroport-dca/src/msg.rs @@ -0,0 +1,135 @@ +use astroport::{ + asset::{Asset, AssetInfo}, + router::SwapOperation, +}; +use cosmwasm_std::{Decimal, Uint128}; +use cw20::Cw20ReceiveMsg; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::{ConfigOverride, DcaInfo}; + +/// Describes the parameters used for creating a contract +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct InstantiateMsg { + /// The maximum amount of hops to perform from `initial_asset` to `target_asset` when DCAing if + /// the user does not specify a custom max hop amount + pub max_hops: u32, + /// The whitelisted tokens that can be used in a DCA hop route + pub whitelisted_tokens: Vec, + /// The maximum amount of spread + pub max_spread: String, + /// The address of the Astroport factory contract + pub factory_addr: String, + /// The address of the Astroport router contract + pub router_addr: String, + /// The allowed tips denom and amount + pub tips: Vec, +} + +/// This structure describes the execute messages available in the contract +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ExecuteMsg { + Receive(Cw20ReceiveMsg), + /// Add top-up for bots to perform DCA requests + AddTips {}, + /// Withdraws a users bot tip from the contract. + WithdrawTips { + tips: Vec, + }, + /// Cancels a DCA order, returning any native asset back to the user + CancelDcaOrder { + id: u64, + }, + /// Creates a new DCA order where `dca_amount` of token `initial_asset` will purchase + /// `target_asset` every `interval` + /// + /// If `initial_asset` is a Cw20 token, the user needs to have increased the allowance prior to + /// calling this execution + CreateDcaOrder { + initial_asset: Asset, + target_asset: AssetInfo, + interval: u64, + dca_amount: Uint128, + start_at: Option, + config_override: Option, + }, + /// Modifies an existing DCA order, allowing the user to change certain parameters + ModifyDcaOrder { + id: u64, + initial_amount: Option, + interval: Option, + dca_amount: Option, + config_override: Option, + }, + /// Performs a DCA purchase for a specified user given a hop route + PerformDcaPurchase { + id: u64, + hops: Vec, + }, + /// Updates the configuration of the contract + UpdateConfig { + /// The new maximum amount of hops to perform from `initial_asset` to `target_asset` when + /// performing DCA purchases if the user does not specify a custom max hop amount + max_hops: Option, + /// The new whitelisted tokens that can be used in a DCA hop route + whitelisted_tokens: Option>, + /// The new maximum spread for DCA purchases + max_spread: Option, + /// The new tips denom and amount + tips: Option>, + }, + /// Update the configuration for a user + UpdateUserConfig { + /// The maximum amount of hops per swap + max_hops: Option, + /// The maximum spread per token when performing DCA purchases + max_spread: Option, + }, +} + +/// This structure describes the query messages available in the contract +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum QueryMsg { + /// Returns information about the contract configuration in a [`Config`] object. + Config {}, + /// Returns the current tips denom and amount configuration as a [`Vec`] object. + Tips {}, + /// Returns information about all current active DCA orders in a [`Vec`] object. + AllDcaOrders { + start_after: Option, + limit: Option, + is_ascending: Option, + }, + /// Returns information about the users current active DCA orders in a [`Vec`] object. + UserDcaOrders { user: String }, + /// Returns the users current configuration as a [`UserConfig`] object. + UserConfig { user: String }, +} + +/// This structure describes a migration message. +/// 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 Cw20HookMsg { + /// Add top-up for bots to perform DCA requests + AddBotTips {}, +} + +/// Describes information for a UserDcaOrders query +/// +/// Contains both the user DCA order and the cw20 token allowance, or, if the initial asset is a +/// native token, the balance. +/// +/// This is useful for bots and front-end to distinguish between a users token allowance (which may +/// have changed) for the DCA contract, and the created DCA order size. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct UserDcaInfo { + pub token_allowance: Uint128, + pub info: DcaInfo, +} diff --git a/packages/astroport-dca/src/user_config.rs b/packages/astroport-dca/src/user_config.rs new file mode 100644 index 0000000..986f453 --- /dev/null +++ b/packages/astroport-dca/src/user_config.rs @@ -0,0 +1,15 @@ +use astroport::asset::Asset; +use cosmwasm_std::Decimal; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +/// Stores the users custom configuration +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, Default)] +pub struct UserConfig { + /// An override for the maximum amount of hops to perform from `initial_asset` to `target_asset` when DCAing + pub max_hops: Option, + /// An override for the maximum amount of spread when performing a swap from `initial_asset` to `target_asset` when DCAing + pub max_spread: Option, + /// The amount of tip in diffrent denom the user has deposited for their tips when performing DCA purchases + pub tips_balance: Vec, +}