diff --git a/Cargo.lock b/Cargo.lock index 042d44575..cea5801a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,69 +2,12 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "airdrops-registry" -version = "0.1.0" -source = "git+https://github.com/stader-labs/stader-liquid-token?tag=v0.2.1#acf69c8cf02c7fc2baf39962c68c1555dc4c5916" -dependencies = [ - "cosmwasm-bignumber", - "cosmwasm-std", - "cosmwasm-storage", - "cw-storage-plus 0.8.1", - "cw2 0.9.1", - "cw20 0.8.1", - "schemars", - "serde", - "stader-utils", - "terra-cosmwasm", - "thiserror", -] - -[[package]] -name = "astroport" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b56c6d8a5a14a5f569a3473a962ed1d12bfec2a19ef43584c86aae5667bdeaa8" -dependencies = [ - "cosmwasm-std", - "cw-storage-plus 0.8.1", - "cw20 0.8.1", - "schemars", - "serde", - "terra-cosmwasm", - "uint", -] - [[package]] name = "base64" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" -[[package]] -name = "basset" -version = "0.1.0" -source = "git+https://github.com/lidofinance/lido-terra-contracts?tag=v1.0.2#f6afd33f01f901bfd393465c3257e1558c65c2ad" -dependencies = [ - "cosmwasm-std", - "cosmwasm-storage", - "cw20 0.8.1", - "schemars", - "serde", - "terra-cosmwasm", - "thiserror", -] - -[[package]] -name = "bigint" -version = "4.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0e8c8a600052b52482eff2cf4d810e462fdff1f656ac1ecb6232132a1ed7def" -dependencies = [ - "byteorder", - "crunchy 0.1.6", -] - [[package]] name = "block-buffer" version = "0.9.0" @@ -92,18 +35,6 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d6f2aa4d0537bcc1c74df8755072bd31c1ef1a3a1b85a68e8404a8c353b7b8b" -[[package]] -name = "cosmwasm-bignumber" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce94de6dd2b3d74cd8d9bc2bf5d6208ffed832ad946774ea9ed2a9ef7d95161f" -dependencies = [ - "bigint", - "cosmwasm-std", - "schemars", - "serde", -] - [[package]] name = "cosmwasm-crypto" version = "0.16.7" @@ -126,16 +57,6 @@ dependencies = [ "syn", ] -[[package]] -name = "cosmwasm-schema" -version = "0.16.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021bdefb9d855c5135e83046e5407a9fddba869e42e78b6036b53a606dc8c10" -dependencies = [ - "schemars", - "serde_json", -] - [[package]] name = "cosmwasm-std" version = "0.16.7" @@ -153,16 +74,6 @@ dependencies = [ "uint", ] -[[package]] -name = "cosmwasm-storage" -version = "0.16.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c243d26ba6c49abb5ed69762648a9c664ba263debce425ad10603e7b8aa92ced" -dependencies = [ - "cosmwasm-std", - "serde", -] - [[package]] name = "cpufeatures" version = "0.2.2" @@ -172,12 +83,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crunchy" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2f4a431c5c9f662e1200b7c7f02c34e91361150e382089a8f2dec3ba680cbda" - [[package]] name = "crunchy" version = "0.2.2" @@ -221,36 +126,13 @@ dependencies = [ [[package]] name = "cw-storage-plus" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1e867b9972b83b32e00e878dfbff48299ba26618dabeb19b9c56fae176dc225" -dependencies = [ - "cosmwasm-std", - "schemars", - "serde", -] - -[[package]] -name = "cw-storage-plus" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8e401ed71bd64abb9b91151a9ff4f7b34e81b2b3eceab23e3cb67fe47e39938" -dependencies = [ - "cosmwasm-std", - "schemars", - "serde", -] - -[[package]] -name = "cw0" -version = "0.8.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c497f885a40918a02df7d938c81809965fa05cfc21b3dc591e9950237b5de0a9" +checksum = "140b764eb9aca3fa8b06ce7ce773705058db1b11cc97bb1bac6702c19887894e" dependencies = [ "cosmwasm-std", "schemars", "serde", - "thiserror", ] [[package]] @@ -267,36 +149,12 @@ dependencies = [ [[package]] name = "cw2" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d48454f96494aa1018556cd457977375cc8c57ef3e5c767cfa2ea5ec24b0258" -dependencies = [ - "cosmwasm-std", - "cw-storage-plus 0.8.1", - "schemars", - "serde", -] - -[[package]] -name = "cw2" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6380164fb236412ff43c7ca075d95847c6fa8c51b2d3a513c23127a0f2a8f6" +checksum = "022d9f06ea46f055f0e107c5cf076949f0b1e7befb78acbee42a28f07b6cd636" dependencies = [ "cosmwasm-std", - "cw-storage-plus 0.9.1", - "schemars", - "serde", -] - -[[package]] -name = "cw20" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a11a2adbd52258f5b4ed5323f62bc6e559f2cefbe52ef0e58290016fde5bb083" -dependencies = [ - "cosmwasm-std", - "cw0 0.8.1", + "cw-storage-plus", "schemars", "serde", ] @@ -308,7 +166,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac49b013ca1e355fd988cc1926acc9a16d7fd45cfb595ee330455582a788b100" dependencies = [ "cosmwasm-std", - "cw0 0.9.1", + "cw0", "schemars", "serde", ] @@ -320,10 +178,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b731a291b63cc26d484b159e8469f0965b0082a20e874616f869537b31d64bb" dependencies = [ "cosmwasm-std", - "cw-storage-plus 0.9.1", - "cw0 0.9.1", - "cw2 0.9.1", - "cw20 0.9.1", + "cw-storage-plus", + "cw0", + "cw2", + "cw20", "schemars", "serde", "thiserror", @@ -495,212 +353,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b" [[package]] -name = "mars-address-provider" -version = "1.0.0" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus 0.9.1", - "mars-core", - "terra-cosmwasm", - "thiserror", -] - -[[package]] -name = "mars-core" -version = "1.0.2" -dependencies = [ - "astroport", - "basset", - "cosmwasm-std", - "cw2 0.9.1", - "cw20 0.9.1", - "cw20-base", - "schemars", - "serde", - "staking", - "terra-cosmwasm", - "thiserror", -] - -[[package]] -name = "mars-council" -version = "1.0.0" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus 0.9.1", - "cw20 0.9.1", - "mars-core", - "schemars", - "serde", - "thiserror", -] - -[[package]] -name = "mars-incentives" -version = "1.0.0" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus 0.9.1", - "cw20 0.9.1", - "mars-core", - "schemars", - "serde", - "thiserror", -] - -[[package]] -name = "mars-ma-token" -version = "1.0.0" +name = "mars-outpost" +version = "0.1.0" dependencies = [ - "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 0.9.1", - "cw0 0.9.1", - "cw2 0.9.1", - "cw20 0.9.1", + "cw2", + "cw20", "cw20-base", - "mars-core", - "schemars", - "serde", - "thiserror", -] - -[[package]] -name = "mars-oracle" -version = "1.0.2" -dependencies = [ - "astroport", - "basset", - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus 0.9.1", - "mars-core", "schemars", "serde", - "staking", "terra-cosmwasm", "thiserror", ] -[[package]] -name = "mars-protocol-rewards-collector" -version = "1.0.0" -dependencies = [ - "astroport", - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus 0.9.1", - "cw20 0.9.1", - "cw20-base", - "mars-core", - "schemars", - "serde", - "thiserror", -] - -[[package]] -name = "mars-red-bank" -version = "1.0.0" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus 0.9.1", - "cw20 0.9.1", - "cw20-base", - "mars-core", - "schemars", - "serde", - "thiserror", -] - -[[package]] -name = "mars-red-bank-closure" -version = "1.0.0" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus 0.9.1", - "cw20 0.9.1", - "mars-core", - "mars-red-bank", - "schemars", - "serde", -] - -[[package]] -name = "mars-safety-fund" -version = "1.0.0" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus 0.9.1", - "mars-core", - "schemars", - "serde", -] - -[[package]] -name = "mars-staking" -version = "1.0.0" -dependencies = [ - "astroport", - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus 0.9.1", - "cw20 0.9.1", - "mars-core", - "schemars", - "serde", - "thiserror", -] - -[[package]] -name = "mars-treasury" -version = "1.0.0" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus 0.9.1", - "mars-core", - "schemars", - "serde", -] - -[[package]] -name = "mars-vesting" -version = "1.0.0" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus 0.9.1", - "cw20 0.9.1", - "mars-core", - "schemars", - "serde", - "terra-cosmwasm", - "thiserror", -] - -[[package]] -name = "mars-xmars-token" -version = "1.0.0" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus 0.9.1", - "cw0 0.9.1", - "cw2 0.9.1", - "cw20 0.9.1", - "cw20-base", - "mars-core", - "schemars", - "serde", - "thiserror", -] - [[package]] name = "opaque-debug" version = "0.3.0" @@ -753,23 +418,6 @@ dependencies = [ "getrandom 0.2.6", ] -[[package]] -name = "reward" -version = "0.1.0" -source = "git+https://github.com/stader-labs/stader-liquid-token?tag=v0.2.1#acf69c8cf02c7fc2baf39962c68c1555dc4c5916" -dependencies = [ - "cosmwasm-std", - "cosmwasm-storage", - "cw-storage-plus 0.8.1", - "cw2 0.9.1", - "cw20 0.8.1", - "schemars", - "serde", - "stader-utils", - "terra-cosmwasm", - "thiserror", -] - [[package]] name = "ryu" version = "1.0.9" @@ -802,9 +450,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.136" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" dependencies = [ "serde_derive", ] @@ -820,9 +468,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.136" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" dependencies = [ "proc-macro2", "quote", @@ -883,42 +531,6 @@ dependencies = [ "der", ] -[[package]] -name = "stader-utils" -version = "0.1.0" -source = "git+https://github.com/stader-labs/stader-liquid-token?tag=v0.2.1#acf69c8cf02c7fc2baf39962c68c1555dc4c5916" -dependencies = [ - "cosmwasm-bignumber", - "cosmwasm-std", - "cosmwasm-storage", - "cw-storage-plus 0.8.1", - "cw20 0.8.1", - "schemars", - "serde", - "terra-cosmwasm", - "thiserror", -] - -[[package]] -name = "staking" -version = "0.1.0" -source = "git+https://github.com/stader-labs/stader-liquid-token?tag=v0.2.1#acf69c8cf02c7fc2baf39962c68c1555dc4c5916" -dependencies = [ - "airdrops-registry", - "cosmwasm-std", - "cosmwasm-storage", - "cw-storage-plus 0.8.1", - "cw2 0.8.1", - "cw20 0.9.1", - "cw20-base", - "reward", - "schemars", - "serde", - "stader-utils", - "terra-cosmwasm", - "thiserror", -] - [[package]] name = "static_assertions" version = "1.1.0" @@ -955,18 +567,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.30" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" dependencies = [ "proc-macro2", "quote", @@ -986,7 +598,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" dependencies = [ "byteorder", - "crunchy 0.2.2", + "crunchy", "hex", "static_assertions", ] diff --git a/Cargo.toml b/Cargo.toml index cad9776c2..9e1bb2c5a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,14 +1,14 @@ [workspace] -members = ["contracts/*", "packages/*"] +members = ["packages/*"] +# members = ["contracts/*", "packages/*"] -[profile.release.package.mars-core] +[profile.release] opt-level = 3 debug = false +rpath = false +lto = true debug-assertions = false codegen-units = 1 +panic = 'abort' incremental = false - -[profile.release] -rpath = false -lto = true overflow-checks = true diff --git a/contracts/mars-council/.cargo/config b/contracts/mars-council/.cargo/config deleted file mode 100644 index 336b618a1..000000000 --- a/contracts/mars-council/.cargo/config +++ /dev/null @@ -1,4 +0,0 @@ -[alias] -wasm = "build --release --target wasm32-unknown-unknown" -unit-test = "test --lib" -schema = "run --example schema" diff --git a/contracts/mars-council/Cargo.toml b/contracts/mars-council/Cargo.toml deleted file mode 100644 index 2d769cbff..000000000 --- a/contracts/mars-council/Cargo.toml +++ /dev/null @@ -1,38 +0,0 @@ -[package] -name = "mars-council" -version = "1.0.0" -authors = ["Spike Spiegel "] -edition = "2018" - -exclude = [ - # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. - "contract.wasm", - "hash.txt", -] - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces"] - -[dependencies] -mars-core = { path = "../../packages/mars-core", version = "1.0.0" } - -cw20 = "0.9.0" -cw-storage-plus = "0.9.0" - -cosmwasm-std = "0.16.2" - -schemars = "0.8.1" -serde = { version = "1.0.103", default-features = false, features = ["derive"] } -thiserror = "1.0.23" - -[dev-dependencies] -cosmwasm-schema = "0.16.2" - -[profile.release] -overflow-checks = true diff --git a/contracts/mars-council/README.md b/contracts/mars-council/README.md deleted file mode 100644 index be300c715..000000000 --- a/contracts/mars-council/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Council - -Handles submission, voting and and execution of proposals. As protocol becomes stable, the Council will be the admin of all protocol contracts and have privileges to update most params. - -This contract can be considered a fork of [anchor protocol's gov contract](https://github.com/Anchor-Protocol/anchor-token-contracts/tree/main/contracts/gov) which was modified to meet Mars protocol's needs. diff --git a/contracts/mars-council/examples/schema.rs b/contracts/mars-council/examples/schema.rs deleted file mode 100644 index ce92c4f3a..000000000 --- a/contracts/mars-council/examples/schema.rs +++ /dev/null @@ -1,24 +0,0 @@ -use std::env::current_dir; -use std::fs::create_dir_all; - -use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; - -use mars_council::msg::{ExecuteMsg, InstantiateMsg, QueryMsg, ReceiveMsg}; -use mars_council::{Config, Proposal, ProposalVotesResponse, ProposalsListResponse}; - -fn main() { - let mut out_dir = current_dir().unwrap(); - out_dir.push("schema"); - create_dir_all(&out_dir).unwrap(); - remove_schemas(&out_dir).unwrap(); - - export_schema(&schema_for!(InstantiateMsg), &out_dir); - export_schema(&schema_for!(ExecuteMsg), &out_dir); - export_schema(&schema_for!(ReceiveMsg), &out_dir); - export_schema(&schema_for!(QueryMsg), &out_dir); - - export_schema(&schema_for!(Config), &out_dir); - export_schema(&schema_for!(Proposal), &out_dir); - export_schema(&schema_for!(ProposalsListResponse), &out_dir); - export_schema(&schema_for!(ProposalVotesResponse), &out_dir); -} diff --git a/contracts/mars-council/schema/config.json b/contracts/mars-council/schema/config.json deleted file mode 100644 index 4ec602364..000000000 --- a/contracts/mars-council/schema/config.json +++ /dev/null @@ -1,81 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Config", - "description": "Council global configuration", - "type": "object", - "required": [ - "address_provider_address", - "proposal_effective_delay", - "proposal_expiration_period", - "proposal_required_deposit", - "proposal_required_quorum", - "proposal_required_threshold", - "proposal_voting_period" - ], - "properties": { - "address_provider_address": { - "description": "Address provider returns addresses for all protocol contracts", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] - }, - "proposal_effective_delay": { - "description": "Blocks that need to pass since a proposal succeeds in order for it to be available to be executed", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "proposal_expiration_period": { - "description": "Blocks after the effective_delay during which a successful proposal can be activated before it expires", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "proposal_required_deposit": { - "description": "Number of Mars needed to make a proposal. Will be returned if successful. Will be distributed between stakers if rejected.", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "proposal_required_quorum": { - "description": "% of total voting power required to participate in the proposal in order to consider it successfull", - "allOf": [ - { - "$ref": "#/definitions/Decimal" - } - ] - }, - "proposal_required_threshold": { - "description": "% of for votes required in order to consider the proposal successful", - "allOf": [ - { - "$ref": "#/definitions/Decimal" - } - ] - }, - "proposal_voting_period": { - "description": "Blocks during which a proposal is active since being submitted", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "definitions": { - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/mars-council/schema/execute_msg.json b/contracts/mars-council/schema/execute_msg.json deleted file mode 100644 index f21059fca..000000000 --- a/contracts/mars-council/schema/execute_msg.json +++ /dev/null @@ -1,219 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ExecuteMsg", - "anyOf": [ - { - "description": "Implementation cw20 receive msg", - "type": "object", - "required": [ - "receive" - ], - "properties": { - "receive": { - "$ref": "#/definitions/Cw20ReceiveMsg" - } - }, - "additionalProperties": false - }, - { - "description": "Vote for a proposal", - "type": "object", - "required": [ - "cast_vote" - ], - "properties": { - "cast_vote": { - "type": "object", - "required": [ - "proposal_id", - "vote" - ], - "properties": { - "proposal_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "vote": { - "$ref": "#/definitions/ProposalVoteOption" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "End proposal after voting period has passed", - "type": "object", - "required": [ - "end_proposal" - ], - "properties": { - "end_proposal": { - "type": "object", - "required": [ - "proposal_id" - ], - "properties": { - "proposal_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Execute a successful proposal", - "type": "object", - "required": [ - "execute_proposal" - ], - "properties": { - "execute_proposal": { - "type": "object", - "required": [ - "proposal_id" - ], - "properties": { - "proposal_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Update config", - "type": "object", - "required": [ - "update_config" - ], - "properties": { - "update_config": { - "type": "object", - "required": [ - "config" - ], - "properties": { - "config": { - "$ref": "#/definitions/CreateOrUpdateConfig" - } - } - } - }, - "additionalProperties": false - } - ], - "definitions": { - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", - "type": "string" - }, - "CreateOrUpdateConfig": { - "type": "object", - "properties": { - "address_provider_address": { - "type": [ - "string", - "null" - ] - }, - "proposal_effective_delay": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - }, - "proposal_expiration_period": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - }, - "proposal_required_deposit": { - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - }, - "proposal_required_quorum": { - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - }, - "proposal_required_threshold": { - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - }, - "proposal_voting_period": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - } - } - }, - "Cw20ReceiveMsg": { - "description": "Cw20ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg", - "type": "object", - "required": [ - "amount", - "msg", - "sender" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "msg": { - "$ref": "#/definitions/Binary" - }, - "sender": { - "type": "string" - } - } - }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "ProposalVoteOption": { - "type": "string", - "enum": [ - "for", - "against" - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/mars-council/schema/instantiate_msg.json b/contracts/mars-council/schema/instantiate_msg.json deleted file mode 100644 index 0003eae14..000000000 --- a/contracts/mars-council/schema/instantiate_msg.json +++ /dev/null @@ -1,88 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InstantiateMsg", - "type": "object", - "required": [ - "config" - ], - "properties": { - "config": { - "$ref": "#/definitions/CreateOrUpdateConfig" - } - }, - "definitions": { - "CreateOrUpdateConfig": { - "type": "object", - "properties": { - "address_provider_address": { - "type": [ - "string", - "null" - ] - }, - "proposal_effective_delay": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - }, - "proposal_expiration_period": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - }, - "proposal_required_deposit": { - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - }, - "proposal_required_quorum": { - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - }, - "proposal_required_threshold": { - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - }, - "proposal_voting_period": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - } - } - }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/mars-council/schema/proposal.json b/contracts/mars-council/schema/proposal.json deleted file mode 100644 index 11102b341..000000000 --- a/contracts/mars-council/schema/proposal.json +++ /dev/null @@ -1,441 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Proposal", - "description": "Proposal metadata stored in state", - "type": "object", - "required": [ - "against_votes", - "deposit_amount", - "description", - "end_height", - "for_votes", - "proposal_id", - "start_height", - "status", - "submitter_address", - "title" - ], - "properties": { - "against_votes": { - "description": "Number of against votes", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "deposit_amount": { - "description": "MARS tokens deposited on the proposal submission. Will be returned to submitter if proposal passes and sent to xMars stakers otherwise", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "description": { - "description": "Description for the proposal", - "type": "string" - }, - "end_height": { - "description": "Block at which voting for the porposal ends", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "for_votes": { - "description": "Number of for votes", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "link": { - "description": "Link provided for cases where the proposal description is too large or some other external resource is intended to be associated with the proposal", - "type": [ - "string", - "null" - ] - }, - "messages": { - "description": "Set of messages available to get executed if the proposal passes", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/ProposalMessage" - } - }, - "proposal_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "start_height": { - "description": "Block at which voting for the porposal starts", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "status": { - "description": "Wether the proposal is Active, Passed, Rejected or Executed", - "allOf": [ - { - "$ref": "#/definitions/ProposalStatus" - } - ] - }, - "submitter_address": { - "description": "Address submitting the proposal", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] - }, - "title": { - "description": "Title for the proposal", - "type": "string" - } - }, - "definitions": { - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "BankMsg": { - "description": "The message types of the bank module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto", - "anyOf": [ - { - "description": "Sends native tokens from the contract to the given address.\n\nThis is translated to a [MsgSend](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28). `from_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "send" - ], - "properties": { - "send": { - "type": "object", - "required": [ - "amount", - "to_address" - ], - "properties": { - "amount": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "to_address": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "This will burn the given coins from the contract's account. There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper. Important if a contract controls significant token supply that must be retired.", - "type": "object", - "required": [ - "burn" - ], - "properties": { - "burn": { - "type": "object", - "required": [ - "amount" - ], - "properties": { - "amount": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - } - } - } - }, - "additionalProperties": false - } - ] - }, - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", - "type": "string" - }, - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } - }, - "CosmosMsg_for_Empty": { - "anyOf": [ - { - "type": "object", - "required": [ - "bank" - ], - "properties": { - "bank": { - "$ref": "#/definitions/BankMsg" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "custom" - ], - "properties": { - "custom": { - "$ref": "#/definitions/Empty" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "wasm" - ], - "properties": { - "wasm": { - "$ref": "#/definitions/WasmMsg" - } - }, - "additionalProperties": false - } - ] - }, - "Empty": { - "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", - "type": "object" - }, - "ProposalMessage": { - "description": "Execute call that will be executed by the DAO if the proposal succeeds", - "type": "object", - "required": [ - "execution_order", - "msg" - ], - "properties": { - "execution_order": { - "description": "Determines order of execution lower order will be executed first", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "msg": { - "description": "CosmosMsg that will be executed by the council", - "allOf": [ - { - "$ref": "#/definitions/CosmosMsg_for_Empty" - } - ] - } - } - }, - "ProposalStatus": { - "description": "Proposal Status", - "type": "string", - "enum": [ - "active", - "passed", - "rejected", - "executed" - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - }, - "WasmMsg": { - "description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto", - "anyOf": [ - { - "description": "Dispatches a call to another contract at a known address (with known ABI).\n\nThis is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78). `sender` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "execute" - ], - "properties": { - "execute": { - "type": "object", - "required": [ - "contract_addr", - "funds", - "msg" - ], - "properties": { - "contract_addr": { - "type": "string" - }, - "funds": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "msg": { - "description": "msg is the json-encoded ExecuteMsg struct (as raw Binary)", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Instantiates a new contracts from previously uploaded Wasm code.\n\nThis is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.16.0-alpha1/x/wasm/internal/types/tx.proto#L47-L61). `sender` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "instantiate" - ], - "properties": { - "instantiate": { - "type": "object", - "required": [ - "code_id", - "funds", - "label", - "msg" - ], - "properties": { - "admin": { - "type": [ - "string", - "null" - ] - }, - "code_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "funds": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "label": { - "description": "A human-readbale label for the contract", - "type": "string" - }, - "msg": { - "description": "msg is the JSON-encoded InstantiateMsg struct (as raw Binary)", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to customize behavior.\n\nOnly the contract admin (as defined in wasmd), if any, is able to make this call.\n\nThis is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96). `sender` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "migrate" - ], - "properties": { - "migrate": { - "type": "object", - "required": [ - "contract_addr", - "msg", - "new_code_id" - ], - "properties": { - "contract_addr": { - "type": "string" - }, - "msg": { - "description": "msg is the json-encoded MigrateMsg struct that will be passed to the new code", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] - }, - "new_code_id": { - "description": "the code_id of the new logic to place in the given contract", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Sets a new admin (for migrate) on the given contract. Fails if this contract is not currently admin of the target contract.", - "type": "object", - "required": [ - "update_admin" - ], - "properties": { - "update_admin": { - "type": "object", - "required": [ - "admin", - "contract_addr" - ], - "properties": { - "admin": { - "type": "string" - }, - "contract_addr": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Clears the admin on the given contract, so no more migration possible. Fails if this contract is not currently admin of the target contract.", - "type": "object", - "required": [ - "clear_admin" - ], - "properties": { - "clear_admin": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "type": "string" - } - } - } - }, - "additionalProperties": false - } - ] - } - } -} diff --git a/contracts/mars-council/schema/proposal_votes_response.json b/contracts/mars-council/schema/proposal_votes_response.json deleted file mode 100644 index c70566b5c..000000000 --- a/contracts/mars-council/schema/proposal_votes_response.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ProposalVotesResponse", - "type": "object", - "required": [ - "proposal_id", - "votes" - ], - "properties": { - "proposal_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "votes": { - "type": "array", - "items": { - "$ref": "#/definitions/ProposalVoteResponse" - } - } - }, - "definitions": { - "ProposalVoteOption": { - "type": "string", - "enum": [ - "for", - "against" - ] - }, - "ProposalVoteResponse": { - "type": "object", - "required": [ - "option", - "power", - "voter_address" - ], - "properties": { - "option": { - "$ref": "#/definitions/ProposalVoteOption" - }, - "power": { - "$ref": "#/definitions/Uint128" - }, - "voter_address": { - "type": "string" - } - } - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/mars-council/schema/proposals_list_response.json b/contracts/mars-council/schema/proposals_list_response.json deleted file mode 100644 index ed30ea645..000000000 --- a/contracts/mars-council/schema/proposals_list_response.json +++ /dev/null @@ -1,463 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ProposalsListResponse", - "type": "object", - "required": [ - "proposal_count", - "proposal_list" - ], - "properties": { - "proposal_count": { - "description": "Total proposals submitted", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "proposal_list": { - "description": "List of proposals (paginated by query)", - "type": "array", - "items": { - "$ref": "#/definitions/Proposal" - } - } - }, - "definitions": { - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "BankMsg": { - "description": "The message types of the bank module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto", - "anyOf": [ - { - "description": "Sends native tokens from the contract to the given address.\n\nThis is translated to a [MsgSend](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28). `from_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "send" - ], - "properties": { - "send": { - "type": "object", - "required": [ - "amount", - "to_address" - ], - "properties": { - "amount": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "to_address": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "This will burn the given coins from the contract's account. There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper. Important if a contract controls significant token supply that must be retired.", - "type": "object", - "required": [ - "burn" - ], - "properties": { - "burn": { - "type": "object", - "required": [ - "amount" - ], - "properties": { - "amount": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - } - } - } - }, - "additionalProperties": false - } - ] - }, - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", - "type": "string" - }, - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } - }, - "CosmosMsg_for_Empty": { - "anyOf": [ - { - "type": "object", - "required": [ - "bank" - ], - "properties": { - "bank": { - "$ref": "#/definitions/BankMsg" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "custom" - ], - "properties": { - "custom": { - "$ref": "#/definitions/Empty" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "wasm" - ], - "properties": { - "wasm": { - "$ref": "#/definitions/WasmMsg" - } - }, - "additionalProperties": false - } - ] - }, - "Empty": { - "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", - "type": "object" - }, - "Proposal": { - "description": "Proposal metadata stored in state", - "type": "object", - "required": [ - "against_votes", - "deposit_amount", - "description", - "end_height", - "for_votes", - "proposal_id", - "start_height", - "status", - "submitter_address", - "title" - ], - "properties": { - "against_votes": { - "description": "Number of against votes", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "deposit_amount": { - "description": "MARS tokens deposited on the proposal submission. Will be returned to submitter if proposal passes and sent to xMars stakers otherwise", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "description": { - "description": "Description for the proposal", - "type": "string" - }, - "end_height": { - "description": "Block at which voting for the porposal ends", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "for_votes": { - "description": "Number of for votes", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "link": { - "description": "Link provided for cases where the proposal description is too large or some other external resource is intended to be associated with the proposal", - "type": [ - "string", - "null" - ] - }, - "messages": { - "description": "Set of messages available to get executed if the proposal passes", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/ProposalMessage" - } - }, - "proposal_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "start_height": { - "description": "Block at which voting for the porposal starts", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "status": { - "description": "Wether the proposal is Active, Passed, Rejected or Executed", - "allOf": [ - { - "$ref": "#/definitions/ProposalStatus" - } - ] - }, - "submitter_address": { - "description": "Address submitting the proposal", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] - }, - "title": { - "description": "Title for the proposal", - "type": "string" - } - } - }, - "ProposalMessage": { - "description": "Execute call that will be executed by the DAO if the proposal succeeds", - "type": "object", - "required": [ - "execution_order", - "msg" - ], - "properties": { - "execution_order": { - "description": "Determines order of execution lower order will be executed first", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "msg": { - "description": "CosmosMsg that will be executed by the council", - "allOf": [ - { - "$ref": "#/definitions/CosmosMsg_for_Empty" - } - ] - } - } - }, - "ProposalStatus": { - "description": "Proposal Status", - "type": "string", - "enum": [ - "active", - "passed", - "rejected", - "executed" - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - }, - "WasmMsg": { - "description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto", - "anyOf": [ - { - "description": "Dispatches a call to another contract at a known address (with known ABI).\n\nThis is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78). `sender` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "execute" - ], - "properties": { - "execute": { - "type": "object", - "required": [ - "contract_addr", - "funds", - "msg" - ], - "properties": { - "contract_addr": { - "type": "string" - }, - "funds": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "msg": { - "description": "msg is the json-encoded ExecuteMsg struct (as raw Binary)", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Instantiates a new contracts from previously uploaded Wasm code.\n\nThis is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.16.0-alpha1/x/wasm/internal/types/tx.proto#L47-L61). `sender` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "instantiate" - ], - "properties": { - "instantiate": { - "type": "object", - "required": [ - "code_id", - "funds", - "label", - "msg" - ], - "properties": { - "admin": { - "type": [ - "string", - "null" - ] - }, - "code_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "funds": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "label": { - "description": "A human-readbale label for the contract", - "type": "string" - }, - "msg": { - "description": "msg is the JSON-encoded InstantiateMsg struct (as raw Binary)", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to customize behavior.\n\nOnly the contract admin (as defined in wasmd), if any, is able to make this call.\n\nThis is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96). `sender` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "migrate" - ], - "properties": { - "migrate": { - "type": "object", - "required": [ - "contract_addr", - "msg", - "new_code_id" - ], - "properties": { - "contract_addr": { - "type": "string" - }, - "msg": { - "description": "msg is the json-encoded MigrateMsg struct that will be passed to the new code", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] - }, - "new_code_id": { - "description": "the code_id of the new logic to place in the given contract", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Sets a new admin (for migrate) on the given contract. Fails if this contract is not currently admin of the target contract.", - "type": "object", - "required": [ - "update_admin" - ], - "properties": { - "update_admin": { - "type": "object", - "required": [ - "admin", - "contract_addr" - ], - "properties": { - "admin": { - "type": "string" - }, - "contract_addr": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Clears the admin on the given contract, so no more migration possible. Fails if this contract is not currently admin of the target contract.", - "type": "object", - "required": [ - "clear_admin" - ], - "properties": { - "clear_admin": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "type": "string" - } - } - } - }, - "additionalProperties": false - } - ] - } - } -} diff --git a/contracts/mars-council/schema/query_msg.json b/contracts/mars-council/schema/query_msg.json deleted file mode 100644 index 8bda1ebe3..000000000 --- a/contracts/mars-council/schema/query_msg.json +++ /dev/null @@ -1,106 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryMsg", - "anyOf": [ - { - "type": "object", - "required": [ - "config" - ], - "properties": { - "config": { - "type": "object" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "proposals" - ], - "properties": { - "proposals": { - "type": "object", - "properties": { - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "start": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - } - } - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "proposal" - ], - "properties": { - "proposal": { - "type": "object", - "required": [ - "proposal_id" - ], - "properties": { - "proposal_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "proposal_votes" - ], - "properties": { - "proposal_votes": { - "type": "object", - "required": [ - "proposal_id" - ], - "properties": { - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "proposal_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "start_after": { - "type": [ - "string", - "null" - ] - } - } - } - }, - "additionalProperties": false - } - ] -} diff --git a/contracts/mars-council/schema/receive_msg.json b/contracts/mars-council/schema/receive_msg.json deleted file mode 100644 index 625a3c858..000000000 --- a/contracts/mars-council/schema/receive_msg.json +++ /dev/null @@ -1,371 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ReceiveMsg", - "anyOf": [ - { - "description": "Submit a proposal to be voted Requires a Mars deposit equal or greater than the proposal_required_deposit", - "type": "object", - "required": [ - "submit_proposal" - ], - "properties": { - "submit_proposal": { - "type": "object", - "required": [ - "description", - "title" - ], - "properties": { - "description": { - "type": "string" - }, - "link": { - "type": [ - "string", - "null" - ] - }, - "messages": { - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/ProposalMessage" - } - }, - "title": { - "type": "string" - } - } - } - }, - "additionalProperties": false - } - ], - "definitions": { - "BankMsg": { - "description": "The message types of the bank module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto", - "anyOf": [ - { - "description": "Sends native tokens from the contract to the given address.\n\nThis is translated to a [MsgSend](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28). `from_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "send" - ], - "properties": { - "send": { - "type": "object", - "required": [ - "amount", - "to_address" - ], - "properties": { - "amount": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "to_address": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "This will burn the given coins from the contract's account. There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper. Important if a contract controls significant token supply that must be retired.", - "type": "object", - "required": [ - "burn" - ], - "properties": { - "burn": { - "type": "object", - "required": [ - "amount" - ], - "properties": { - "amount": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - } - } - } - }, - "additionalProperties": false - } - ] - }, - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", - "type": "string" - }, - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } - }, - "CosmosMsg_for_Empty": { - "anyOf": [ - { - "type": "object", - "required": [ - "bank" - ], - "properties": { - "bank": { - "$ref": "#/definitions/BankMsg" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "custom" - ], - "properties": { - "custom": { - "$ref": "#/definitions/Empty" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "wasm" - ], - "properties": { - "wasm": { - "$ref": "#/definitions/WasmMsg" - } - }, - "additionalProperties": false - } - ] - }, - "Empty": { - "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", - "type": "object" - }, - "ProposalMessage": { - "description": "Execute call that will be executed by the DAO if the proposal succeeds", - "type": "object", - "required": [ - "execution_order", - "msg" - ], - "properties": { - "execution_order": { - "description": "Determines order of execution lower order will be executed first", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "msg": { - "description": "CosmosMsg that will be executed by the council", - "allOf": [ - { - "$ref": "#/definitions/CosmosMsg_for_Empty" - } - ] - } - } - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - }, - "WasmMsg": { - "description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto", - "anyOf": [ - { - "description": "Dispatches a call to another contract at a known address (with known ABI).\n\nThis is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78). `sender` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "execute" - ], - "properties": { - "execute": { - "type": "object", - "required": [ - "contract_addr", - "funds", - "msg" - ], - "properties": { - "contract_addr": { - "type": "string" - }, - "funds": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "msg": { - "description": "msg is the json-encoded ExecuteMsg struct (as raw Binary)", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Instantiates a new contracts from previously uploaded Wasm code.\n\nThis is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.16.0-alpha1/x/wasm/internal/types/tx.proto#L47-L61). `sender` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "instantiate" - ], - "properties": { - "instantiate": { - "type": "object", - "required": [ - "code_id", - "funds", - "label", - "msg" - ], - "properties": { - "admin": { - "type": [ - "string", - "null" - ] - }, - "code_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "funds": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "label": { - "description": "A human-readbale label for the contract", - "type": "string" - }, - "msg": { - "description": "msg is the JSON-encoded InstantiateMsg struct (as raw Binary)", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to customize behavior.\n\nOnly the contract admin (as defined in wasmd), if any, is able to make this call.\n\nThis is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96). `sender` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "migrate" - ], - "properties": { - "migrate": { - "type": "object", - "required": [ - "contract_addr", - "msg", - "new_code_id" - ], - "properties": { - "contract_addr": { - "type": "string" - }, - "msg": { - "description": "msg is the json-encoded MigrateMsg struct that will be passed to the new code", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] - }, - "new_code_id": { - "description": "the code_id of the new logic to place in the given contract", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Sets a new admin (for migrate) on the given contract. Fails if this contract is not currently admin of the target contract.", - "type": "object", - "required": [ - "update_admin" - ], - "properties": { - "update_admin": { - "type": "object", - "required": [ - "admin", - "contract_addr" - ], - "properties": { - "admin": { - "type": "string" - }, - "contract_addr": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Clears the admin on the given contract, so no more migration possible. Fails if this contract is not currently admin of the target contract.", - "type": "object", - "required": [ - "clear_admin" - ], - "properties": { - "clear_admin": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "type": "string" - } - } - } - }, - "additionalProperties": false - } - ] - } - } -} diff --git a/contracts/mars-council/src/contract.rs b/contracts/mars-council/src/contract.rs deleted file mode 100644 index 4b67e74ce..000000000 --- a/contracts/mars-council/src/contract.rs +++ /dev/null @@ -1,2212 +0,0 @@ -#[cfg(not(feature = "library"))] -use cosmwasm_std::entry_point; -use cosmwasm_std::{ - attr, from_binary, to_binary, Addr, Binary, CosmosMsg, Deps, DepsMut, Env, MessageInfo, Order, - QuerierWrapper, QueryRequest, Response, StdResult, Uint128, WasmMsg, WasmQuery, -}; -use cw20::{Cw20ExecuteMsg, Cw20ReceiveMsg}; -use cw_storage_plus::{Bound, U64Key}; - -use mars_core::council::error::ContractError; -use mars_core::error::MarsError; -use mars_core::helpers::{option_string_to_addr, zero_address}; -use mars_core::math::decimal::Decimal; - -use mars_core::address_provider; -use mars_core::address_provider::MarsContract; -use mars_core::vesting; -use mars_core::xmars_token; - -use crate::msg::{CreateOrUpdateConfig, ExecuteMsg, InstantiateMsg, QueryMsg, ReceiveMsg}; -use crate::state::{CONFIG, GLOBAL_STATE, PROPOSALS, PROPOSAL_VOTES}; -use crate::{ - Config, GlobalState, Proposal, ProposalMessage, ProposalStatus, ProposalVote, - ProposalVoteOption, ProposalVoteResponse, ProposalVotesResponse, ProposalsListResponse, -}; - -// Proposal validation attributes -const MIN_TITLE_LENGTH: usize = 4; -const MAX_TITLE_LENGTH: usize = 64; -const MIN_DESC_LENGTH: usize = 4; -const MAX_DESC_LENGTH: usize = 1024; -const MIN_LINK_LENGTH: usize = 12; -const MAX_LINK_LENGTH: usize = 128; - -// INSTANTIATE - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn instantiate( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - msg: InstantiateMsg, -) -> Result { - // Destructuring a struct’s fields into separate variables in order to force - // compile error if we add more params - let CreateOrUpdateConfig { - address_provider_address, - proposal_voting_period, - proposal_effective_delay, - proposal_expiration_period, - proposal_required_deposit, - proposal_required_quorum, - proposal_required_threshold, - } = msg.config; - - // Check required fields are available - let available = address_provider_address.is_some() - && proposal_voting_period.is_some() - && proposal_effective_delay.is_some() - && proposal_expiration_period.is_some() - && proposal_required_deposit.is_some() - && proposal_required_quorum.is_some() - && proposal_required_threshold.is_some(); - - if !available { - return Err(MarsError::InstantiateParamsUnavailable {}.into()); - }; - - // initialize Config - let config = Config { - address_provider_address: option_string_to_addr( - deps.api, - address_provider_address, - zero_address(), - )?, - proposal_voting_period: proposal_voting_period.unwrap(), - proposal_effective_delay: proposal_effective_delay.unwrap(), - proposal_expiration_period: proposal_expiration_period.unwrap(), - proposal_required_deposit: proposal_required_deposit.unwrap(), - proposal_required_quorum: proposal_required_quorum.unwrap(), - proposal_required_threshold: proposal_required_threshold.unwrap(), - }; - - // Validate config - config.validate()?; - - CONFIG.save(deps.storage, &config)?; - - // initialize State - GLOBAL_STATE.save(deps.storage, &GlobalState { proposal_count: 0 })?; - - // Prepare response, should instantiate Mars and use the Register hook - Ok(Response::default()) -} - -// EXECUTE - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn execute( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: ExecuteMsg, -) -> Result { - match msg { - ExecuteMsg::Receive(cw20_msg) => execute_receive_cw20(deps, env, info, cw20_msg), - - ExecuteMsg::CastVote { proposal_id, vote } => { - execute_cast_vote(deps, env, info, proposal_id, vote) - } - - ExecuteMsg::EndProposal { proposal_id } => { - execute_end_proposal(deps, env, info, proposal_id) - } - - ExecuteMsg::ExecuteProposal { proposal_id } => { - execute_execute_proposal(deps, env, info, proposal_id) - } - - ExecuteMsg::UpdateConfig { config } => execute_update_config(deps, env, info, config), - } -} - -/// cw20 receive implementation -pub fn execute_receive_cw20( - deps: DepsMut, - env: Env, - info: MessageInfo, - cw20_msg: Cw20ReceiveMsg, -) -> Result { - match from_binary(&cw20_msg.msg)? { - ReceiveMsg::SubmitProposal { - title, - description, - link, - messages, - } => execute_submit_proposal( - deps, - env, - info, - cw20_msg.sender, - cw20_msg.amount, - title, - description, - link, - messages, - ), - } -} - -pub fn execute_submit_proposal( - deps: DepsMut, - env: Env, - info: MessageInfo, - submitter_address_unchecked: String, - deposit_amount: Uint128, - title: String, - description: String, - option_link: Option, - option_messages: Option>, -) -> Result { - // Validate title - if title.len() < MIN_TITLE_LENGTH { - return Err(ContractError::invalid_proposal("title too short")); - } - if title.len() > MAX_TITLE_LENGTH { - return Err(ContractError::invalid_proposal("title too long")); - } - - // Validate description - if description.len() < MIN_DESC_LENGTH { - return Err(ContractError::invalid_proposal("description too short")); - } - if description.len() > MAX_DESC_LENGTH { - return Err(ContractError::invalid_proposal("description too long")); - } - - // Validate Link - if let Some(link) = &option_link { - if link.len() < MIN_LINK_LENGTH { - return Err(ContractError::invalid_proposal("Link too short")); - } - if link.len() > MAX_LINK_LENGTH { - return Err(ContractError::invalid_proposal("Link too long")); - } - } - - let config = CONFIG.load(deps.storage)?; - let mars_token_address = address_provider::helpers::query_address( - &deps.querier, - config.address_provider_address, - MarsContract::MarsToken, - )?; - - let is_mars = info.sender == mars_token_address; - // Validate deposit amount - if (deposit_amount < config.proposal_required_deposit) || !is_mars { - return Err(ContractError::invalid_proposal(format!( - "Must deposit at least {} Mars tokens", - config.proposal_required_deposit - ))); - } - - // Update proposal totals - let mut global_state = GLOBAL_STATE.load(deps.storage)?; - global_state.proposal_count += 1; - GLOBAL_STATE.save(deps.storage, &global_state)?; - - let new_proposal = Proposal { - proposal_id: global_state.proposal_count, - submitter_address: deps.api.addr_validate(&submitter_address_unchecked)?, - status: ProposalStatus::Active, - for_votes: Uint128::zero(), - against_votes: Uint128::zero(), - start_height: env.block.height, - end_height: env.block.height + config.proposal_voting_period, - title, - description, - link: option_link, - messages: option_messages, - deposit_amount, - }; - PROPOSALS.save( - deps.storage, - U64Key::new(global_state.proposal_count), - &new_proposal, - )?; - - let response = Response::new().add_attributes(vec![ - attr("action", "submit_proposal"), - attr("submitter", submitter_address_unchecked), - attr("proposal_id", &global_state.proposal_count.to_string()), - attr("proposal_end_height", &new_proposal.end_height.to_string()), - ]); - - Ok(response) -} - -pub fn execute_cast_vote( - deps: DepsMut, - env: Env, - info: MessageInfo, - proposal_id: u64, - vote_option: ProposalVoteOption, -) -> Result { - let proposal_path = PROPOSALS.key(U64Key::new(proposal_id)); - let mut proposal = proposal_path.load(deps.storage)?; - if proposal.status != ProposalStatus::Active { - return Err(ContractError::ProposalNotActive {}); - } - - if env.block.height > proposal.end_height { - return Err(ContractError::VoteVotingPeriodEnded {}); - } - - let proposal_vote_path = PROPOSAL_VOTES.key((U64Key::new(proposal_id), &info.sender)); - - if proposal_vote_path.may_load(deps.storage)?.is_some() { - return Err(ContractError::VoteUserAlreadyVoted {}); - } - - let config = CONFIG.load(deps.storage)?; - let mars_contracts = vec![MarsContract::XMarsToken, MarsContract::Vesting]; - let mut addresses_query = address_provider::helpers::query_addresses( - &deps.querier, - config.address_provider_address, - mars_contracts, - )?; - let vesting_address = addresses_query.pop().unwrap(); - let xmars_token_address = addresses_query.pop().unwrap(); - - let balance_at_block = proposal.start_height - 1; - - // The voting power of a user for a proposal is defined as the sum of two parts: - // - // - Free voting power: the amount of xMARS token in the user's wallet, at the block before the - // proposal was created - // - Locked voting power: the amount of MARS locked in the vesting contract owned by the user, - // at the block before the proposal was created - // - // The reason we can use the amount of MARS (instead of xMARS) for locked voting power is that, - // since vesting allocations can only be created when 1 MARS == 1 xMARS, these MARS tokens would - // have produced the same amount of xMARS if they were staked. - let voting_power_free = xmars_get_balance_at( - &deps.querier, - xmars_token_address, - info.sender.clone(), - balance_at_block, - )?; - let voting_power_locked = vesting_get_voting_power_at( - &deps.querier, - vesting_address, - info.sender.clone(), - balance_at_block, - )?; - let voting_power = voting_power_free + voting_power_locked; - - if voting_power.is_zero() { - return Err(ContractError::VoteNoVotingPower { - block: balance_at_block, - }); - } - - match vote_option { - ProposalVoteOption::For => proposal.for_votes += voting_power, - ProposalVoteOption::Against => proposal.against_votes += voting_power, - }; - - proposal_vote_path.save( - deps.storage, - &ProposalVote { - option: vote_option.clone(), - power: voting_power, - }, - )?; - - proposal_path.save(deps.storage, &proposal)?; - - let response = Response::new().add_attributes(vec![ - attr("action", "cast_vote"), - attr("proposal_id", proposal_id.to_string()), - attr("voter", &info.sender), - attr("vote", vote_option.to_string()), - attr("voting_power", voting_power.to_string()), - ]); - - Ok(response) -} - -pub fn execute_end_proposal( - deps: DepsMut, - env: Env, - _info: MessageInfo, - proposal_id: u64, -) -> Result { - let proposal_path = PROPOSALS.key(U64Key::new(proposal_id)); - let mut proposal = proposal_path.load(deps.storage)?; - - if proposal.status != ProposalStatus::Active { - return Err(ContractError::ProposalNotActive {}); - } - - if env.block.height <= proposal.end_height { - return Err(ContractError::EndProposalVotingPeriodNotEnded {}); - } - - let config = CONFIG.load(deps.storage)?; - let mars_contracts = vec![ - MarsContract::MarsToken, - MarsContract::Staking, - MarsContract::Vesting, - MarsContract::XMarsToken, - ]; - let mut addresses_query = address_provider::helpers::query_addresses( - &deps.querier, - config.address_provider_address, - mars_contracts, - )?; - let xmars_token_address = addresses_query.pop().unwrap(); - let vesting_address = addresses_query.pop().unwrap(); - let staking_address = addresses_query.pop().unwrap(); - let mars_token_address = addresses_query.pop().unwrap(); - - // The total voting power of a proposal is defined as the sum of two parts: - // - // - Free voting power: the total supply of xMARS token at the block before the proposal was - // created - // - Locked voting power: the total amount of MARS token locked in the vesting contract, at the - // block before the proposal was created - // - // The reason we can use the amount of MARS (instead of xMARS) for locked voting power is that, - // since vesting allocations can only be created when 1 MARS == 1 xMARS, these MARS tokens would - // have produced the same amount of xMARS if they were staked. - let total_voting_power_free = xmars_get_total_supply_at( - &deps.querier, - xmars_token_address, - proposal.start_height - 1, - )?; - let total_voting_power_locked = vesting_get_total_voting_power_at( - &deps.querier, - vesting_address, - proposal.start_height - 1, - )?; - let total_voting_power = total_voting_power_free + total_voting_power_locked; - - // Compute proposal quorum and threshold - let for_votes = proposal.for_votes; - let against_votes = proposal.against_votes; - let total_votes = for_votes + against_votes; - - let mut proposal_quorum: Decimal = Decimal::zero(); - let mut proposal_threshold: Decimal = Decimal::zero(); - if total_voting_power > Uint128::zero() { - proposal_quorum = Decimal::from_ratio(total_votes, total_voting_power); - } - if total_votes > Uint128::zero() { - proposal_threshold = Decimal::from_ratio(for_votes, total_votes); - } - - // Determine proposal result - let (new_proposal_status, log_proposal_result, messages) = if proposal_quorum - >= config.proposal_required_quorum - && proposal_threshold > config.proposal_required_threshold - { - // if quorum and threshold are met then proposal passes - // refund deposit amount to submitter - let msg = CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: mars_token_address.into(), - funds: vec![], - msg: to_binary(&Cw20ExecuteMsg::Transfer { - recipient: proposal.submitter_address.to_string(), - amount: proposal.deposit_amount, - })?, - }); - - (ProposalStatus::Passed, "passed", vec![msg]) - } else { - // Else proposal is rejected - let msg = CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: mars_token_address.into(), - msg: to_binary(&Cw20ExecuteMsg::Transfer { - recipient: staking_address.into(), - amount: proposal.deposit_amount, - })?, - funds: vec![], - }); - - (ProposalStatus::Rejected, "rejected", vec![msg]) - }; - - // Update proposal status - proposal.status = new_proposal_status; - proposal_path.save(deps.storage, &proposal)?; - - let response = Response::new() - .add_attributes(vec![ - attr("action", "end_proposal"), - attr("proposal_id", proposal_id.to_string()), - attr("proposal_result", log_proposal_result), - ]) - .add_messages(messages); - - Ok(response) -} - -pub fn execute_execute_proposal( - deps: DepsMut, - env: Env, - _info: MessageInfo, - proposal_id: u64, -) -> Result { - let proposal_path = PROPOSALS.key(U64Key::new(proposal_id)); - let mut proposal = proposal_path.load(deps.storage)?; - - if proposal.status != ProposalStatus::Passed { - return Err(ContractError::ExecuteProposalNotPassed {}); - } - - let config = CONFIG.load(deps.storage)?; - if env.block.height < (proposal.end_height + config.proposal_effective_delay) { - return Err(ContractError::ExecuteProposalDelayNotEnded {}); - } - if env.block.height - > (proposal.end_height - + config.proposal_effective_delay - + config.proposal_expiration_period) - { - return Err(ContractError::ExecuteProposalExpired {}); - } - - proposal.status = ProposalStatus::Executed; - proposal_path.save(deps.storage, &proposal)?; - - let messages = match proposal.messages { - Some(mut messages) => { - messages.sort_by(|a, b| a.execution_order.cmp(&b.execution_order)); - messages.into_iter().map(|message| message.msg).collect() - } - None => vec![], - }; - - let response = Response::new() - .add_attributes(vec![ - attr("action", "execute_proposal"), - attr("proposal_id", proposal_id.to_string()), - ]) - .add_messages(messages); - - Ok(response) -} - -/// Update config -pub fn execute_update_config( - deps: DepsMut, - env: Env, - info: MessageInfo, - new_config: CreateOrUpdateConfig, -) -> Result { - let mut config = CONFIG.load(deps.storage)?; - - // In council, config can be updated only by itself (through an approved proposal) - // instead of by it's owner - if info.sender != env.contract.address { - return Err(MarsError::Unauthorized {}.into()); - } - - // Destructuring a struct’s fields into separate variables in order to force - // compile error if we add more params - let CreateOrUpdateConfig { - address_provider_address, - - proposal_voting_period, - proposal_effective_delay, - proposal_expiration_period, - proposal_required_deposit, - proposal_required_quorum, - proposal_required_threshold, - } = new_config; - - // Update config - config.address_provider_address = option_string_to_addr( - deps.api, - address_provider_address, - config.address_provider_address, - )?; - - config.proposal_voting_period = proposal_voting_period.unwrap_or(config.proposal_voting_period); - config.proposal_effective_delay = - proposal_effective_delay.unwrap_or(config.proposal_effective_delay); - config.proposal_expiration_period = - proposal_expiration_period.unwrap_or(config.proposal_expiration_period); - config.proposal_required_deposit = - proposal_required_deposit.unwrap_or(config.proposal_required_deposit); - config.proposal_required_quorum = - proposal_required_quorum.unwrap_or(config.proposal_required_quorum); - config.proposal_required_threshold = - proposal_required_threshold.unwrap_or(config.proposal_required_threshold); - - // Validate config - config.validate()?; - - CONFIG.save(deps.storage, &config)?; - - let res = Response::new().add_attribute("action", "update_config"); - Ok(res) -} - -// QUERIES - -// Pagination defaults -const PAGINATION_DEFAULT_LIMIT: u32 = 10; -const PAGINATION_MAX_LIMIT: u32 = 30; - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::Config {} => to_binary(&query_config(deps)?), - QueryMsg::Proposals { start, limit } => to_binary(&query_proposals(deps, start, limit)?), - QueryMsg::Proposal { proposal_id } => to_binary(&query_proposal(deps, proposal_id)?), - QueryMsg::ProposalVotes { - proposal_id, - start_after, - limit, - } => to_binary(&query_proposal_votes( - deps, - proposal_id, - start_after, - limit, - )?), - } -} - -fn query_config(deps: Deps) -> StdResult { - let config = CONFIG.load(deps.storage)?; - Ok(config) -} - -fn query_proposals( - deps: Deps, - start_from: Option, - option_limit: Option, -) -> StdResult { - let global_state = GLOBAL_STATE.load(deps.storage)?; - - let option_start = start_from.map(|start| Bound::inclusive(U64Key::new(start))); - let limit = option_limit - .unwrap_or(PAGINATION_DEFAULT_LIMIT) - .min(PAGINATION_MAX_LIMIT) as usize; - - let proposals_list: StdResult> = PROPOSALS - .range(deps.storage, option_start, None, Order::Ascending) - .take(limit) - .map(|item| { - let (_k, v) = item?; - Ok(v) - }) - .collect(); - - Ok(ProposalsListResponse { - proposal_count: global_state.proposal_count, - proposal_list: proposals_list?, - }) -} - -fn query_proposal(deps: Deps, proposal_id: u64) -> StdResult { - let proposal = PROPOSALS.load(deps.storage, U64Key::new(proposal_id))?; - Ok(proposal) -} - -fn query_proposal_votes( - deps: Deps, - proposal_id: u64, - start_after: Option, - option_limit: Option, -) -> StdResult { - let limit = option_limit - .unwrap_or(PAGINATION_DEFAULT_LIMIT) - .min(PAGINATION_MAX_LIMIT) as usize; - let option_start = start_after.map(Bound::exclusive); - - let votes: StdResult> = PROPOSAL_VOTES - .prefix(U64Key::new(proposal_id)) - .range(deps.storage, option_start, None, Order::Ascending) - .take(limit) - .map(|vote| { - let (k, v) = vote?; - let voter_address = String::from_utf8(k)?; - - Ok(ProposalVoteResponse { - voter_address, - option: v.option, - power: v.power, - }) - }) - .collect(); - - Ok(ProposalVotesResponse { - proposal_id, - votes: votes?, - }) -} - -// HELPERS - -fn xmars_get_total_supply_at( - querier: &QuerierWrapper, - xmars_address: Addr, - block: u64, -) -> StdResult { - let query: xmars_token::TotalSupplyResponse = - querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: xmars_address.into(), - msg: to_binary(&xmars_token::msg::QueryMsg::TotalSupplyAt { block })?, - }))?; - - Ok(query.total_supply) -} - -fn xmars_get_balance_at( - querier: &QuerierWrapper, - xmars_address: Addr, - user_address: Addr, - block: u64, -) -> StdResult { - let query: cw20::BalanceResponse = querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: xmars_address.into(), - msg: to_binary(&xmars_token::msg::QueryMsg::BalanceAt { - address: user_address.to_string(), - block, - })?, - }))?; - - Ok(query.balance) -} - -fn vesting_get_total_voting_power_at( - querier: &QuerierWrapper, - vesting_address: Addr, - block: u64, -) -> StdResult { - querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: vesting_address.into(), - msg: to_binary(&vesting::msg::QueryMsg::TotalVotingPowerAt { block })?, - })) -} - -fn vesting_get_voting_power_at( - querier: &QuerierWrapper, - vesting_address: Addr, - user_address: Addr, - block: u64, -) -> StdResult { - querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: vesting_address.into(), - msg: to_binary(&vesting::msg::QueryMsg::VotingPowerAt { - user_address: user_address.to_string(), - block, - })?, - })) -} - -// TESTS - -#[cfg(test)] -mod tests { - use super::*; - use cosmwasm_std::testing::{MockApi, MockStorage, MOCK_CONTRACT_ADDR}; - use cosmwasm_std::{Coin, OwnedDeps, StdError, SubMsg}; - use mars_core::council::MINIMUM_PROPOSAL_REQUIRED_THRESHOLD_PERCENTAGE; - use mars_core::math::decimal::Decimal; - use mars_core::testing::{ - mock_dependencies, mock_env, mock_info, MarsMockQuerier, MockEnvParams, - }; - - use crate::msg::ExecuteMsg::UpdateConfig; - - const TEST_PROPOSAL_VOTING_PERIOD: u64 = 2000; - const TEST_PROPOSAL_EFFECTIVE_DELAY: u64 = 200; - const TEST_PROPOSAL_EXPIRATION_PERIOD: u64 = 300; - const TEST_PROPOSAL_REQUIRED_DEPOSIT: Uint128 = Uint128::new(10000); - - #[test] - fn test_proper_initialization() { - let mut deps = mock_dependencies(&[]); - let env = cosmwasm_std::testing::mock_env(); - let info = mock_info("someone"); - - // init config with empty params - { - let empty_config = CreateOrUpdateConfig { - address_provider_address: None, - - proposal_voting_period: None, - proposal_effective_delay: None, - proposal_expiration_period: None, - proposal_required_deposit: None, - proposal_required_threshold: None, - proposal_required_quorum: None, - }; - let msg = InstantiateMsg { - config: empty_config, - }; - let error_res = instantiate(deps.as_mut(), env.clone(), info.clone(), msg).unwrap_err(); - assert_eq!(error_res, MarsError::InstantiateParamsUnavailable {}.into()); - } - - let init_config = CreateOrUpdateConfig { - address_provider_address: Some(String::from("address_provider")), - proposal_voting_period: Some(1), - proposal_effective_delay: Some(1), - proposal_expiration_period: Some(1), - proposal_required_deposit: Some(Uint128::new(1)), - proposal_required_quorum: Some(Decimal::percent(75)), - proposal_required_threshold: Some(Decimal::percent( - MINIMUM_PROPOSAL_REQUIRED_THRESHOLD_PERCENTAGE, - )), - }; - - // * - // init with invalid params - // * - { - // init with proposal_required_quorum greater than 1 - let config = CreateOrUpdateConfig { - proposal_required_quorum: Some(Decimal::percent(101)), - ..init_config.clone() - }; - let msg = InstantiateMsg { config }; - let error_res = instantiate(deps.as_mut(), env.clone(), info.clone(), msg).unwrap_err(); - assert_eq!( - error_res, - MarsError::InvalidParam { - param_name: "proposal_required_quorum".to_string(), - invalid_value: "1.01".to_string(), - predicate: "<= 1".to_string(), - } - .into() - ); - - // init with proposal_required_threshold less than 50% - let config = CreateOrUpdateConfig { - proposal_required_threshold: Some(Decimal::percent(49)), - ..init_config.clone() - }; - let msg = InstantiateMsg { config }; - let error_res = instantiate(deps.as_mut(), env.clone(), info.clone(), msg).unwrap_err(); - assert_eq!( - error_res, - MarsError::InvalidParam { - param_name: "proposal_required_threshold".to_string(), - invalid_value: "0.49".to_string(), - predicate: ">= 0.5 and <= 1".to_string(), - } - .into() - ); - - // init with proposal_required_threshold greater than 100% - let config = CreateOrUpdateConfig { - proposal_required_threshold: Some(Decimal::percent(101)), - ..init_config.clone() - }; - let msg = InstantiateMsg { config }; - let error_res = instantiate(deps.as_mut(), env.clone(), info.clone(), msg).unwrap_err(); - assert_eq!( - error_res, - MarsError::InvalidParam { - param_name: "proposal_required_threshold".to_string(), - invalid_value: "1.01".to_string(), - predicate: ">= 0.5 and <= 1".to_string(), - } - .into() - ); - } - - // Successful Init - { - let msg = InstantiateMsg { - config: init_config, - }; - let res = instantiate(deps.as_mut(), env, info, msg).unwrap(); - assert_eq!(0, res.messages.len()); - - let config = CONFIG.load(&deps.storage).unwrap(); - assert_eq!( - Addr::unchecked("address_provider"), - config.address_provider_address - ); - - let global_state = GLOBAL_STATE.load(&deps.storage).unwrap(); - assert_eq!(global_state.proposal_count, 0); - } - } - - #[test] - fn test_update_config() { - let mut deps = mock_dependencies(&[]); - - // * - // init config with valid params - // * - let init_config = CreateOrUpdateConfig { - address_provider_address: Some(String::from("address_provider")), - - proposal_voting_period: Some(10), - proposal_effective_delay: Some(11), - proposal_expiration_period: Some(12), - proposal_required_deposit: Some(Uint128::new(111)), - proposal_required_threshold: Some(Decimal::percent( - MINIMUM_PROPOSAL_REQUIRED_THRESHOLD_PERCENTAGE, - )), - proposal_required_quorum: Some(Decimal::one()), - }; - let msg = InstantiateMsg { - config: init_config.clone(), - }; - let env = cosmwasm_std::testing::mock_env(); - let info = mock_info(MOCK_CONTRACT_ADDR); - let _res = instantiate(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); - - // * - // update config with invalid params - // * - { - let env = cosmwasm_std::testing::mock_env(); - let info = mock_info(MOCK_CONTRACT_ADDR); - - // proposal_required_quorum greater than 1 - let config = CreateOrUpdateConfig { - proposal_required_quorum: Some(Decimal::percent(101)), - ..init_config.clone() - }; - let msg = UpdateConfig { config }; - let error_res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap_err(); - assert_eq!( - error_res, - MarsError::InvalidParam { - param_name: "proposal_required_quorum".to_string(), - invalid_value: "1.01".to_string(), - predicate: "<= 1".to_string(), - } - .into() - ); - - // proposal_required_threshold less than 50% - let config = CreateOrUpdateConfig { - proposal_required_threshold: Some(Decimal::percent(49)), - ..init_config.clone() - }; - let msg = UpdateConfig { config }; - let error_res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap_err(); - assert_eq!( - error_res, - MarsError::InvalidParam { - param_name: "proposal_required_threshold".to_string(), - invalid_value: "0.49".to_string(), - predicate: ">= 0.5 and <= 1".to_string(), - } - .into() - ); - - // proposal_required_threshold greater than 100% - let config = CreateOrUpdateConfig { - proposal_required_threshold: Some(Decimal::percent(101)), - ..init_config.clone() - }; - let msg = UpdateConfig { config }; - let error_res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap_err(); - assert_eq!( - error_res, - MarsError::InvalidParam { - param_name: "proposal_required_threshold".to_string(), - invalid_value: "1.01".to_string(), - predicate: ">= 0.5 and <= 1".to_string(), - } - .into() - ); - } - - // * - // only council itself is authorized - // * - { - let msg = UpdateConfig { - config: init_config, - }; - let info = mock_info("somebody"); - let error_res = execute(deps.as_mut(), env.clone(), info, msg).unwrap_err(); - assert_eq!(error_res, MarsError::Unauthorized {}.into()); - } - - // * - // update config with all new params - // * - { - let config = CreateOrUpdateConfig { - address_provider_address: Some(String::from("new_address_provider")), - - proposal_voting_period: Some(101), - proposal_effective_delay: Some(111), - proposal_expiration_period: Some(121), - proposal_required_deposit: Some(Uint128::new(1111)), - proposal_required_threshold: Some(Decimal::from_ratio(4u128, 5u128)), - proposal_required_quorum: Some(Decimal::from_ratio(1u128, 5u128)), - }; - let msg = UpdateConfig { - config: config.clone(), - }; - let info = mock_info(MOCK_CONTRACT_ADDR); - let res = execute(deps.as_mut(), env, info, msg).unwrap(); - assert_eq!(0, res.messages.len()); - - // Read config from state - let new_config = CONFIG.load(&deps.storage).unwrap(); - - assert_eq!( - new_config.address_provider_address, - Addr::unchecked("new_address_provider") - ); - assert_eq!( - new_config.proposal_voting_period, - config.proposal_voting_period.unwrap() - ); - assert_eq!( - new_config.proposal_effective_delay, - config.proposal_effective_delay.unwrap() - ); - assert_eq!( - new_config.proposal_expiration_period, - config.proposal_expiration_period.unwrap() - ); - assert_eq!( - new_config.proposal_required_deposit, - config.proposal_required_deposit.unwrap() - ); - assert_eq!( - new_config.proposal_required_threshold, - config.proposal_required_threshold.unwrap() - ); - assert_eq!( - new_config.proposal_required_quorum, - config.proposal_required_quorum.unwrap() - ); - } - } - - #[test] - fn test_submit_proposal_invalid_params() { - let mut deps = th_setup(&[]); - - // * - // Invalid title - // * - { - let msg = ExecuteMsg::Receive(Cw20ReceiveMsg { - msg: to_binary(&ReceiveMsg::SubmitProposal { - title: "a".to_string(), - description: "A valid description".to_string(), - link: None, - messages: None, - }) - .unwrap(), - sender: String::from("submitter"), - amount: Uint128::new(2_000_000), - }); - let env = mock_env(MockEnvParams::default()); - let info = mock_info("mars_token"); - let response = execute(deps.as_mut(), env, info, msg).unwrap_err(); - assert_eq!(response, ContractError::invalid_proposal("title too short")); - } - - { - let msg = ExecuteMsg::Receive(Cw20ReceiveMsg { - msg: to_binary(&ReceiveMsg::SubmitProposal { - title: (0..100).map(|_| "a").collect::(), - description: "A valid description".to_string(), - link: None, - messages: None, - }) - .unwrap(), - sender: String::from("submitter"), - amount: Uint128::new(2_000_000), - }); - let env = mock_env(MockEnvParams::default()); - let info = mock_info("mars_token"); - let response = execute(deps.as_mut(), env, info, msg).unwrap_err(); - assert_eq!(response, ContractError::invalid_proposal("title too long")); - } - - // * - // Invalid description - // * - { - let msg = ExecuteMsg::Receive(Cw20ReceiveMsg { - msg: to_binary(&ReceiveMsg::SubmitProposal { - title: "A valid Title".to_string(), - description: "a".to_string(), - link: None, - messages: None, - }) - .unwrap(), - sender: String::from("submitter"), - amount: Uint128::new(2_000_000), - }); - let env = mock_env(MockEnvParams::default()); - let info = mock_info("mars_token"); - let response = execute(deps.as_mut(), env, info, msg).unwrap_err(); - assert_eq!( - response, - ContractError::invalid_proposal("description too short") - ); - } - - { - let msg = ExecuteMsg::Receive(Cw20ReceiveMsg { - msg: to_binary(&ReceiveMsg::SubmitProposal { - title: "A valid Title".to_string(), - description: (0..1030).map(|_| "a").collect::(), - link: None, - messages: None, - }) - .unwrap(), - sender: String::from("submitter"), - amount: Uint128::new(2_000_000), - }); - let env = mock_env(MockEnvParams::default()); - let info = mock_info("mars_token"); - let response = execute(deps.as_mut(), env, info, msg).unwrap_err(); - assert_eq!( - response, - ContractError::invalid_proposal("description too long") - ); - } - - // * - // Invalid link - // * - { - let msg = ExecuteMsg::Receive(Cw20ReceiveMsg { - msg: to_binary(&ReceiveMsg::SubmitProposal { - title: "A valid Title".to_string(), - description: "A valid description".to_string(), - link: Some("a".to_string()), - messages: None, - }) - .unwrap(), - sender: String::from("submitter"), - amount: Uint128::new(2_000_000), - }); - let env = mock_env(MockEnvParams::default()); - let info = mock_info("mars_token"); - let response = execute(deps.as_mut(), env, info, msg).unwrap_err(); - assert_eq!(response, ContractError::invalid_proposal("Link too short")); - } - - { - let msg = ExecuteMsg::Receive(Cw20ReceiveMsg { - msg: to_binary(&ReceiveMsg::SubmitProposal { - title: "A valid Title".to_string(), - description: "A valid description".to_string(), - link: Some((0..150).map(|_| "a").collect::()), - messages: None, - }) - .unwrap(), - sender: String::from("submitter"), - amount: Uint128::new(2_000_000), - }); - let env = mock_env(MockEnvParams::default()); - let info = mock_info("mars_token"); - let response = execute(deps.as_mut(), env, info, msg).unwrap_err(); - assert_eq!(response, ContractError::invalid_proposal("Link too long")); - } - - // * - // Invalid deposit amount - // * - { - let msg = ExecuteMsg::Receive(Cw20ReceiveMsg { - msg: to_binary(&ReceiveMsg::SubmitProposal { - title: "A valid Title".to_string(), - description: "A valid description".to_string(), - link: None, - messages: None, - }) - .unwrap(), - sender: String::from("submitter"), - amount: TEST_PROPOSAL_REQUIRED_DEPOSIT - Uint128::new(100), - }); - let env = mock_env(MockEnvParams::default()); - let info = mock_info("mars_token"); - let response = execute(deps.as_mut(), env, info, msg).unwrap_err(); - assert_eq!( - response, - ContractError::invalid_proposal("Must deposit at least 10000 Mars tokens") - ); - } - - // * - // Invalid deposit currency - // * - { - let msg = ExecuteMsg::Receive(Cw20ReceiveMsg { - msg: to_binary(&ReceiveMsg::SubmitProposal { - title: "A valid Title".to_string(), - description: "A valid description".to_string(), - link: None, - messages: None, - }) - .unwrap(), - sender: String::from("submitter"), - amount: TEST_PROPOSAL_REQUIRED_DEPOSIT, - }); - let env = mock_env(MockEnvParams::default()); - let info = mock_info("other_token"); - let res_error = execute(deps.as_mut(), env, info, msg).unwrap_err(); - assert_eq!( - res_error, - ContractError::invalid_proposal("Must deposit at least 10000 Mars tokens") - ); - } - } - - #[test] - fn test_submit_proposal() { - let mut deps = th_setup(&[]); - let submitter_address = Addr::unchecked("submitter"); - - // Submit Proposal without link or call data - let msg = ExecuteMsg::Receive(Cw20ReceiveMsg { - msg: to_binary(&ReceiveMsg::SubmitProposal { - title: "A valid title".to_string(), - description: "A valid description".to_string(), - link: None, - messages: None, - }) - .unwrap(), - sender: submitter_address.to_string(), - amount: TEST_PROPOSAL_REQUIRED_DEPOSIT, - }); - let env = mock_env(MockEnvParams { - block_height: 100_000, - ..Default::default() - }); - let info = mock_info("mars_token"); - let res = execute(deps.as_mut(), env, info, msg).unwrap(); - let expected_end_height = 100_000 + TEST_PROPOSAL_VOTING_PERIOD; - assert_eq!( - res.attributes, - vec![ - attr("action", "submit_proposal"), - attr("submitter", "submitter"), - attr("proposal_id", 1.to_string()), - attr("proposal_end_height", expected_end_height.to_string()), - ] - ); - - let global_state = GLOBAL_STATE.load(&deps.storage).unwrap(); - assert_eq!(global_state.proposal_count, 1); - - let proposal = PROPOSALS.load(&deps.storage, U64Key::new(1_u64)).unwrap(); - assert_eq!(proposal.proposal_id, 1); - assert_eq!(proposal.submitter_address, submitter_address); - assert_eq!(proposal.status, ProposalStatus::Active); - assert_eq!(proposal.for_votes, Uint128::new(0)); - assert_eq!(proposal.against_votes, Uint128::new(0)); - assert_eq!(proposal.start_height, 100_000); - assert_eq!(proposal.end_height, expected_end_height); - assert_eq!(proposal.title, "A valid title"); - assert_eq!(proposal.description, "A valid description"); - assert_eq!(proposal.link, None); - assert_eq!(proposal.messages, None); - assert_eq!(proposal.deposit_amount, TEST_PROPOSAL_REQUIRED_DEPOSIT); - - // Submit Proposal with link and call data - let msg = ExecuteMsg::Receive(Cw20ReceiveMsg { - msg: to_binary(&ReceiveMsg::SubmitProposal { - title: "A valid title".to_string(), - description: "A valid description".to_string(), - link: Some("https://www.avalidlink.com".to_string()), - messages: Some(vec![ProposalMessage { - execution_order: 0, - msg: CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: String::from(MOCK_CONTRACT_ADDR), - msg: to_binary(&ExecuteMsg::UpdateConfig { - config: CreateOrUpdateConfig::default(), - }) - .unwrap(), - funds: vec![], - }), - }]), - }) - .unwrap(), - sender: submitter_address.to_string(), - amount: TEST_PROPOSAL_REQUIRED_DEPOSIT, - }); - let env = mock_env(MockEnvParams { - block_height: 100_000, - ..Default::default() - }); - let info = mock_info("mars_token"); - let res = execute(deps.as_mut(), env, info, msg).unwrap(); - let expected_end_height = 100_000 + TEST_PROPOSAL_VOTING_PERIOD; - assert_eq!( - res.attributes, - vec![ - attr("action", "submit_proposal"), - attr("submitter", "submitter"), - attr("proposal_id", 2.to_string()), - attr("proposal_end_height", expected_end_height.to_string()), - ] - ); - - let global_state = GLOBAL_STATE.load(&deps.storage).unwrap(); - assert_eq!(global_state.proposal_count, 2); - - let proposal = PROPOSALS.load(&deps.storage, U64Key::new(2_u64)).unwrap(); - assert_eq!( - proposal.link, - Some("https://www.avalidlink.com".to_string()) - ); - assert_eq!( - proposal.messages, - Some(vec![ProposalMessage { - execution_order: 0, - msg: CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: String::from(MOCK_CONTRACT_ADDR), - msg: to_binary(&ExecuteMsg::UpdateConfig { - config: CreateOrUpdateConfig::default(), - }) - .unwrap(), - funds: vec![], - }), - }]) - ); - } - - #[test] - fn test_invalid_cast_votes() { - let mut deps = th_setup(&[]); - let voter_address = Addr::unchecked("valid_voter"); - let invalid_voter_address = Addr::unchecked("invalid_voter"); - - deps.querier - .set_xmars_address(Addr::unchecked("xmars_token")); - deps.querier - .set_xmars_balance_at(voter_address, 99_999, Uint128::new(100)); - deps.querier - .set_xmars_balance_at(invalid_voter_address, 99_999, Uint128::zero()); - deps.querier.set_vesting_address(Addr::unchecked("vesting")); - - let active_proposal_id = 1_u64; - th_build_mock_proposal( - deps.as_mut(), - MockProposal { - id: active_proposal_id, - status: ProposalStatus::Active, - start_height: 100_000, - end_height: 100_100, - ..Default::default() - }, - ); - - let executed_proposal_id = 2_u64; - th_build_mock_proposal( - deps.as_mut(), - MockProposal { - id: executed_proposal_id, - status: ProposalStatus::Executed, - start_height: 100_000, - end_height: 100_100, - ..Default::default() - }, - ); - - // * - // voting on a non-existent proposal should fail - // * - { - let msg = ExecuteMsg::CastVote { - proposal_id: 3, - vote: ProposalVoteOption::For, - }; - let env = mock_env(MockEnvParams { - block_height: 100_001, - ..Default::default() - }); - let info = mock_info("valid_voter"); - let res_error = execute(deps.as_mut(), env, info, msg).unwrap_err(); - assert_eq!( - res_error, - StdError::NotFound { - kind: "mars_core::council::Proposal".to_string(), - } - .into() - ); - } - - // * - // voting on an inactive proposal should fail - // * - { - let msg = ExecuteMsg::CastVote { - proposal_id: executed_proposal_id, - vote: ProposalVoteOption::For, - }; - let env = mock_env(MockEnvParams { - block_height: 100_001, - ..Default::default() - }); - let info = mock_info("valid_voter"); - let response = execute(deps.as_mut(), env, info, msg).unwrap_err(); - assert_eq!(response, ContractError::ProposalNotActive {}); - } - - // * - // voting after proposal end should fail - // * - { - let msg = ExecuteMsg::CastVote { - proposal_id: active_proposal_id, - vote: ProposalVoteOption::For, - }; - let env = mock_env(MockEnvParams { - block_height: 100_200, - ..Default::default() - }); - let info = mock_info("valid_voter"); - let response = execute(deps.as_mut(), env, info, msg).unwrap_err(); - assert_eq!(response, ContractError::VoteVotingPeriodEnded {}); - } - - // * - // voting without any voting power should fail - // * - { - let msg = ExecuteMsg::CastVote { - proposal_id: active_proposal_id, - vote: ProposalVoteOption::For, - }; - let env = mock_env(MockEnvParams { - block_height: 100_001, - ..Default::default() - }); - let info = mock_info("invalid_voter"); - let response = execute(deps.as_mut(), env, info, msg).unwrap_err(); - assert_eq!(response, ContractError::VoteNoVotingPower { block: 99_999 }); - } - } - - #[test] - fn test_cast_vote() { - // setup - let mut deps = th_setup(&[]); - let voter_address = Addr::unchecked("voter"); - - let active_proposal_id = 1_u64; - - deps.querier - .set_xmars_address(Addr::unchecked("xmars_token")); - deps.querier - .set_xmars_balance_at(voter_address.clone(), 99_999, Uint128::new(100)); - - deps.querier.set_vesting_address(Addr::unchecked("vesting")); - deps.querier - .set_vesting_voting_power_at(voter_address.clone(), 99_999, Uint128::new(23)); - - let active_proposal = th_build_mock_proposal( - deps.as_mut(), - MockProposal { - id: active_proposal_id, - status: ProposalStatus::Active, - start_height: 100_000, - end_height: 100_100, - ..Default::default() - }, - ); - - // Add another vote on an extra proposal to voter to validate voting on multiple proposals - // is valid - PROPOSAL_VOTES - .save( - &mut deps.storage, - (U64Key::new(4_u64), &voter_address), - &ProposalVote { - option: ProposalVoteOption::Against, - power: Uint128::new(100), - }, - ) - .unwrap(); - - // Valid vote for - let msg = ExecuteMsg::CastVote { - proposal_id: active_proposal_id, - vote: ProposalVoteOption::For, - }; - - let env = mock_env(MockEnvParams { - block_height: active_proposal.start_height + 1, - ..Default::default() - }); - let info = mock_info("voter"); - let res = execute(deps.as_mut(), env, info, msg).unwrap(); - - assert_eq!( - vec![ - attr("action", "cast_vote"), - attr("proposal_id", active_proposal_id.to_string()), - attr("voter", "voter"), - attr("vote", "for"), - attr("voting_power", 123.to_string()), // 100 (free) + 23 (locked) - ], - res.attributes - ); - - let proposal = PROPOSALS - .load(&deps.storage, U64Key::new(active_proposal_id)) - .unwrap(); - assert_eq!(proposal.for_votes, Uint128::new(123)); - assert_eq!(proposal.against_votes, Uint128::new(0)); - - let proposal_vote = PROPOSAL_VOTES - .load( - &deps.storage, - (U64Key::new(active_proposal_id), &voter_address), - ) - .unwrap(); - - assert_eq!(proposal_vote.option, ProposalVoteOption::For); - assert_eq!(proposal_vote.power, Uint128::new(123)); - - // Voting again with same address should fail - let msg = ExecuteMsg::CastVote { - proposal_id: active_proposal_id, - vote: ProposalVoteOption::For, - }; - - let env = mock_env(MockEnvParams { - block_height: active_proposal.start_height + 1, - ..Default::default() - }); - let info = mock_info("voter"); - let response = execute(deps.as_mut(), env, info, msg).unwrap_err(); - assert_eq!(response, ContractError::VoteUserAlreadyVoted {}); - - // Valid against vote - { - let msg = ExecuteMsg::CastVote { - proposal_id: active_proposal_id, - vote: ProposalVoteOption::Against, - }; - - deps.querier.set_xmars_balance_at( - Addr::unchecked("voter2"), - active_proposal.start_height - 1, - Uint128::new(200), - ); - - let env = mock_env(MockEnvParams { - block_height: active_proposal.start_height + 1, - ..Default::default() - }); - let info = mock_info("voter2"); - let res = execute(deps.as_mut(), env, info, msg).unwrap(); - assert_eq!( - vec![ - attr("action", "cast_vote"), - attr("proposal_id", active_proposal_id.to_string()), - attr("voter", "voter2"), - attr("vote", "against"), - attr("voting_power", 200.to_string()), - ], - res.attributes - ); - } - - // Extra for and against votes to check aggregates are computed correctly - deps.querier.set_xmars_balance_at( - Addr::unchecked("voter3"), - active_proposal.start_height - 1, - Uint128::new(300), - ); - - deps.querier.set_xmars_balance_at( - Addr::unchecked("voter4"), - active_proposal.start_height - 1, - Uint128::new(400), - ); - - { - let msg = ExecuteMsg::CastVote { - proposal_id: active_proposal_id, - vote: ProposalVoteOption::For, - }; - let env = mock_env(MockEnvParams { - block_height: active_proposal.start_height + 1, - ..Default::default() - }); - let info = mock_info("voter3"); - execute(deps.as_mut(), env, info, msg).unwrap(); - } - - { - let msg = ExecuteMsg::CastVote { - proposal_id: active_proposal_id, - vote: ProposalVoteOption::Against, - }; - let env = mock_env(MockEnvParams { - block_height: active_proposal.start_height + 1, - ..Default::default() - }); - let info = mock_info("voter4"); - execute(deps.as_mut(), env, info, msg).unwrap(); - } - - let proposal = PROPOSALS - .load(&deps.storage, U64Key::new(active_proposal_id)) - .unwrap(); - assert_eq!(proposal.for_votes, Uint128::new(123 + 300)); - assert_eq!(proposal.against_votes, Uint128::new(200 + 400)); - } - - #[test] - fn test_query_proposals() { - // Arrange - let mut deps = th_setup(&[]); - - let active_proposal_1_id = 1_u64; - th_build_mock_proposal( - deps.as_mut(), - MockProposal { - id: active_proposal_1_id, - status: ProposalStatus::Active, - start_height: 100_000, - end_height: 100_100, - ..Default::default() - }, - ); - - let active_proposal_2_id = 2_u64; - let msg = CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: String::from("test_address"), - msg: Binary::from(br#"{"some":123}"#), - funds: vec![], - }); - let messages = Option::from(vec![ProposalMessage { - execution_order: 0, - msg: msg.clone(), - }]); - th_build_mock_proposal( - deps.as_mut(), - MockProposal { - id: active_proposal_2_id, - status: ProposalStatus::Active, - start_height: 100_000, - end_height: 100_100, - messages, - ..Default::default() - }, - ); - - let global_state = GlobalState { - proposal_count: 2_u64, - }; - GLOBAL_STATE.save(&mut deps.storage, &global_state).unwrap(); - // Assert corectly sorts asc - let res = query_proposals(deps.as_ref(), None, None).unwrap(); - assert_eq!(res.proposal_count, 2); - assert_eq!(res.proposal_list.len(), 2); - assert_eq!(res.proposal_list[0].proposal_id, active_proposal_1_id); - assert_eq!(res.proposal_list[1].proposal_id, active_proposal_2_id); - assert_eq!(res.proposal_list[1].messages.clone().unwrap()[0].msg, msg); - - // Assert start != 0 - let res = query_proposals(deps.as_ref(), Some(2), None).unwrap(); - assert_eq!(res.proposal_count, 2); - assert_eq!(res.proposal_list.len(), 1); - assert_eq!(res.proposal_list[0].proposal_id, active_proposal_2_id); - - // Assert start > length of collection - let res = query_proposals(deps.as_ref(), Some(99), None).unwrap(); - assert_eq!(res.proposal_count, 2); - assert_eq!(res.proposal_list.len(), 0); - - // Assert limit - let res = query_proposals(deps.as_ref(), None, Some(1)).unwrap(); - assert_eq!(res.proposal_count, 2); - assert_eq!(res.proposal_list.len(), 1); - assert_eq!(res.proposal_list[0].proposal_id, active_proposal_1_id); - - // Assert limit greater than length of collection - let res = query_proposals(deps.as_ref(), None, Some(99)).unwrap(); - assert_eq!(res.proposal_count, 2); - assert_eq!(res.proposal_list.len(), 2); - } - - #[test] - fn test_invalid_end_proposals() { - let mut deps = th_setup(&[]); - - let active_proposal_id = 1_u64; - let executed_proposal_id = 2_u64; - - deps.querier - .set_xmars_address(Addr::unchecked("xmars_token")); - deps.querier - .set_xmars_total_supply_at(99_999, Uint128::new(100)); - deps.querier.set_vesting_address(Addr::unchecked("vesting")); - deps.querier - .set_vesting_total_voting_power_at(99_999, Uint128::zero()); - - th_build_mock_proposal( - deps.as_mut(), - MockProposal { - id: active_proposal_id, - status: ProposalStatus::Active, - end_height: 100_000, - ..Default::default() - }, - ); - th_build_mock_proposal( - deps.as_mut(), - MockProposal { - id: executed_proposal_id, - status: ProposalStatus::Executed, - ..Default::default() - }, - ); - - // cannot end a proposal that has not ended its voting period - let msg = ExecuteMsg::EndProposal { - proposal_id: active_proposal_id, - }; - let env = mock_env(MockEnvParams { - block_height: 100_000, - ..Default::default() - }); - let info = mock_info("sender"); - let response = execute(deps.as_mut(), env, info, msg).unwrap_err(); - assert_eq!(response, ContractError::EndProposalVotingPeriodNotEnded {}); - - // cannot end a non active proposal - let msg = ExecuteMsg::EndProposal { - proposal_id: executed_proposal_id, - }; - let env = mock_env(MockEnvParams { - block_height: 100_001, - ..Default::default() - }); - let info = mock_info("sender"); - let response = execute(deps.as_mut(), env, info, msg).unwrap_err(); - assert_eq!(response, ContractError::ProposalNotActive {}); - } - - #[test] - fn test_end_proposal() { - let mut deps = th_setup(&[]); - - deps.querier - .set_xmars_address(Addr::unchecked("xmars_token")); - deps.querier - .set_xmars_total_supply_at(89_999, Uint128::new(100_000)); - deps.querier.set_vesting_address(Addr::unchecked("vesting")); - deps.querier - .set_vesting_total_voting_power_at(99_999, Uint128::zero()); - - let proposal_threshold = Decimal::from_ratio(51_u128, 100_u128); - let proposal_quorum = Decimal::from_ratio(2_u128, 100_u128); - let proposal_end_height = 100_000u64; - - CONFIG - .update(&mut deps.storage, |mut config| -> StdResult { - config.proposal_required_threshold = proposal_threshold; - config.proposal_required_quorum = proposal_quorum; - Ok(config) - }) - .unwrap(); - - // end passed proposal - let initial_passed_proposal = th_build_mock_proposal( - deps.as_mut(), - MockProposal { - id: 1, - status: ProposalStatus::Active, - for_votes: Uint128::new(11_000), - against_votes: Uint128::new(10_000), - start_height: 90_000, - end_height: proposal_end_height + 1, - ..Default::default() - }, - ); - - let msg = ExecuteMsg::EndProposal { proposal_id: 1 }; - - let env = mock_env(MockEnvParams { - block_height: initial_passed_proposal.end_height + 1, - ..Default::default() - }); - let info = mock_info("sender"); - - let res = execute(deps.as_mut(), env, info, msg).unwrap(); - - assert_eq!( - res.attributes, - vec![ - attr("action", "end_proposal"), - attr("proposal_id", 1.to_string()), - attr("proposal_result", "passed"), - ] - ); - - assert_eq!( - res.messages, - vec![SubMsg::new(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: String::from("mars_token"), - funds: vec![], - msg: to_binary(&Cw20ExecuteMsg::Transfer { - recipient: String::from("submitter"), - amount: TEST_PROPOSAL_REQUIRED_DEPOSIT, - }) - .unwrap(), - })),] - ); - - let final_passed_proposal = PROPOSALS.load(&deps.storage, U64Key::new(1u64)).unwrap(); - assert_eq!(final_passed_proposal.status, ProposalStatus::Passed); - - // end rejected proposal (no quorum) - let initial_passed_proposal = th_build_mock_proposal( - deps.as_mut(), - MockProposal { - id: 2, - status: ProposalStatus::Active, - for_votes: Uint128::new(11), - against_votes: Uint128::new(10), - end_height: proposal_end_height + 1, - start_height: 90_000, - ..Default::default() - }, - ); - - let msg = ExecuteMsg::EndProposal { proposal_id: 2 }; - - let env = mock_env(MockEnvParams { - block_height: initial_passed_proposal.end_height + 1, - ..Default::default() - }); - let info = mock_info("sender"); - - let res = execute(deps.as_mut(), env, info, msg).unwrap(); - - assert_eq!( - res.attributes, - vec![ - attr("action", "end_proposal"), - attr("proposal_id", 2.to_string()), - attr("proposal_result", "rejected"), - ] - ); - - assert_eq!( - res.messages, - vec![SubMsg::new(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: String::from("mars_token"), - msg: to_binary(&Cw20ExecuteMsg::Transfer { - recipient: String::from("staking"), - amount: TEST_PROPOSAL_REQUIRED_DEPOSIT, - }) - .unwrap(), - funds: vec![], - }))] - ); - - let final_passed_proposal = PROPOSALS.load(&deps.storage, U64Key::new(2_u64)).unwrap(); - assert_eq!(final_passed_proposal.status, ProposalStatus::Rejected); - - // end rejected proposal (no threshold) - let initial_passed_proposal = th_build_mock_proposal( - deps.as_mut(), - MockProposal { - id: 3, - status: ProposalStatus::Active, - for_votes: Uint128::new(10_000), - against_votes: Uint128::new(11_000), - start_height: 90_000, - end_height: proposal_end_height + 1, - ..Default::default() - }, - ); - - let msg = ExecuteMsg::EndProposal { proposal_id: 3 }; - - let env = mock_env(MockEnvParams { - block_height: initial_passed_proposal.end_height + 1, - ..Default::default() - }); - let info = mock_info("sender"); - - let res = execute(deps.as_mut(), env, info, msg).unwrap(); - - assert_eq!( - res.attributes, - vec![ - attr("action", "end_proposal"), - attr("proposal_id", 3.to_string()), - attr("proposal_result", "rejected"), - ] - ); - - assert_eq!( - res.messages, - vec![SubMsg::new(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: String::from("mars_token"), - msg: to_binary(&Cw20ExecuteMsg::Transfer { - recipient: String::from("staking"), - amount: TEST_PROPOSAL_REQUIRED_DEPOSIT, - }) - .unwrap(), - funds: vec![], - }))] - ); - - let final_passed_proposal = PROPOSALS.load(&deps.storage, U64Key::new(3_u64)).unwrap(); - assert_eq!(final_passed_proposal.status, ProposalStatus::Rejected); - } - - #[test] - fn test_invalid_execute_proposals() { - let mut deps = th_setup(&[]); - - let passed_proposal_id = 1_u64; - let executed_proposal_id = 2_u64; - - let passed_proposal = th_build_mock_proposal( - deps.as_mut(), - MockProposal { - id: passed_proposal_id, - status: ProposalStatus::Passed, - end_height: 100_000, - ..Default::default() - }, - ); - let executed_proposal = th_build_mock_proposal( - deps.as_mut(), - MockProposal { - id: executed_proposal_id, - status: ProposalStatus::Executed, - ..Default::default() - }, - ); - - // cannot execute a non Passed proposal - let msg = ExecuteMsg::ExecuteProposal { - proposal_id: executed_proposal_id, - }; - let env = mock_env(MockEnvParams { - block_height: executed_proposal.end_height + TEST_PROPOSAL_EFFECTIVE_DELAY + 1, - ..Default::default() - }); - let info = mock_info("executer"); - let response = execute(deps.as_mut(), env, info, msg).unwrap_err(); - assert_eq!(response, ContractError::ExecuteProposalNotPassed {},); - - // cannot execute a proposal before the effective delay has passed - let msg = ExecuteMsg::ExecuteProposal { - proposal_id: passed_proposal_id, - }; - let env = mock_env(MockEnvParams { - block_height: passed_proposal.end_height + 1, - ..Default::default() - }); - let info = mock_info("executer"); - let response = execute(deps.as_mut(), env, info, msg).unwrap_err(); - assert_eq!(response, ContractError::ExecuteProposalDelayNotEnded {}); - - // cannot execute an expired proposal - let msg = ExecuteMsg::ExecuteProposal { - proposal_id: passed_proposal_id, - }; - let env = mock_env(MockEnvParams { - block_height: passed_proposal.end_height - + TEST_PROPOSAL_EFFECTIVE_DELAY - + TEST_PROPOSAL_EXPIRATION_PERIOD - + 1, - ..Default::default() - }); - let info = mock_info("executer"); - let response = execute(deps.as_mut(), env, info, msg).unwrap_err(); - assert_eq!(response, ContractError::ExecuteProposalExpired {}); - } - - #[test] - fn test_execute_proposals() { - let mut deps = th_setup(&[]); - let contract_address = Addr::unchecked(MOCK_CONTRACT_ADDR); - let other_address = Addr::unchecked("other"); - let new_code_id = 123; - - let binary_msg = Binary::from(br#"{"key": 123}"#); - let initial_proposal = th_build_mock_proposal( - deps.as_mut(), - MockProposal { - id: 1, - status: ProposalStatus::Passed, - end_height: 100_000, - messages: Some(vec![ - ProposalMessage { - execution_order: 2, - msg: CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: other_address.to_string(), - msg: binary_msg.clone(), - funds: vec![], - }), - }, - ProposalMessage { - execution_order: 3, - msg: CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: contract_address.to_string(), - msg: to_binary(&ExecuteMsg::UpdateConfig { - config: CreateOrUpdateConfig::default(), - }) - .unwrap(), - funds: vec![], - }), - }, - ProposalMessage { - execution_order: 1, - msg: CosmosMsg::Wasm(WasmMsg::Migrate { - contract_addr: contract_address.to_string(), - new_code_id, - msg: binary_msg.clone(), - }), - }, - ]), - ..Default::default() - }, - ); - - let env = mock_env(MockEnvParams { - block_height: initial_proposal.end_height + TEST_PROPOSAL_EFFECTIVE_DELAY + 1, - ..Default::default() - }); - let info = mock_info("executer"); - - let msg = ExecuteMsg::ExecuteProposal { proposal_id: 1 }; - - let res = execute(deps.as_mut(), env, info, msg).unwrap(); - - assert_eq!( - res.attributes, - vec![ - attr("action", "execute_proposal"), - attr("proposal_id", 1.to_string()), - ] - ); - - assert_eq!( - res.messages, - vec![ - SubMsg::new(CosmosMsg::Wasm(WasmMsg::Migrate { - contract_addr: contract_address.to_string(), - new_code_id, - msg: binary_msg.clone(), - })), - SubMsg::new(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: other_address.to_string(), - funds: vec![], - msg: binary_msg, - })), - SubMsg::new(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: contract_address.to_string(), - funds: vec![], - msg: to_binary(&ExecuteMsg::UpdateConfig { - config: CreateOrUpdateConfig::default() - }) - .unwrap(), - })), - ] - ); - - let final_passed_proposal = PROPOSALS - .load(&mut deps.storage, U64Key::new(1_u64)) - .unwrap(); - - assert_eq!(ProposalStatus::Executed, final_passed_proposal.status); - } - - #[test] - fn test_query_proposal_votes() { - // Arrange - let mut deps = th_setup(&[]); - - deps.querier - .set_xmars_address(Addr::unchecked("xmars_token")); - deps.querier.set_vesting_address(Addr::unchecked("vesting")); - - let active_proposal_id = 1_u64; - - let voter_address1 = Addr::unchecked("voter1"); - let voter_address2 = Addr::unchecked("voter2"); - let voter_address3 = Addr::unchecked("voter3"); - let voter_address4 = Addr::unchecked("voter4"); - let voter_address5 = Addr::unchecked("voter5"); - deps.querier - .set_xmars_balance_at(voter_address1, 99_999, Uint128::new(100)); - deps.querier - .set_xmars_balance_at(voter_address2, 99_999, Uint128::new(200)); - deps.querier - .set_xmars_balance_at(voter_address3, 99_999, Uint128::new(300)); - deps.querier - .set_xmars_balance_at(voter_address4, 99_999, Uint128::new(400)); - deps.querier - .set_xmars_balance_at(voter_address5, 99_999, Uint128::new(500)); - - let active_proposal = th_build_mock_proposal( - deps.as_mut(), - MockProposal { - id: active_proposal_id, - status: ProposalStatus::Active, - start_height: 100_000, - end_height: 100_100, - ..Default::default() - }, - ); - PROPOSALS - .save( - &mut deps.storage, - U64Key::new(active_proposal_id), - &active_proposal, - ) - .unwrap(); - - let msg_vote_for = ExecuteMsg::CastVote { - proposal_id: active_proposal_id, - vote: ProposalVoteOption::For, - }; - let msg_vote_against = ExecuteMsg::CastVote { - proposal_id: active_proposal_id, - vote: ProposalVoteOption::Against, - }; - - // Act - let env = mock_env(MockEnvParams { - block_height: active_proposal.start_height + 1, - ..Default::default() - }); - let info = mock_info("voter1"); - execute(deps.as_mut(), env.clone(), info, msg_vote_for.clone()).unwrap(); - - let info = mock_info("voter2"); - execute(deps.as_mut(), env.clone(), info, msg_vote_for.clone()).unwrap(); - - let info = mock_info("voter3"); - execute(deps.as_mut(), env.clone(), info, msg_vote_for.clone()).unwrap(); - - let info = mock_info("voter4"); - execute(deps.as_mut(), env.clone(), info, msg_vote_against.clone()).unwrap(); - - let info = mock_info("voter5"); - execute(deps.as_mut(), env, info, msg_vote_against.clone()).unwrap(); - - // Assert default params - let res = query_proposal_votes( - deps.as_ref(), - active_proposal_id, - Option::None, - Option::None, - ) - .unwrap(); - assert_eq!(res.votes.len(), 5); - assert_eq!(res.proposal_id, active_proposal_id); - - // Assert corectly sorts asc - assert_eq!(res.votes[0].voter_address, Addr::unchecked("voter1")); - assert_eq!(res.votes[0].option, ProposalVoteOption::For); - assert_eq!(res.votes[0].power, Uint128::new(100)); - assert_eq!(res.votes[4].voter_address, Addr::unchecked("voter5")); - assert_eq!(res.votes[4].option, ProposalVoteOption::Against); - assert_eq!(res.votes[4].power, Uint128::new(500)); - - // Assert start_after - let res = query_proposal_votes( - deps.as_ref(), - active_proposal_id, - Option::from(String::from("voter4")), - Option::None, - ) - .unwrap(); - assert_eq!(res.votes.len(), 1); - assert_eq!(res.votes[0].voter_address, Addr::unchecked("voter5")); - - // Assert take - let res = query_proposal_votes( - deps.as_ref(), - active_proposal_id, - Option::None, - Option::from(1), - ) - .unwrap(); - assert_eq!(res.votes.len(), 1); - assert_eq!(res.votes[0].voter_address, Addr::unchecked("voter1")); - } - - // TEST HELPERS - fn th_setup(contract_balances: &[Coin]) -> OwnedDeps { - let mut deps = mock_dependencies(contract_balances); - - let config = CreateOrUpdateConfig { - address_provider_address: Some(String::from("address_provider")), - - proposal_voting_period: Some(TEST_PROPOSAL_VOTING_PERIOD), - proposal_effective_delay: Some(TEST_PROPOSAL_EFFECTIVE_DELAY), - proposal_expiration_period: Some(TEST_PROPOSAL_EXPIRATION_PERIOD), - proposal_required_deposit: Some(TEST_PROPOSAL_REQUIRED_DEPOSIT), - proposal_required_quorum: Some(Decimal::one()), - proposal_required_threshold: Some(Decimal::one()), - }; - - let msg = InstantiateMsg { config }; - let info = mock_info("initializer"); - let env = mock_env(MockEnvParams::default()); - instantiate(deps.as_mut(), env, info, msg).unwrap(); - - deps - } - - #[derive(Debug)] - struct MockProposal { - id: u64, - status: ProposalStatus, - for_votes: Uint128, - against_votes: Uint128, - start_height: u64, - end_height: u64, - messages: Option>, - } - - impl Default for MockProposal { - fn default() -> Self { - MockProposal { - id: 1, - status: ProposalStatus::Active, - for_votes: Uint128::zero(), - against_votes: Uint128::zero(), - start_height: 1, - end_height: 1, - messages: None, - } - } - } - - fn th_build_mock_proposal(deps: DepsMut, mock_proposal: MockProposal) -> Proposal { - let proposal = Proposal { - proposal_id: mock_proposal.id, - submitter_address: Addr::unchecked("submitter"), - status: mock_proposal.status, - for_votes: mock_proposal.for_votes, - against_votes: mock_proposal.against_votes, - start_height: mock_proposal.start_height, - end_height: mock_proposal.end_height, - title: "A valid title".to_string(), - description: "A description".to_string(), - link: None, - messages: mock_proposal.messages, - deposit_amount: TEST_PROPOSAL_REQUIRED_DEPOSIT, - }; - - PROPOSALS - .save(deps.storage, U64Key::new(mock_proposal.id), &proposal) - .unwrap(); - - proposal - } -} diff --git a/contracts/mars-council/src/lib.rs b/contracts/mars-council/src/lib.rs deleted file mode 100644 index 7a7fc406b..000000000 --- a/contracts/mars-council/src/lib.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod contract; -pub mod state; - -pub use mars_core::council::*; diff --git a/contracts/mars-council/src/state.rs b/contracts/mars-council/src/state.rs deleted file mode 100644 index 87c48d1ff..000000000 --- a/contracts/mars-council/src/state.rs +++ /dev/null @@ -1,8 +0,0 @@ -use crate::{Config, GlobalState, Proposal, ProposalVote}; -use cosmwasm_std::Addr; -use cw_storage_plus::{Item, Map, U64Key}; - -pub const CONFIG: Item = Item::new("config"); -pub const GLOBAL_STATE: Item = Item::new("global_state"); -pub const PROPOSALS: Map = Map::new("proposals"); -pub const PROPOSAL_VOTES: Map<(U64Key, &Addr), ProposalVote> = Map::new("proposal_votes"); diff --git a/contracts/mars-red-bank-closure/Cargo.toml b/contracts/mars-red-bank-closure/Cargo.toml deleted file mode 100644 index e672bef9c..000000000 --- a/contracts/mars-red-bank-closure/Cargo.toml +++ /dev/null @@ -1,37 +0,0 @@ -[package] -name = "mars-red-bank-closure" -version = "1.0.0" -authors = ["larry "] -edition = "2018" - -exclude = [ - # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. - "contract.wasm", - "hash.txt", -] - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces"] -# use library feature to disable all instantiate/execute/query exports -library = [] - -[dependencies] -mars-core = { path = "../../packages/mars-core", version = "1.0.0" } -mars-red-bank = { path = "../mars-red-bank", version = "1.0.0", features = ["library"] } - -cw20 = "0.9.0" -cw-storage-plus = "0.9.0" - -cosmwasm-std = "0.16.2" - -schemars = "0.8.1" -serde = { version = "1.0.103", default-features = false, features = ["derive"] } - -[dev-dependencies] -cosmwasm-schema = "0.16.2" diff --git a/contracts/mars-red-bank-closure/src/contract.rs b/contracts/mars-red-bank-closure/src/contract.rs deleted file mode 100644 index 8bae0747e..000000000 --- a/contracts/mars-red-bank-closure/src/contract.rs +++ /dev/null @@ -1,108 +0,0 @@ -use cosmwasm_std::{ - entry_point, to_binary, Binary, CosmosMsg, Deps, DepsMut, Empty, Env, Event, MessageInfo, - Response, StdError, StdResult, WasmMsg, -}; - -use mars_core::asset::Asset; -use mars_core::helpers::cw20_get_total_supply; -use mars_core::ma_token::msg::ExecuteMsg as MaTokenExecuteMsg; - -use mars_red_bank::state::MARKETS; - -use crate::helpers::{build_transfer_asset_msg, cw20_get_owners_balances, get_asset_balance}; -use crate::msg::ExecuteMsg; - -#[entry_point] -pub fn instantiate( - _deps: DepsMut, - _env: Env, - _info: MessageInfo, - _msg: Empty, -) -> StdResult { - Err(StdError::generic_err("`instantiate` is not implemented")) -} - -#[entry_point] -pub fn execute( - deps: DepsMut, - env: Env, - _info: MessageInfo, - msg: ExecuteMsg, -) -> StdResult { - match msg { - ExecuteMsg::Refund { asset } => refund(deps, env, asset), - } -} - -pub fn refund(deps: DepsMut, env: Env, asset: Asset) -> StdResult { - let (asset_label, asset_ref, _) = asset.get_attributes(); - let market = MARKETS.load(deps.storage, &asset_ref)?; - - // to initiate the refund, all borrowings of the specified asset must have been repaid. that is, - // `debt_total_scaled` parameter in its `Market` must be zero - if !market.debt_total_scaled.is_zero() { - return Err(StdError::generic_err(format!( - "`debt_total_scaled` must be zero before a refund a be initiated; current value: {}", - market.debt_total_scaled - ))); - } - - // query: - // - the amount of this asset held by Red Bank - // - the maToken's total supply - // - grab the first 10 holders of the asset's corresponding maToken and their respective balances - let mut total_amount_to_refund = - get_asset_balance(&deps.querier, &asset, &env.contract.address)?; - let mut ma_token_supply = - cw20_get_total_supply(&deps.querier, market.ma_token_address.clone())?; - let owners_balances = - cw20_get_owners_balances(&deps.querier, deps.api, &market.ma_token_address)?; - - // for each maToken owner, calculate how much tokens they should be refunded; create the messages - // to burn their maToken and tranfer funds - let mut msgs: Vec = vec![]; - let mut events: Vec = vec![]; - for (owner_addr, balance) in owners_balances { - let amount_to_refund = total_amount_to_refund.multiply_ratio(balance, ma_token_supply); - total_amount_to_refund -= amount_to_refund; - ma_token_supply -= balance; - - // burn maToken - msgs.push(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: market.ma_token_address.to_string(), - msg: to_binary(&MaTokenExecuteMsg::Burn { - user: owner_addr.to_string(), - amount: balance, - })?, - funds: vec![], - })); - - // refund asset - msgs.push(build_transfer_asset_msg( - &asset, - amount_to_refund, - &owner_addr, - )?); - - // event log - events.push( - Event::new("mars_red_bank/refunded") - .add_attribute("user", &owner_addr) - .add_attribute("asset", &asset_label) - .add_attribute("asset_amount", amount_to_refund) - .add_attribute("matoken_burned", balance), - ) - } - - Ok(Response::new().add_messages(msgs).add_events(events)) -} - -#[entry_point] -pub fn query(_deps: Deps, _env: Env, _msg: Empty) -> StdResult { - Err(StdError::generic_err("`query` is not implemented")) -} - -#[entry_point] -pub fn migrate(_deps: DepsMut, _env: Env, _msg: Empty) -> StdResult { - Ok(Response::new()) -} diff --git a/contracts/mars-red-bank-closure/src/contract_tests.rs b/contracts/mars-red-bank-closure/src/contract_tests.rs deleted file mode 100644 index c3e51cef9..000000000 --- a/contracts/mars-red-bank-closure/src/contract_tests.rs +++ /dev/null @@ -1,166 +0,0 @@ -use cosmwasm_std::testing::{mock_env, mock_info, MockApi, MockStorage}; -use cosmwasm_std::{ - coin, to_binary, Addr, BankMsg, CosmosMsg, OwnedDeps, ReplyOn, SubMsg, Uint128, WasmMsg, -}; - -use mars_core::asset::Asset; -use mars_core::ma_token::msg::ExecuteMsg as MaTokenExecuteMsg; -use mars_core::testing::{mock_dependencies, MarsMockQuerier}; -use mars_red_bank::state::MARKETS; -use mars_red_bank::Market; - -use crate::contract::execute; -use crate::msg::ExecuteMsg; - -fn uusd() -> Asset { - Asset::Native { - denom: "uusd".to_string(), - } -} - -// contract has 120 UST -// -// maUST: -// total supply: 100 -// alice: 60 -// bob: 30 -// charlie: 10 -fn setup_test() -> OwnedDeps { - let mut deps = mock_dependencies(&[coin(120000000, "uusd")]); - - deps.querier.set_cw20_balances( - Addr::unchecked("maUST"), - &[ - (Addr::unchecked("alice"), Uint128::new(60000000)), - (Addr::unchecked("bob"), Uint128::new(30000000)), - (Addr::unchecked("charlie"), Uint128::new(10000000)), - ], - ); - deps.querier - .set_cw20_total_supply(Addr::unchecked("maUST"), Uint128::new(100000000)); - - MARKETS - .save( - deps.as_mut().storage, - &uusd().get_reference(), - &Market { - ma_token_address: Addr::unchecked("maUST"), - debt_total_scaled: Uint128::zero(), - ..Default::default() - }, - ) - .unwrap(); - - deps -} - -#[test] -fn refunding() { - let mut deps = setup_test(); - - // alice gets 120000000 * 60000000 / 100000000 = 72000000 - // total_amount_to_refund = 120000000 - 72000000 = 48000000 - // maUST total supply = 10000000 - 60000000 = 40000000 - // - // bob gets 48000000 * 30000000 / 40000000 = 36000000 - // total_amount_to_refund = 48000000 - 36000000 = 12000000 - // maUST total supply = = 40000000 - 30000000 = 10000000 - // - // charlie gets the rest 12000000 uusd - let res = execute( - deps.as_mut(), - mock_env(), - mock_info("admin", &[]), - ExecuteMsg::Refund { asset: uusd() }, - ) - .unwrap(); - - assert_eq!(res.messages.len(), 6); - assert_eq!( - res.messages[0], - SubMsg { - id: 0, - msg: CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: "maUST".to_string(), - msg: to_binary(&MaTokenExecuteMsg::Burn { - user: "alice".to_string(), - amount: Uint128::new(60000000) - }) - .unwrap(), - funds: vec![] - }), - gas_limit: None, - reply_on: ReplyOn::Never, - } - ); - assert_eq!( - res.messages[1], - SubMsg { - id: 0, - msg: CosmosMsg::Bank(BankMsg::Send { - to_address: "alice".to_string(), - amount: vec![coin(72000000, "uusd")] - }), - gas_limit: None, - reply_on: ReplyOn::Never, - } - ); - assert_eq!( - res.messages[2], - SubMsg { - id: 0, - msg: CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: "maUST".to_string(), - msg: to_binary(&MaTokenExecuteMsg::Burn { - user: "bob".to_string(), - amount: Uint128::new(30000000) - }) - .unwrap(), - funds: vec![] - }), - gas_limit: None, - reply_on: ReplyOn::Never, - } - ); - assert_eq!( - res.messages[3], - SubMsg { - id: 0, - msg: CosmosMsg::Bank(BankMsg::Send { - to_address: "bob".to_string(), - amount: vec![coin(36000000, "uusd")] - }), - gas_limit: None, - reply_on: ReplyOn::Never, - } - ); - assert_eq!( - res.messages[4], - SubMsg { - id: 0, - msg: CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: "maUST".to_string(), - msg: to_binary(&MaTokenExecuteMsg::Burn { - user: "charlie".to_string(), - amount: Uint128::new(10000000) - }) - .unwrap(), - funds: vec![] - }), - gas_limit: None, - reply_on: ReplyOn::Never, - } - ); - assert_eq!( - res.messages[5], - SubMsg { - id: 0, - msg: CosmosMsg::Bank(BankMsg::Send { - to_address: "charlie".to_string(), - amount: vec![coin(12000000, "uusd")] - }), - gas_limit: None, - reply_on: ReplyOn::Never, - } - ); -} diff --git a/contracts/mars-red-bank-closure/src/helpers.rs b/contracts/mars-red-bank-closure/src/helpers.rs deleted file mode 100644 index 023f5ec64..000000000 --- a/contracts/mars-red-bank-closure/src/helpers.rs +++ /dev/null @@ -1,83 +0,0 @@ -use cosmwasm_std::{ - coin, to_binary, Addr, Api, BankMsg, CosmosMsg, QuerierWrapper, QueryRequest, StdResult, - Uint128, WasmMsg, WasmQuery, -}; -use cw20::{AllAccountsResponse, BalanceResponse, Cw20ExecuteMsg, Cw20QueryMsg}; - -use mars_core::asset::Asset; - -/// Get the first 10 token owners and their respective token balances -pub fn cw20_get_owners_balances( - querier: &QuerierWrapper, - api: &dyn Api, - token_addr: &Addr, -) -> StdResult> { - let AllAccountsResponse { accounts } = - querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: token_addr.to_string(), - msg: to_binary(&Cw20QueryMsg::AllAccounts { - start_after: None, - limit: Some(10), - })?, - }))?; - - accounts - .iter() - .map(|acct| { - let acct_addr = api.addr_validate(acct)?; - let BalanceResponse { balance } = - querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: token_addr.to_string(), - msg: to_binary(&Cw20QueryMsg::Balance { - address: acct_addr.to_string(), - })?, - }))?; - Ok((acct_addr, balance)) - }) - .collect::>>() -} - -pub fn get_asset_balance>( - querier: &QuerierWrapper, - asset: &Asset, - account: T, -) -> StdResult { - let balance = match &asset { - Asset::Cw20 { contract_addr } => { - let query: BalanceResponse = querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: contract_addr.clone(), - msg: to_binary(&Cw20QueryMsg::Balance { - address: account.into(), - })?, - }))?; - query.balance - } - Asset::Native { denom } => { - let balance_query = querier.query_balance(account, denom)?; - balance_query.amount - } - }; - Ok(balance) -} - -pub fn build_transfer_asset_msg( - asset: &Asset, - amount: Uint128, - recipient: &Addr, -) -> StdResult { - let msg = match asset { - Asset::Cw20 { contract_addr } => CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: contract_addr.clone(), - msg: to_binary(&Cw20ExecuteMsg::Transfer { - recipient: recipient.to_string(), - amount, - })?, - funds: vec![], - }), - Asset::Native { denom } => CosmosMsg::Bank(BankMsg::Send { - to_address: recipient.to_string(), - amount: vec![coin(amount.u128(), denom)], - }), - }; - Ok(msg) -} diff --git a/contracts/mars-red-bank-closure/src/lib.rs b/contracts/mars-red-bank-closure/src/lib.rs deleted file mode 100644 index 4590b8a77..000000000 --- a/contracts/mars-red-bank-closure/src/lib.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub mod contract; -pub mod helpers; -pub mod msg; - -#[cfg(test)] -mod contract_tests; diff --git a/contracts/mars-red-bank-closure/src/msg.rs b/contracts/mars-red-bank-closure/src/msg.rs deleted file mode 100644 index 7fd5826c9..000000000 --- a/contracts/mars-red-bank-closure/src/msg.rs +++ /dev/null @@ -1,11 +0,0 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use mars_core::asset::Asset; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum ExecuteMsg { - /// Refund the first 10 users of a specific asset; call this function repeatedly to refund all users - Refund { asset: Asset }, -} diff --git a/contracts/mars-staking/.cargo/config b/contracts/mars-staking/.cargo/config deleted file mode 100644 index 336b618a1..000000000 --- a/contracts/mars-staking/.cargo/config +++ /dev/null @@ -1,4 +0,0 @@ -[alias] -wasm = "build --release --target wasm32-unknown-unknown" -unit-test = "test --lib" -schema = "run --example schema" diff --git a/contracts/mars-staking/Cargo.toml b/contracts/mars-staking/Cargo.toml deleted file mode 100644 index dddb3a743..000000000 --- a/contracts/mars-staking/Cargo.toml +++ /dev/null @@ -1,42 +0,0 @@ -[package] -name = "mars-staking" -version = "1.0.0" -authors = ["Spike Spiegel "] -edition = "2018" - -exclude = [ - # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. - "contract.wasm", - "hash.txt", -] - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces"] -# use library feature to disable all instantiate/execute/query exports -library = [] - -[dependencies] -mars-core = { path = "../../packages/mars-core", version = "1.0.0" } - -cw20 = "0.9.0" -cw-storage-plus = "0.9.0" - -cosmwasm-std = "0.16.2" - -schemars = "0.8.1" -serde = { version = "1.0.103", default-features = false, features = ["derive"] } -thiserror = "1.0.23" - -astroport = "1.0" - -[dev-dependencies] -cosmwasm-schema = "0.16.2" - -[profile.release] -overflow-checks = true diff --git a/contracts/mars-staking/README.md b/contracts/mars-staking/README.md deleted file mode 100644 index 6c705a845..000000000 --- a/contracts/mars-staking/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Staking - -Handles the staking of MARS and the minting of xMARS. diff --git a/contracts/mars-staking/examples/schema.rs b/contracts/mars-staking/examples/schema.rs deleted file mode 100644 index aba4b5d82..000000000 --- a/contracts/mars-staking/examples/schema.rs +++ /dev/null @@ -1,23 +0,0 @@ -use std::env::current_dir; -use std::fs::create_dir_all; - -use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; - -use mars_staking::msg::{ExecuteMsg, InstantiateMsg, QueryMsg, ReceiveMsg}; -use mars_staking::{ClaimResponse, Config, GlobalState}; - -fn main() { - let mut out_dir = current_dir().unwrap(); - out_dir.push("schema"); - create_dir_all(&out_dir).unwrap(); - remove_schemas(&out_dir).unwrap(); - - export_schema(&schema_for!(InstantiateMsg), &out_dir); - export_schema(&schema_for!(ExecuteMsg), &out_dir); - export_schema(&schema_for!(ReceiveMsg), &out_dir); - export_schema(&schema_for!(QueryMsg), &out_dir); - - export_schema(&schema_for!(Config), &out_dir); - export_schema(&schema_for!(GlobalState), &out_dir); - export_schema(&schema_for!(ClaimResponse), &out_dir); -} diff --git a/contracts/mars-staking/schema/claim_response.json b/contracts/mars-staking/schema/claim_response.json deleted file mode 100644 index 9c144ff20..000000000 --- a/contracts/mars-staking/schema/claim_response.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ClaimResponse", - "description": "Response to Claim query", - "type": "object", - "properties": { - "claim": { - "description": "Existing claim for a given address. Will return None if it doesn't exist", - "anyOf": [ - { - "$ref": "#/definitions/Claim" - }, - { - "type": "null" - } - ] - } - }, - "definitions": { - "Claim": { - "description": "Unstaking cooldown data", - "type": "object", - "required": [ - "amount", - "cooldown_end_timestamp", - "created_at_block" - ], - "properties": { - "amount": { - "description": "Amount of Mars that the user is allowed to claim", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "cooldown_end_timestamp": { - "description": "Timestamp (in seconds) after which the claim is unlocked", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "created_at_block": { - "description": "Block when the claim was created (Used to apply slash events when claiming)", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/mars-staking/schema/config.json b/contracts/mars-staking/schema/config.json deleted file mode 100644 index 6a34ca27e..000000000 --- a/contracts/mars-staking/schema/config.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Config", - "description": "Protocol configuration", - "type": "object", - "required": [ - "address_provider_address", - "astroport_factory_address", - "astroport_max_spread", - "cooldown_duration", - "owner" - ], - "properties": { - "address_provider_address": { - "description": "Address provider address", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] - }, - "astroport_factory_address": { - "description": "Astroport factory contract address", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] - }, - "astroport_max_spread": { - "description": "Astroport max spread", - "allOf": [ - { - "$ref": "#/definitions/Decimal" - } - ] - }, - "cooldown_duration": { - "description": "Cooldown duration in seconds", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "owner": { - "description": "Contract owner", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] - } - }, - "definitions": { - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - } - } -} diff --git a/contracts/mars-staking/schema/execute_msg.json b/contracts/mars-staking/schema/execute_msg.json deleted file mode 100644 index 8037e9f42..000000000 --- a/contracts/mars-staking/schema/execute_msg.json +++ /dev/null @@ -1,186 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ExecuteMsg", - "anyOf": [ - { - "description": "Update staking config", - "type": "object", - "required": [ - "update_config" - ], - "properties": { - "update_config": { - "type": "object", - "required": [ - "config" - ], - "properties": { - "config": { - "$ref": "#/definitions/CreateOrUpdateConfig" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Implementation for cw20 receive msg", - "type": "object", - "required": [ - "receive" - ], - "properties": { - "receive": { - "$ref": "#/definitions/Cw20ReceiveMsg" - } - }, - "additionalProperties": false - }, - { - "description": "Close claim sending the claimable Mars to the specified address (sender is the default)", - "type": "object", - "required": [ - "claim" - ], - "properties": { - "claim": { - "type": "object", - "properties": { - "recipient": { - "type": [ - "string", - "null" - ] - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Transfer Mars, deducting it proportionally from both xMars holders and addresses with an open claim", - "type": "object", - "required": [ - "transfer_mars" - ], - "properties": { - "transfer_mars": { - "type": "object", - "required": [ - "amount", - "recipient" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "recipient": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Swap uusd on the contract to Mars. Meant for received protocol rewards in order for them to belong to xMars holders as underlying Mars.", - "type": "object", - "required": [ - "swap_uusd_to_mars" - ], - "properties": { - "swap_uusd_to_mars": { - "type": "object", - "properties": { - "amount": { - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - } - } - } - }, - "additionalProperties": false - } - ], - "definitions": { - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", - "type": "string" - }, - "CreateOrUpdateConfig": { - "type": "object", - "properties": { - "address_provider_address": { - "type": [ - "string", - "null" - ] - }, - "astroport_factory_address": { - "type": [ - "string", - "null" - ] - }, - "astroport_max_spread": { - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - }, - "cooldown_duration": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - }, - "owner": { - "type": [ - "string", - "null" - ] - } - } - }, - "Cw20ReceiveMsg": { - "description": "Cw20ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg", - "type": "object", - "required": [ - "amount", - "msg", - "sender" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "msg": { - "$ref": "#/definitions/Binary" - }, - "sender": { - "type": "string" - } - } - }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/mars-staking/schema/global_state.json b/contracts/mars-staking/schema/global_state.json deleted file mode 100644 index 8c4c5a697..000000000 --- a/contracts/mars-staking/schema/global_state.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "GlobalState", - "description": "Global State", - "type": "object", - "required": [ - "total_mars_for_claimers" - ], - "properties": { - "total_mars_for_claimers": { - "description": "Total amount of Mars belonging to open claims", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - } - }, - "definitions": { - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/mars-staking/schema/instantiate_msg.json b/contracts/mars-staking/schema/instantiate_msg.json deleted file mode 100644 index 169d1306a..000000000 --- a/contracts/mars-staking/schema/instantiate_msg.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InstantiateMsg", - "type": "object", - "required": [ - "config" - ], - "properties": { - "config": { - "$ref": "#/definitions/CreateOrUpdateConfig" - } - }, - "definitions": { - "CreateOrUpdateConfig": { - "type": "object", - "properties": { - "address_provider_address": { - "type": [ - "string", - "null" - ] - }, - "astroport_factory_address": { - "type": [ - "string", - "null" - ] - }, - "astroport_max_spread": { - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - }, - "cooldown_duration": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - }, - "owner": { - "type": [ - "string", - "null" - ] - } - } - }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - } - } -} diff --git a/contracts/mars-staking/schema/query_msg.json b/contracts/mars-staking/schema/query_msg.json deleted file mode 100644 index b76a7ed2a..000000000 --- a/contracts/mars-staking/schema/query_msg.json +++ /dev/null @@ -1,79 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryMsg", - "anyOf": [ - { - "description": "Get contract config", - "type": "object", - "required": [ - "config" - ], - "properties": { - "config": { - "type": "object" - } - }, - "additionalProperties": false - }, - { - "description": "Get contract global state", - "type": "object", - "required": [ - "global_state" - ], - "properties": { - "global_state": { - "type": "object" - } - }, - "additionalProperties": false - }, - { - "description": "Compute the amount of xMars token to be minted by staking 1 unit of Mars token. The ratio may be undefined, in which case we return `Ok(None)`", - "type": "object", - "required": [ - "x_mars_per_mars" - ], - "properties": { - "x_mars_per_mars": { - "type": "object" - } - }, - "additionalProperties": false - }, - { - "description": "Compute the amount of Mars token to be claimed by burning 1 unit of xMars token. The ratio may be undefined, in which case we return `Ok(None)`", - "type": "object", - "required": [ - "mars_per_x_mars" - ], - "properties": { - "mars_per_x_mars": { - "type": "object" - } - }, - "additionalProperties": false - }, - { - "description": "Get open claim for given user. If claim exists, slash events are applied to the amount so actual amount of Mars received is given.", - "type": "object", - "required": [ - "claim" - ], - "properties": { - "claim": { - "type": "object", - "required": [ - "user_address" - ], - "properties": { - "user_address": { - "type": "string" - } - } - } - }, - "additionalProperties": false - } - ] -} diff --git a/contracts/mars-staking/schema/receive_msg.json b/contracts/mars-staking/schema/receive_msg.json deleted file mode 100644 index 185c2ad48..000000000 --- a/contracts/mars-staking/schema/receive_msg.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ReceiveMsg", - "anyOf": [ - { - "description": "Stake Mars and mint xMars in return", - "type": "object", - "required": [ - "stake" - ], - "properties": { - "stake": { - "type": "object", - "properties": { - "recipient": { - "description": "Address to receive the xMars tokens. Set to sender if not specified", - "type": [ - "string", - "null" - ] - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Burn xMars and initiate a cooldown period on which the underlying Mars will be claimable. Only one open claim per address is allowed.", - "type": "object", - "required": [ - "unstake" - ], - "properties": { - "unstake": { - "type": "object", - "properties": { - "recipient": { - "description": "Address to claim the Mars tokens after cooldown. Set to sender is not specified", - "type": [ - "string", - "null" - ] - } - } - } - }, - "additionalProperties": false - } - ] -} diff --git a/contracts/mars-staking/src/contract.rs b/contracts/mars-staking/src/contract.rs deleted file mode 100644 index 57ba992de..000000000 --- a/contracts/mars-staking/src/contract.rs +++ /dev/null @@ -1,1579 +0,0 @@ -#[cfg(not(feature = "library"))] -use cosmwasm_std::entry_point; -use cosmwasm_std::{ - from_binary, to_binary, Addr, Binary, CosmosMsg, Deps, DepsMut, Env, MessageInfo, Order, - Response, StdError, StdResult, Storage, Uint128, WasmMsg, -}; -use cw20::{Cw20ExecuteMsg, Cw20ReceiveMsg}; -use cw_storage_plus::{Bound, U64Key}; - -use astroport::asset::AssetInfo; - -use mars_core::error::MarsError; -use mars_core::helpers::{ - cw20_get_balance, cw20_get_total_supply, option_string_to_addr, zero_address, -}; -use mars_core::math::decimal::Decimal; -use mars_core::swapping::execute_swap; - -use mars_core::address_provider::{self, MarsContract}; - -use crate::error::ContractError; -use crate::msg::{CreateOrUpdateConfig, ExecuteMsg, InstantiateMsg, QueryMsg, ReceiveMsg}; -use crate::state::{CLAIMS, CONFIG, GLOBAL_STATE, SLASH_EVENTS}; -use crate::{Claim, ClaimResponse, Config, GlobalState, SlashEvent}; - -// INSTANTIATE - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn instantiate( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - msg: InstantiateMsg, -) -> Result { - // Destructuring a struct’s fields into separate variables in order to force - // compile error if we add more params - let CreateOrUpdateConfig { - owner, - cooldown_duration, - address_provider_address, - astroport_factory_address, - astroport_max_spread, - } = msg.config; - - // All fields should be available - let available = owner.is_some() - && cooldown_duration.is_some() - && address_provider_address.is_some() - && astroport_factory_address.is_some() - && astroport_max_spread.is_some(); - - if !available { - return Err(MarsError::InstantiateParamsUnavailable {}.into()); - }; - - // Initialize config - let config = Config { - owner: option_string_to_addr(deps.api, owner, zero_address())?, - cooldown_duration: cooldown_duration.unwrap(), - address_provider_address: option_string_to_addr( - deps.api, - address_provider_address, - zero_address(), - )?, - astroport_factory_address: option_string_to_addr( - deps.api, - astroport_factory_address, - zero_address(), - )?, - astroport_max_spread: astroport_max_spread.unwrap(), - }; - - CONFIG.save(deps.storage, &config)?; - - // Initialize global state - GLOBAL_STATE.save( - deps.storage, - &GlobalState { - total_mars_for_claimers: Uint128::zero(), - }, - )?; - - Ok(Response::default()) -} - -// EXECUTE - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn execute( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: ExecuteMsg, -) -> Result { - match msg { - ExecuteMsg::Receive(cw20_msg) => Ok(execute_receive_cw20(deps, env, info, cw20_msg)?), - - ExecuteMsg::UpdateConfig { config } => Ok(execute_update_config(deps, info, config)?), - - ExecuteMsg::Claim { recipient } => Ok(execute_claim(deps, env, info, recipient)?), - - ExecuteMsg::TransferMars { recipient, amount } => { - Ok(execute_transfer_mars(deps, env, info, recipient, amount)?) - } - - ExecuteMsg::SwapUusdToMars { amount } => Ok(execute_swap_uusd_to_mars(deps, env, amount)?), - } -} - -pub fn execute_receive_cw20( - deps: DepsMut, - env: Env, - info: MessageInfo, - cw20_msg: Cw20ReceiveMsg, -) -> Result { - match from_binary(&cw20_msg.msg)? { - ReceiveMsg::Stake { recipient } => { - execute_stake(deps, env, info, cw20_msg.sender, recipient, cw20_msg.amount) - } - - ReceiveMsg::Unstake { recipient } => { - execute_unstake(deps, env, info, cw20_msg.sender, recipient, cw20_msg.amount) - } - } -} - -pub fn execute_update_config( - deps: DepsMut, - info: MessageInfo, - new_config: CreateOrUpdateConfig, -) -> Result { - let mut config = CONFIG.load(deps.storage)?; - - if info.sender != config.owner { - return Err(MarsError::Unauthorized {}); - } - - // Destructuring a struct’s fields into separate variables in order to force - // compile error if we add more params - let CreateOrUpdateConfig { - owner, - cooldown_duration, - address_provider_address, - astroport_factory_address, - astroport_max_spread, - } = new_config; - - // Update config - config.owner = option_string_to_addr(deps.api, owner, config.owner)?; - config.address_provider_address = option_string_to_addr( - deps.api, - address_provider_address, - config.address_provider_address, - )?; - config.astroport_factory_address = option_string_to_addr( - deps.api, - astroport_factory_address, - config.astroport_factory_address, - )?; - config.astroport_max_spread = astroport_max_spread.unwrap_or(config.astroport_max_spread); - config.cooldown_duration = cooldown_duration.unwrap_or(config.cooldown_duration); - - CONFIG.save(deps.storage, &config)?; - - let res = Response::new().add_attribute("action", "update_config"); - Ok(res) -} - -pub fn execute_stake( - deps: DepsMut, - env: Env, - info: MessageInfo, - staker: String, - option_recipient: Option, - stake_amount: Uint128, -) -> Result { - // check stake is valid - let config = CONFIG.load(deps.storage)?; - let global_state = GLOBAL_STATE.load(deps.storage)?; - - if stake_amount.is_zero() { - return Err(ContractError::StakeAmountZero {}); - } - - let staking_tokens_info = - get_staking_tokens_info(deps.as_ref(), &env, &config, &global_state, stake_amount)?; - - // Has to send Mars tokens - if info.sender != staking_tokens_info.mars_token_address { - return Err(MarsError::Unauthorized {}.into()); - } - - let xmars_per_mars_option = compute_xmars_per_mars(&staking_tokens_info)?; - - let mint_amount = if let Some(xmars_per_mars) = xmars_per_mars_option { - stake_amount * xmars_per_mars - } else { - stake_amount - }; - - let recipient = option_recipient.unwrap_or_else(|| staker.clone()); - - let res = Response::new() - .add_message(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: staking_tokens_info.xmars_token_address.to_string(), - funds: vec![], - msg: to_binary(&Cw20ExecuteMsg::Mint { - recipient: recipient.clone(), - amount: mint_amount, - })?, - })) - .add_attribute("action", "stake") - .add_attribute("staker", staker) - .add_attribute("mars_staked", stake_amount) - .add_attribute("xmars_minted", mint_amount) - .add_attribute("recipient", recipient); - - Ok(res) -} - -pub fn execute_unstake( - deps: DepsMut, - env: Env, - info: MessageInfo, - staker: String, - option_recipient: Option, - burn_amount: Uint128, -) -> Result { - // check if unstake is valid - let config = CONFIG.load(deps.storage)?; - let mut global_state = GLOBAL_STATE.load(deps.storage)?; - - let staking_tokens_info = - get_staking_tokens_info(deps.as_ref(), &env, &config, &global_state, Uint128::zero())?; - - if info.sender != staking_tokens_info.xmars_token_address { - return Err(MarsError::Unauthorized {}.into()); - } - if burn_amount.is_zero() { - return Err(ContractError::UnstakeAmountZero {}); - } - - let mars_per_xmars_option = compute_mars_per_xmars(&staking_tokens_info)?; - - let claimable_amount = if let Some(mars_per_xmars) = mars_per_xmars_option { - burn_amount * mars_per_xmars - } else { - return Err(StdError::generic_err("mars/xmars ratio is undefined").into()); - }; - - let claim = Claim { - created_at_block: env.block.height, - cooldown_end_timestamp: env.block.time.seconds() + config.cooldown_duration, - amount: claimable_amount, - }; - - let recipient = option_recipient - .unwrap_or_else(|| staker.clone()) - .to_lowercase(); - let recipient_addr = deps.api.addr_validate(&recipient)?; - - if CLAIMS.may_load(deps.storage, &recipient_addr)?.is_some() { - return Err(ContractError::UnstakeActiveClaim {}); - } - CLAIMS.save(deps.storage, &recipient_addr, &claim)?; - - global_state.total_mars_for_claimers = global_state - .total_mars_for_claimers - .checked_add(claimable_amount)?; - - if global_state.total_mars_for_claimers > staking_tokens_info.total_mars_in_staking_contract { - return Err(ContractError::MarsForClaimersOverflow {}); - } - - GLOBAL_STATE.save(deps.storage, &global_state)?; - - let res = Response::new() - .add_message(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: staking_tokens_info.xmars_token_address.to_string(), - funds: vec![], - msg: to_binary(&Cw20ExecuteMsg::Burn { - amount: burn_amount, - })?, - })) - .add_attribute("action", "unstake") - .add_attribute("staker", staker) - .add_attribute("recipient", recipient) - .add_attribute("xmars_burned", burn_amount) - .add_attribute("mars_claimable", claimable_amount); - Ok(res) -} - -pub fn execute_claim( - deps: DepsMut, - env: Env, - info: MessageInfo, - option_recipient: Option, -) -> Result { - let mut claim = CLAIMS.load(deps.storage, &info.sender)?; - - if claim.cooldown_end_timestamp > env.block.time.seconds() { - return Err(ContractError::ClaimCooldownNotEnded {}); - } - - apply_slash_events_to_claim(deps.storage, &mut claim)?; - - let mut global_state = GLOBAL_STATE.load(deps.storage)?; - global_state.total_mars_for_claimers = global_state - .total_mars_for_claimers - .checked_sub(claim.amount)?; - - CLAIMS.remove(deps.storage, &info.sender); - GLOBAL_STATE.save(deps.storage, &global_state)?; - - let config = CONFIG.load(deps.storage)?; - let mars_token_address = address_provider::helpers::query_address( - &deps.querier, - config.address_provider_address, - MarsContract::MarsToken, - )?; - - let recipient = option_recipient.unwrap_or_else(|| info.sender.to_string()); - - let res = Response::new() - .add_message(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: mars_token_address.to_string(), - funds: vec![], - msg: to_binary(&Cw20ExecuteMsg::Transfer { - recipient: recipient.clone(), - amount: claim.amount, - })?, - })) - .add_attribute("action", "claim") - .add_attribute("claimer", info.sender) - .add_attribute("mars_claimed", claim.amount) - .add_attribute("recipient", recipient); - Ok(res) -} - -pub fn execute_transfer_mars( - deps: DepsMut, - env: Env, - info: MessageInfo, - recipient_unchecked: String, - amount: Uint128, -) -> Result { - let config = CONFIG.load(deps.storage)?; - - if info.sender != config.owner { - return Err(MarsError::Unauthorized {}.into()); - } - - // Check there are no slash events on the same block - let slash_event_on_block = - SLASH_EVENTS.may_load(deps.storage, U64Key::new(env.block.height))?; - - if slash_event_on_block.is_some() { - return Err(ContractError::TransferMarsCannotHaveTwoSlashEventsOnBlock {}); - } - - let mars_token_address = address_provider::helpers::query_address( - &deps.querier, - config.address_provider_address, - MarsContract::MarsToken, - )?; - - let total_mars_in_staking_contract = cw20_get_balance( - &deps.querier, - mars_token_address.clone(), - env.contract.address, - )?; - - if amount > total_mars_in_staking_contract { - return Err(ContractError::TransferMarsAmountTooLarge {}); - } - - let slash_percentage = Decimal::from_ratio(amount, total_mars_in_staking_contract); - - SLASH_EVENTS.save( - deps.storage, - U64Key::new(env.block.height), - &SlashEvent { slash_percentage }, - )?; - - let mut global_state = GLOBAL_STATE.load(deps.storage)?; - global_state.total_mars_for_claimers = - global_state.total_mars_for_claimers * (Decimal::one() - slash_percentage); - GLOBAL_STATE.save(deps.storage, &global_state)?; - - let res = Response::new() - .add_message(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: mars_token_address.to_string(), - funds: vec![], - msg: to_binary(&Cw20ExecuteMsg::Transfer { - recipient: recipient_unchecked.clone(), - amount, - })?, - })) - .add_attribute("action", "transfer_mars") - .add_attribute("recipient", recipient_unchecked) - .add_attribute("amount", amount) - .add_attribute("slash_percentage", slash_percentage.to_string()) - .add_attribute( - "new_total_mars_for_claimers", - global_state.total_mars_for_claimers, - ); - - Ok(res) -} - -pub fn execute_swap_uusd_to_mars( - deps: DepsMut, - env: Env, - amount: Option, -) -> Result { - let config = CONFIG.load(deps.storage)?; - - let offer_asset_info = AssetInfo::NativeToken { - denom: "uusd".to_string(), - }; - - let mars_token_address = address_provider::helpers::query_address( - &deps.querier, - config.address_provider_address, - MarsContract::MarsToken, - )?; - - let ask_asset_info = AssetInfo::Token { - contract_addr: mars_token_address, - }; - - let astroport_max_spread = Some(config.astroport_max_spread); - - Ok(execute_swap( - deps, - env, - offer_asset_info, - ask_asset_info, - amount, - config.astroport_factory_address, - astroport_max_spread, - )?) -} - -// QUERY - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::Config {} => to_binary(&query_config(deps)?), - QueryMsg::GlobalState {} => to_binary(&query_global_state(deps)?), - QueryMsg::XMarsPerMars {} => to_binary(&query_xmars_per_mars(deps, env)?), - QueryMsg::MarsPerXMars {} => to_binary(&query_mars_per_xmars(deps, env)?), - QueryMsg::Claim { user_address } => to_binary(&query_claim(deps, env, user_address)?), - } -} - -fn query_config(deps: Deps) -> StdResult { - let config = CONFIG.load(deps.storage)?; - Ok(config) -} - -fn query_global_state(deps: Deps) -> StdResult { - let global_state = GLOBAL_STATE.load(deps.storage)?; - Ok(global_state) -} - -fn query_xmars_per_mars(deps: Deps, env: Env) -> StdResult> { - let config = CONFIG.load(deps.storage)?; - let global_state = GLOBAL_STATE.load(deps.storage)?; - - let staking_tokens_info = - get_staking_tokens_info(deps, &env, &config, &global_state, Uint128::zero())?; - - compute_xmars_per_mars(&staking_tokens_info) -} - -fn query_mars_per_xmars(deps: Deps, env: Env) -> StdResult> { - let config = CONFIG.load(deps.storage)?; - let global_state = GLOBAL_STATE.load(deps.storage)?; - - let staking_tokens_info = - get_staking_tokens_info(deps, &env, &config, &global_state, Uint128::zero())?; - - compute_mars_per_xmars(&staking_tokens_info) -} - -fn query_claim(deps: Deps, _env: Env, user_address_unchecked: String) -> StdResult { - let user_address = deps.api.addr_validate(&user_address_unchecked)?; - let option_claim = CLAIMS.may_load(deps.storage, &user_address)?; - - if let Some(mut claim) = option_claim { - apply_slash_events_to_claim(deps.storage, &mut claim)?; - Ok(ClaimResponse { claim: Some(claim) }) - } else { - Ok(ClaimResponse { - claim: option_claim, - }) - } -} - -// HELPERS - -/// Gets mars and xmars token addresses from address provider and returns them in a tuple. -fn get_token_addresses(deps: Deps, config: &Config) -> StdResult<(Addr, Addr)> { - let mut addresses_query = address_provider::helpers::query_addresses( - &deps.querier, - config.address_provider_address.clone(), - vec![MarsContract::MarsToken, MarsContract::XMarsToken], - ) - .map_err(|_| StdError::generic_err("Failed to query token addresses"))?; - - let xmars_token_address = addresses_query.pop().unwrap(); - let mars_token_address = addresses_query.pop().unwrap(); - - Ok((mars_token_address, xmars_token_address)) -} - -fn apply_slash_events_to_claim(storage: &dyn Storage, claim: &mut Claim) -> StdResult<()> { - let start = Some(Bound::inclusive(U64Key::new(claim.created_at_block))); - - // Slash events are applied in chronological order - for kv in SLASH_EVENTS.range(storage, start, None, Order::Ascending) { - let (_, slash_event) = kv?; - - claim.amount = claim.amount * (Decimal::one() - slash_event.slash_percentage); - } - Ok(()) -} - -struct StakingTokensInfo { - mars_token_address: Addr, - xmars_token_address: Addr, - total_mars_in_staking_contract: Uint128, - total_mars_for_stakers: Uint128, - total_xmars_supply: Uint128, -} - -/// Gets mars and xmars info to check addresses and compute ratios -/// mars_to_deduct accounts for mars that are already in -/// the contract but should not be taken into account for the net amount -/// in the balance -fn get_staking_tokens_info( - deps: Deps, - env: &Env, - config: &Config, - global_state: &GlobalState, - mars_to_deduct: Uint128, -) -> StdResult { - let (mars_token_address, xmars_token_address) = get_token_addresses(deps, config)?; - - let total_mars_in_staking_contract = cw20_get_balance( - &deps.querier, - mars_token_address.clone(), - env.contract.address.clone(), - )? - .checked_sub(mars_to_deduct)?; - let total_mars_for_stakers = - total_mars_in_staking_contract.checked_sub(global_state.total_mars_for_claimers)?; - - let total_xmars_supply = cw20_get_total_supply(&deps.querier, xmars_token_address.clone())?; - - Ok(StakingTokensInfo { - mars_token_address, - xmars_token_address, - total_mars_in_staking_contract, - total_mars_for_stakers, - total_xmars_supply, - }) -} - -/// Compute the ratio between xMars and Mars token in terms of how many xMars token will be minted -/// by staking 1 Mars token. -fn compute_xmars_per_mars(staking_tokens_info: &StakingTokensInfo) -> StdResult> { - let total_mars_for_stakers = staking_tokens_info.total_mars_for_stakers; - let total_xmars_supply = staking_tokens_info.total_xmars_supply; - // Mars/xMars ratio is undefined if either `total_mars_for_stakers` or `total_xmars_supply` is zero - // in this case, we return None - if total_mars_for_stakers.is_zero() || total_xmars_supply.is_zero() { - Ok(None) - } else { - Ok(Some(Decimal::from_ratio( - staking_tokens_info.total_xmars_supply, - staking_tokens_info.total_mars_for_stakers, - ))) - } -} - -/// Compute the ratio between Mars and xMars in terms of how many Mars tokens can be claimed by -/// burning 1 xMars token. -/// -/// This is calculated by simply taking the inversion of `xmars_per_mars`. -fn compute_mars_per_xmars(staking_tokens_info: &StakingTokensInfo) -> StdResult> { - let total_mars_for_stakers = staking_tokens_info.total_mars_for_stakers; - let total_xmars_supply = staking_tokens_info.total_xmars_supply; - - // Mars/xMars ratio is undefined if either `total_mars_for_stakers` or `total_xmars_supply` is zero - // in this case, we return None - if total_mars_for_stakers.is_zero() || total_xmars_supply.is_zero() { - Ok(None) - } else { - Ok(Some(Decimal::from_ratio( - total_mars_for_stakers, - total_xmars_supply, - ))) - } -} - -// TESTS - -#[cfg(test)] -mod tests { - use super::*; - use cosmwasm_std::testing::{mock_info, MockApi, MockStorage, MOCK_CONTRACT_ADDR}; - use cosmwasm_std::{ - attr, Addr, Coin, CosmosMsg, Decimal as StdDecimal, OwnedDeps, StdError, SubMsg, Timestamp, - }; - use mars_core::testing::{ - mock_dependencies, mock_env, mock_env_at_block_height, mock_env_at_block_time, - MarsMockQuerier, MockEnvParams, - }; - - const TEST_COOLDOWN_DURATION: u64 = 1000; - - #[test] - fn test_proper_initialization() { - let mut deps = mock_dependencies(&[]); - - // * - // init config with empty params - // * - let empty_config = CreateOrUpdateConfig { - owner: None, - address_provider_address: None, - astroport_factory_address: None, - astroport_max_spread: None, - cooldown_duration: None, - }; - let msg = InstantiateMsg { - config: empty_config, - }; - let info = mock_info("owner", &[]); - let response = instantiate( - deps.as_mut(), - mock_env(MockEnvParams::default()), - info.clone(), - msg, - ) - .unwrap_err(); - assert_eq!( - response, - ContractError::Mars(MarsError::InstantiateParamsUnavailable {}) - ); - - let config = CreateOrUpdateConfig { - owner: Some(String::from("owner")), - address_provider_address: Some(String::from("address_provider")), - astroport_factory_address: Some(String::from("astroport_factory")), - astroport_max_spread: Some(StdDecimal::from_ratio(1u128, 100u128)), - cooldown_duration: Some(20), - }; - let msg = InstantiateMsg { config }; - - let res = - instantiate(deps.as_mut(), mock_env(MockEnvParams::default()), info, msg).unwrap(); - assert_eq!(0, res.messages.len()); - - let config = CONFIG.load(deps.as_ref().storage).unwrap(); - assert_eq!(config.owner, Addr::unchecked("owner")); - assert_eq!( - config.address_provider_address, - Addr::unchecked("address_provider") - ); - } - - #[test] - fn test_update_config() { - let mut deps = mock_dependencies(&[]); - - // * - // init config with valid params - // * - let init_config = CreateOrUpdateConfig { - owner: Some(String::from("owner")), - address_provider_address: Some(String::from("address_provider")), - astroport_factory_address: Some(String::from("astroport_factory")), - astroport_max_spread: Some(StdDecimal::from_ratio(1u128, 100u128)), - cooldown_duration: Some(20), - }; - let msg = InstantiateMsg { - config: init_config.clone(), - }; - let info = mock_info("owner", &[]); - let _res = - instantiate(deps.as_mut(), mock_env(MockEnvParams::default()), info, msg).unwrap(); - - // * - // non owner is not authorized - // * - let msg = ExecuteMsg::UpdateConfig { - config: init_config, - }; - let info = mock_info("somebody", &[]); - let error_res = - execute(deps.as_mut(), mock_env(MockEnvParams::default()), info, msg).unwrap_err(); - assert_eq!(error_res, ContractError::Mars(MarsError::Unauthorized {})); - - // * - // update config with all new params - // * - let config = CreateOrUpdateConfig { - owner: Some(String::from("new_owner")), - address_provider_address: Some(String::from("new_address_provider")), - astroport_factory_address: Some(String::from("new_factory")), - astroport_max_spread: Some(StdDecimal::from_ratio(2u128, 100u128)), - cooldown_duration: Some(200), - }; - let msg = ExecuteMsg::UpdateConfig { - config: config.clone(), - }; - let info = mock_info("owner", &[]); - // we can just call .unwrap() to assert this was a success - let res = execute(deps.as_mut(), mock_env(MockEnvParams::default()), info, msg).unwrap(); - assert_eq!(0, res.messages.len()); - - // Read config from state - let new_config = CONFIG.load(deps.as_ref().storage).unwrap(); - - assert_eq!(new_config.owner, "new_owner"); - assert_eq!(new_config.address_provider_address, "new_address_provider"); - assert_eq!(new_config.astroport_factory_address, "new_factory"); - assert_eq!( - new_config.cooldown_duration, - config.cooldown_duration.unwrap() - ); - } - - #[test] - fn test_stake() { - let mut deps = th_setup(&[]); - - // no Mars in pool - // stake X Mars -> should receive X xMars - { - let msg = ExecuteMsg::Receive(Cw20ReceiveMsg { - sender: String::from("staker"), - amount: Uint128::new(2_000_000), - msg: to_binary(&ReceiveMsg::Stake { recipient: None }).unwrap(), - }); - - deps.querier.set_cw20_balances( - Addr::unchecked("mars_token"), - &[(Addr::unchecked(MOCK_CONTRACT_ADDR), Uint128::new(2_000_000))], - ); - - deps.querier - .set_cw20_total_supply(Addr::unchecked("xmars_token"), Uint128::zero()); - - let info = mock_info("mars_token", &[]); - let res = execute( - deps.as_mut(), - mock_env(MockEnvParams::default()), - info.clone(), - msg, - ) - .unwrap(); - - assert_eq!( - vec![SubMsg::new(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: String::from("xmars_token"), - funds: vec![], - msg: to_binary(&Cw20ExecuteMsg::Mint { - recipient: String::from("staker"), - amount: Uint128::new(2_000_000), - }) - .unwrap(), - }))], - res.messages - ); - assert_eq!( - vec![ - attr("action", "stake"), - attr("staker", String::from("staker")), - attr("mars_staked", 2_000_000.to_string()), - attr("xmars_minted", 2_000_000.to_string()), - attr("recipient", String::from("staker")), - ], - res.attributes - ); - } - - // Some Mars in pool and some xMars supply - // * stake Mars -> should receive less xMars - // * set recipient -> should send xMars to recipient - // * some open claims -> do not count on staked mars - { - let stake_amount = Uint128::new(2_000_000); - let mars_in_contract = Uint128::new(4_000_000); - let xmars_supply = Uint128::new(1_000_000); - let total_mars_for_claimers = Uint128::new(500_000); - - GLOBAL_STATE - .save( - &mut deps.storage, - &GlobalState { - total_mars_for_claimers: total_mars_for_claimers, - }, - ) - .unwrap(); - - deps.querier.set_cw20_balances( - Addr::unchecked("mars_token"), - &[(Addr::unchecked(MOCK_CONTRACT_ADDR), mars_in_contract)], - ); - - deps.querier - .set_cw20_total_supply(Addr::unchecked("xmars_token"), xmars_supply); - - let msg = ExecuteMsg::Receive(Cw20ReceiveMsg { - msg: to_binary(&ReceiveMsg::Stake { - recipient: Some(String::from("recipient")), - }) - .unwrap(), - - sender: String::from("staker"), - amount: stake_amount, - }); - let info = mock_info("mars_token", &[]); - - let res = - execute(deps.as_mut(), mock_env(MockEnvParams::default()), info, msg).unwrap(); - - let expected_minted_xmars = stake_amount.multiply_ratio( - xmars_supply, - mars_in_contract - stake_amount - total_mars_for_claimers, - ); - - assert_eq!( - vec![SubMsg::new(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: String::from("xmars_token"), - funds: vec![], - msg: to_binary(&Cw20ExecuteMsg::Mint { - recipient: String::from("recipient"), - amount: expected_minted_xmars, - }) - .unwrap(), - }))], - res.messages - ); - assert_eq!( - vec![ - attr("action", "stake"), - attr("staker", String::from("staker")), - attr("mars_staked", stake_amount), - attr("xmars_minted", expected_minted_xmars), - attr("recipient", String::from("recipient")), - ], - res.attributes - ); - } - - // stake other token -> Unauthorized - { - let msg = ExecuteMsg::Receive(Cw20ReceiveMsg { - sender: String::from("staker"), - amount: Uint128::new(2_000_000), - msg: to_binary(&ReceiveMsg::Stake { recipient: None }).unwrap(), - }); - - let info = mock_info("other_token", &[]); - let res_error = - execute(deps.as_mut(), mock_env(MockEnvParams::default()), info, msg).unwrap_err(); - assert_eq!(res_error, ContractError::Mars(MarsError::Unauthorized {})); - } - } - - #[test] - fn test_unstake() { - let mut deps = th_setup(&[]); - - // setup variables for unstake - let unstake_amount = Uint128::new(1_000_000); - let unstake_mars_in_contract = Uint128::new(4_000_000); - let unstake_xmars_supply = Uint128::new(3_000_000); - let unstake_height = 123456; - let unstake_time = 1_000_000_000; - let env = mock_env(MockEnvParams { - block_height: unstake_height, - block_time: Timestamp::from_seconds(unstake_time), - }); - let initial_mars_for_claimers = Uint128::new(700_000); - let mut mars_for_claimers = initial_mars_for_claimers; - - deps.querier.set_cw20_balances( - Addr::unchecked("mars_token"), - &[( - Addr::unchecked(MOCK_CONTRACT_ADDR), - unstake_mars_in_contract, - )], - ); - deps.querier - .set_cw20_total_supply(Addr::unchecked("xmars_token"), unstake_xmars_supply); - GLOBAL_STATE - .save( - &mut deps.storage, - &GlobalState { - total_mars_for_claimers: initial_mars_for_claimers, - }, - ) - .unwrap(); - - // unstake other token -> Unauthorized - { - let msg = ExecuteMsg::Receive(Cw20ReceiveMsg { - msg: to_binary(&ReceiveMsg::Unstake { - recipient: Some(String::from("recipient")), - }) - .unwrap(), - sender: String::from("staker"), - amount: unstake_amount, - }); - let info = mock_info("other_token", &[]); - let res_error = execute(deps.as_mut(), env.clone(), info, msg.clone()).unwrap_err(); - assert_eq!(res_error, ContractError::Mars(MarsError::Unauthorized {})); - } - - // valid unstake - { - let msg = ExecuteMsg::Receive(Cw20ReceiveMsg { - msg: to_binary(&ReceiveMsg::Unstake { - recipient: Some(String::from("recipient")), - }) - .unwrap(), - sender: String::from("staker"), - amount: unstake_amount, - }); - let info = mock_info("xmars_token", &[]); - - let res = execute(deps.as_mut(), env.clone(), info, msg.clone()).unwrap(); - - let expected_claimable_mars = unstake_amount.multiply_ratio( - unstake_mars_in_contract - initial_mars_for_claimers, - unstake_xmars_supply, - ); - - assert_eq!( - vec![SubMsg::new(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: String::from("xmars_token"), - funds: vec![], - msg: to_binary(&Cw20ExecuteMsg::Burn { - amount: unstake_amount, - }) - .unwrap(), - })),], - res.messages - ); - assert_eq!( - vec![ - attr("action", "unstake"), - attr("staker", String::from("staker")), - attr("recipient", String::from("recipient")), - attr("xmars_burned", unstake_amount), - attr("mars_claimable", expected_claimable_mars), - ], - res.attributes - ); - - let claim = CLAIMS - .load(&deps.storage, &Addr::unchecked("recipient")) - .unwrap(); - - assert_eq!( - claim, - Claim { - created_at_block: unstake_height, - cooldown_end_timestamp: unstake_time + TEST_COOLDOWN_DURATION, - amount: expected_claimable_mars, - } - ); - - mars_for_claimers += expected_claimable_mars; - - let global_state = GLOBAL_STATE.load(&deps.storage).unwrap(); - - assert_eq!(global_state.total_mars_for_claimers, mars_for_claimers); - } - - // cannot unstake again (recipient has an open claim) - { - let msg = ExecuteMsg::Receive(Cw20ReceiveMsg { - msg: to_binary(&ReceiveMsg::Unstake { - recipient: Some(String::from("recipient")), - }) - .unwrap(), - sender: String::from("staker"), - amount: unstake_amount, - }); - let info = mock_info("xmars_token", &[]); - - let err = execute(deps.as_mut(), env.clone(), info, msg.clone()).unwrap_err(); - - assert_eq!(err, ContractError::UnstakeActiveClaim {}); - } - - // unstake again, but use `None` as recipient - // recipient should default to staker, which does not have an open claim - { - let msg = ExecuteMsg::Receive(Cw20ReceiveMsg { - msg: to_binary(&ReceiveMsg::Unstake { recipient: None }).unwrap(), - sender: String::from("staker"), - amount: unstake_amount, - }); - let info = mock_info("xmars_token", &[]); - - let res = execute(deps.as_mut(), env, info, msg.clone()).unwrap(); - - assert_eq!(attr("recipient", String::from("staker")), res.attributes[2]); - - let expected_claimable_mars = unstake_amount.multiply_ratio( - unstake_mars_in_contract - mars_for_claimers, - unstake_xmars_supply, - ); - - let claim = CLAIMS - .load(&deps.storage, &Addr::unchecked("staker")) - .unwrap(); - - assert_eq!( - claim, - Claim { - created_at_block: unstake_height, - cooldown_end_timestamp: unstake_time + TEST_COOLDOWN_DURATION, - amount: expected_claimable_mars, - } - ); - - mars_for_claimers += expected_claimable_mars; - - let global_state = GLOBAL_STATE.load(&deps.storage).unwrap(); - - assert_eq!(global_state.total_mars_for_claimers, mars_for_claimers); - } - } - - #[test] - fn test_claim_with_recipient_is_case_insensitive() { - let mut deps = th_setup(&[]); - - // setup variables for unstake - let unstake_amount = Uint128::new(1_200_000); - let unstake_mars_in_contract = Uint128::new(3_000_000); - let unstake_xmars_supply = Uint128::new(2_500_000); - let unstake_height = 123456; - let unstake_time = 1_000_000_000; - let env = mock_env(MockEnvParams { - block_height: unstake_height, - block_time: Timestamp::from_seconds(unstake_time), - }); - let initial_mars_for_claimers = Uint128::new(700_000); - let mut mars_for_claimers = initial_mars_for_claimers; - - deps.querier.set_cw20_balances( - Addr::unchecked("mars_token"), - &[( - Addr::unchecked(MOCK_CONTRACT_ADDR), - unstake_mars_in_contract, - )], - ); - deps.querier - .set_cw20_total_supply(Addr::unchecked("xmars_token"), unstake_xmars_supply); - GLOBAL_STATE - .save( - &mut deps.storage, - &GlobalState { - total_mars_for_claimers: initial_mars_for_claimers, - }, - ) - .unwrap(); - // valid unstake - { - let msg = ExecuteMsg::Receive(Cw20ReceiveMsg { - msg: to_binary(&ReceiveMsg::Unstake { - recipient: Some(String::from("reCipient")), - }) - .unwrap(), - sender: String::from("staker"), - amount: unstake_amount, - }); - let info = mock_info("xmars_token", &[]); - - let res = execute(deps.as_mut(), env.clone(), info, msg.clone()).unwrap(); - - let expected_claimable_mars = unstake_amount.multiply_ratio( - unstake_mars_in_contract - initial_mars_for_claimers, - unstake_xmars_supply, - ); - - assert_eq!( - vec![SubMsg::new(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: String::from("xmars_token"), - funds: vec![], - msg: to_binary(&Cw20ExecuteMsg::Burn { - amount: unstake_amount, - }) - .unwrap(), - })),], - res.messages - ); - assert_eq!( - vec![ - attr("action", "unstake"), - attr("staker", String::from("staker")), - attr("recipient", String::from("recipient")), - attr("xmars_burned", unstake_amount), - attr("mars_claimable", expected_claimable_mars), - ], - res.attributes - ); - - let claim_upper = CLAIMS - .may_load(&deps.storage, &Addr::unchecked("reCipient")) - .unwrap(); - - assert_eq!(claim_upper, None); - - let claim = CLAIMS - .load(&deps.storage, &Addr::unchecked("recipient")) - .unwrap(); - - assert_eq!( - claim, - Claim { - created_at_block: unstake_height, - cooldown_end_timestamp: unstake_time + TEST_COOLDOWN_DURATION, - amount: expected_claimable_mars, - } - ); - - mars_for_claimers += expected_claimable_mars; - - let global_state = GLOBAL_STATE.load(&deps.storage).unwrap(); - - assert_eq!(global_state.total_mars_for_claimers, mars_for_claimers); - } - } - - #[test] - fn test_claim() { - let mut deps = th_setup(&[]); - let initial_mars_for_claimers = Uint128::new(4_000_000_000000); - let claimer_address = Addr::unchecked("claimer"); - let claim = Claim { - amount: Uint128::new(5_000_000000), - created_at_block: 123456_u64, - cooldown_end_timestamp: 1_000_000_u64, - }; - - CLAIMS - .save(&mut deps.storage, &claimer_address, &claim) - .unwrap(); - GLOBAL_STATE - .save( - &mut deps.storage, - &GlobalState { - total_mars_for_claimers: initial_mars_for_claimers, - }, - ) - .unwrap(); - - // Claim previous to cooldown end fails - { - let info = mock_info("claimer", &[]); - let env = mock_env_at_block_time(999_999); - let msg = ExecuteMsg::Claim { recipient: None }; - let err = execute(deps.as_mut(), env, info, msg).unwrap_err(); - assert_eq!(err, ContractError::ClaimCooldownNotEnded {}); - } - - // Query claim gives correct claim - { - let queried_claim = query_claim( - deps.as_ref(), - mock_env_at_block_time(1_233_000), - "claimer".to_string(), - ) - .unwrap(); - assert_eq!(claim.amount, queried_claim.claim.unwrap().amount); - } - - // Successful claim - { - let info = mock_info("claimer", &[]); - let env = mock_env_at_block_time(1_233_000); - let msg = ExecuteMsg::Claim { recipient: None }; - let res = execute(deps.as_mut(), env, info, msg).unwrap(); - - assert_eq!( - res.messages, - vec![SubMsg::new(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: String::from("mars_token"), - funds: vec![], - msg: to_binary(&Cw20ExecuteMsg::Transfer { - recipient: claimer_address.clone().to_string(), - amount: claim.amount, - }) - .unwrap(), - })),] - ); - - assert_eq!( - res.attributes, - vec![ - attr("action", "claim"), - attr("claimer", "claimer"), - attr("mars_claimed", claim.amount), - attr("recipient", "claimer"), - ] - ); - - let queried_claim = query_claim( - deps.as_ref(), - mock_env_at_block_time(1_233_000), - "claimer".to_string(), - ) - .unwrap(); - assert_eq!(None, queried_claim.claim); - - let global_state = GLOBAL_STATE.load(&deps.storage).unwrap(); - assert_eq!( - global_state.total_mars_for_claimers, - initial_mars_for_claimers - claim.amount - ); - assert_eq!( - CLAIMS.may_load(&deps.storage, &claimer_address).unwrap(), - None - ); - } - - // Claim now fails (it was deleted) - { - let info = mock_info("claimer", &[]); - let env = mock_env_at_block_time(1_233_000); - let msg = ExecuteMsg::Claim { recipient: None }; - let err = execute(deps.as_mut(), env, info, msg).unwrap_err(); - assert!( - matches!(err, ContractError::Std(StdError::NotFound { .. })), - "Expected StdError::NotFound, received {}", - err - ); - } - } - - #[test] - fn test_claim_with_slash() { - let mut deps = th_setup(&[]); - let claimer_address = Addr::unchecked("claimer"); - - let initial_mars_for_claimers = Uint128::new(100_000_000_00000); - let initial_claim_amount = Uint128::new(5_000_000000); - let claim = Claim { - amount: initial_claim_amount, - created_at_block: 100_000_u64, - cooldown_end_timestamp: 1_000_000_u64, - }; - - let claim_height = 150_000_u64; - let claim_time = 1_000_000_u64; - let env = mock_env(MockEnvParams { - block_height: claim_height, - block_time: Timestamp::from_seconds(claim_time), - }); - - let slash_percentage_one = Decimal::from_ratio(1_u128, 2_u128); - let slash_percentage_two = Decimal::from_ratio(1_u128, 3_u128); - - CLAIMS - .save(&mut deps.storage, &claimer_address, &claim) - .unwrap(); - GLOBAL_STATE - .save( - &mut deps.storage, - &GlobalState { - total_mars_for_claimers: initial_mars_for_claimers, - }, - ) - .unwrap(); - - SLASH_EVENTS - .save( - &mut deps.storage, - U64Key::new(claim.created_at_block - 1), - &SlashEvent { - slash_percentage: Decimal::from_ratio(80_u128, 100_u128), - }, - ) - .unwrap(); - SLASH_EVENTS - .save( - &mut deps.storage, - U64Key::new(claim.created_at_block), - &SlashEvent { - slash_percentage: slash_percentage_one, - }, - ) - .unwrap(); - - // one slash (slashes previous to claim don't count) - // set other as recipient - { - let expected_claim_amount = - initial_claim_amount * (Decimal::one() - slash_percentage_one); - let queried_claim = query_claim( - deps.as_ref(), - mock_env_at_block_time(1_233_000), - "claimer".to_string(), - ) - .unwrap(); - assert_eq!(expected_claim_amount, queried_claim.claim.unwrap().amount); - - let info = mock_info("claimer", &[]); - let msg = ExecuteMsg::Claim { - recipient: Some("recipient".to_string()), - }; - let res = execute(deps.as_mut(), env.clone(), info, msg).unwrap(); - - assert_eq!( - res.messages, - vec![SubMsg::new(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: String::from("mars_token"), - funds: vec![], - msg: to_binary(&Cw20ExecuteMsg::Transfer { - recipient: "recipient".to_string(), - amount: expected_claim_amount, - }) - .unwrap(), - })),] - ); - - assert_eq!( - res.attributes, - vec![ - attr("action", "claim"), - attr("claimer", "claimer"), - attr("mars_claimed", expected_claim_amount), - attr("recipient", "recipient"), - ] - ); - - let global_state = GLOBAL_STATE.load(&deps.storage).unwrap(); - assert_eq!( - global_state.total_mars_for_claimers, - initial_mars_for_claimers - expected_claim_amount - ); - assert_eq!( - CLAIMS.may_load(&deps.storage, &claimer_address).unwrap(), - None - ); - } - - // create claim again as previous was deleted - CLAIMS - .save(&mut deps.storage, &claimer_address, &claim) - .unwrap(); - GLOBAL_STATE - .save( - &mut deps.storage, - &GlobalState { - total_mars_for_claimers: initial_mars_for_claimers, - }, - ) - .unwrap(); - SLASH_EVENTS - .save( - &mut deps.storage, - U64Key::new(claim.created_at_block + 200), - &SlashEvent { - slash_percentage: slash_percentage_two, - }, - ) - .unwrap(); - - // two slashes - { - let expected_claim_amount = (initial_claim_amount - * (Decimal::one() - slash_percentage_one)) - * (Decimal::one() - slash_percentage_two); - let queried_claim = query_claim( - deps.as_ref(), - mock_env_at_block_time(1_233_000), - "claimer".to_string(), - ) - .unwrap(); - assert_eq!(expected_claim_amount, queried_claim.claim.unwrap().amount); - - let info = mock_info("claimer", &[]); - let msg = ExecuteMsg::Claim { recipient: None }; - let res = execute(deps.as_mut(), env.clone(), info, msg).unwrap(); - - assert_eq!( - res.messages, - vec![SubMsg::new(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: String::from("mars_token"), - funds: vec![], - msg: to_binary(&Cw20ExecuteMsg::Transfer { - recipient: "claimer".to_string(), - amount: expected_claim_amount, - }) - .unwrap(), - })),] - ); - - assert_eq!( - res.attributes, - vec![ - attr("action", "claim"), - attr("claimer", "claimer"), - attr("mars_claimed", expected_claim_amount), - attr("recipient", "claimer"), - ] - ); - - let global_state = GLOBAL_STATE.load(&deps.storage).unwrap(); - assert_eq!( - global_state.total_mars_for_claimers, - initial_mars_for_claimers - expected_claim_amount - ); - assert_eq!( - CLAIMS.may_load(&deps.storage, &claimer_address).unwrap(), - None - ); - } - } - - #[test] - fn test_transfer_mars() { - let mut deps = th_setup(&[]); - let initial_mars_for_claimers = Uint128::new(4_000_000_000000); - let initial_mars_in_contract = Uint128::new(10_000_000_000000); - let transfer_amount = Uint128::new(4_000_000_000000); - let transfer_block = 123456_u64; - - deps.querier.set_cw20_balances( - Addr::unchecked("mars_token"), - &[( - Addr::unchecked(MOCK_CONTRACT_ADDR), - initial_mars_in_contract, - )], - ); - - GLOBAL_STATE - .save( - &mut deps.storage, - &GlobalState { - total_mars_for_claimers: initial_mars_for_claimers, - }, - ) - .unwrap(); - - // Transfer by non owner fails - { - let env = mock_env(MockEnvParams::default()); - let info = mock_info("anyone", &[]); - let msg = ExecuteMsg::TransferMars { - recipient: "recipient".to_string(), - amount: transfer_amount, - }; - let err = execute(deps.as_mut(), env, info, msg).unwrap_err(); - assert_eq!(err, ContractError::Mars(MarsError::Unauthorized {})); - } - - // Transfer big amount fails - { - let env = mock_env(MockEnvParams::default()); - let info = mock_info("owner", &[]); - let msg = ExecuteMsg::TransferMars { - recipient: "recipient".to_string(), - amount: initial_mars_in_contract + Uint128::new(10), - }; - let err = execute(deps.as_mut(), env, info, msg).unwrap_err(); - assert_eq!(err, ContractError::TransferMarsAmountTooLarge {}); - } - - // Successful transfer - { - let env = mock_env_at_block_height(transfer_block); - let info = mock_info("owner", &[]); - let msg = ExecuteMsg::TransferMars { - recipient: "recipient".to_string(), - amount: transfer_amount, - }; - let res = execute(deps.as_mut(), env, info, msg).unwrap(); - assert_eq!( - res.messages, - vec![SubMsg::new(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: String::from("mars_token"), - funds: vec![], - msg: to_binary(&Cw20ExecuteMsg::Transfer { - recipient: "recipient".to_string(), - amount: transfer_amount, - }) - .unwrap(), - })),] - ); - - let expected_slash_percentage = - Decimal::from_ratio(transfer_amount, initial_mars_in_contract); - - // should be reduced proportionally - let expected_total_mars_for_claimers = initial_mars_for_claimers.multiply_ratio( - initial_mars_in_contract - transfer_amount, - initial_mars_in_contract, - ); - - assert_eq!( - res.attributes, - vec![ - attr("action", "transfer_mars"), - attr("recipient", "recipient"), - attr("amount", transfer_amount), - attr("slash_percentage", expected_slash_percentage.to_string()), - attr( - "new_total_mars_for_claimers", - expected_total_mars_for_claimers - ), - ] - ); - - let slash_event = SLASH_EVENTS - .load(&deps.storage, U64Key::new(transfer_block)) - .unwrap(); - assert_eq!( - slash_event, - SlashEvent { - slash_percentage: expected_slash_percentage - } - ); - - let global_state = GLOBAL_STATE.load(&deps.storage).unwrap(); - assert_eq!( - global_state.total_mars_for_claimers, - expected_total_mars_for_claimers - ); - } - - // Transfer on same block fails - { - let env = mock_env_at_block_height(transfer_block); - let info = mock_info("owner", &[]); - let msg = ExecuteMsg::TransferMars { - recipient: "recipient".to_string(), - amount: Uint128::new(200_000u128), - }; - let err = execute(deps.as_mut(), env, info, msg).unwrap_err(); - - assert_eq!( - err, - ContractError::TransferMarsCannotHaveTwoSlashEventsOnBlock {} - ) - } - } - - // TEST HELPERS - fn th_setup(contract_balances: &[Coin]) -> OwnedDeps { - let mut deps = mock_dependencies(contract_balances); - - let config = CreateOrUpdateConfig { - owner: Some(String::from("owner")), - address_provider_address: Some(String::from("address_provider")), - astroport_factory_address: Some(String::from("astroport_factory")), - astroport_max_spread: Some(StdDecimal::from_ratio(1u128, 100u128)), - cooldown_duration: Some(TEST_COOLDOWN_DURATION), - }; - let msg = InstantiateMsg { config }; - let info = mock_info("owner", &[]); - instantiate(deps.as_mut(), mock_env(MockEnvParams::default()), info, msg).unwrap(); - - deps - } -} diff --git a/contracts/mars-staking/src/error.rs b/contracts/mars-staking/src/error.rs deleted file mode 100644 index e13544904..000000000 --- a/contracts/mars-staking/src/error.rs +++ /dev/null @@ -1,41 +0,0 @@ -use thiserror::Error; - -use cosmwasm_std::{OverflowError, StdError}; - -use mars_core::error::MarsError; - -#[derive(Error, Debug, PartialEq)] -pub enum ContractError { - #[error("{0}")] - Mars(#[from] MarsError), - - #[error("{0}")] - Std(#[from] StdError), - - #[error("{0}")] - Overflow(#[from] OverflowError), - - #[error("Stake amount must be greater than 0")] - StakeAmountZero {}, - - #[error("Unstake amount must be greater than 0")] - UnstakeAmountZero {}, - - #[error("Cannot unstake if address has an active claim")] - UnstakeActiveClaim {}, - - #[error("Total MARS being claimed cannot be greater than staking contract's balance")] - MarsForClaimersOverflow {}, - - #[error("Cooldown has not ended")] - ClaimCooldownNotEnded {}, - - #[error("Mars amount to transfer is greater than total balance")] - TransferMarsAmountTooLarge {}, - - #[error("Cannot have two slash events on the same block")] - TransferMarsCannotHaveTwoSlashEventsOnBlock {}, - - #[error("Cannot swap MARS")] - MarsCannotSwap {}, -} diff --git a/contracts/mars-staking/src/lib.rs b/contracts/mars-staking/src/lib.rs deleted file mode 100644 index 0ab9946d6..000000000 --- a/contracts/mars-staking/src/lib.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod contract; -pub mod error; -pub mod state; -pub use mars_core::staking::*; diff --git a/contracts/mars-staking/src/state.rs b/contracts/mars-staking/src/state.rs deleted file mode 100644 index 84bdf96e9..000000000 --- a/contracts/mars-staking/src/state.rs +++ /dev/null @@ -1,10 +0,0 @@ -use cosmwasm_std::Addr; -use cw_storage_plus::{Item, Map, U64Key}; - -use crate::{Claim, Config, GlobalState, SlashEvent}; - -pub const CONFIG: Item = Item::new("config"); -pub const GLOBAL_STATE: Item = Item::new("global_state"); - -pub const CLAIMS: Map<&Addr, Claim> = Map::new("claims"); -pub const SLASH_EVENTS: Map = Map::new("slash_events"); diff --git a/contracts/mars-treasury/.cargo/config b/contracts/mars-treasury/.cargo/config deleted file mode 100644 index 3b4a527c6..000000000 --- a/contracts/mars-treasury/.cargo/config +++ /dev/null @@ -1,4 +0,0 @@ -[alias] -wasm = "build --release --target wasm32-unknown-unknown" -unit-test = "test --lib --features backtraces" -schema = "run --example schema" diff --git a/contracts/mars-treasury/Cargo.toml b/contracts/mars-treasury/Cargo.toml deleted file mode 100644 index 1c6c9cb0b..000000000 --- a/contracts/mars-treasury/Cargo.toml +++ /dev/null @@ -1,36 +0,0 @@ -[package] -name = "mars-treasury" -version = "1.0.0" -authors = ["Spike Spiegel "] -edition = "2018" - -exclude = [ - # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. - "contract.wasm", - "hash.txt", -] - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces"] - -[dependencies] -mars-core = { path = "../../packages/mars-core", version = "1.0.0" } - -cw-storage-plus = "0.9.0" - -cosmwasm-std = "0.16.2" - -schemars = "0.8.1" -serde = { version = "1.0.103", default-features = false, features = ["derive"] } - -[dev-dependencies] -cosmwasm-schema = "0.16.2" - -[profile.release] -overflow-checks = true diff --git a/contracts/mars-treasury/README.md b/contracts/mars-treasury/README.md deleted file mode 100644 index 50dae7791..000000000 --- a/contracts/mars-treasury/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# Treasury -Receives protocol fees that are kept in order to be spent in campaigns that are submitted and approved by the council. diff --git a/contracts/mars-treasury/examples/schema.rs b/contracts/mars-treasury/examples/schema.rs deleted file mode 100644 index f7b36fda5..000000000 --- a/contracts/mars-treasury/examples/schema.rs +++ /dev/null @@ -1,20 +0,0 @@ -use std::env::current_dir; -use std::fs::create_dir_all; - -use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; - -use mars_treasury::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; -use mars_treasury::Config; - -fn main() { - let mut out_dir = current_dir().unwrap(); - out_dir.push("schema"); - create_dir_all(&out_dir).unwrap(); - remove_schemas(&out_dir).unwrap(); - - export_schema(&schema_for!(InstantiateMsg), &out_dir); - export_schema(&schema_for!(ExecuteMsg), &out_dir); - export_schema(&schema_for!(QueryMsg), &out_dir); - - export_schema(&schema_for!(Config), &out_dir); -} diff --git a/contracts/mars-treasury/schema/config.json b/contracts/mars-treasury/schema/config.json deleted file mode 100644 index 36b88ad28..000000000 --- a/contracts/mars-treasury/schema/config.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Config", - "description": "Treasury global configuration", - "type": "object", - "required": [ - "owner" - ], - "properties": { - "owner": { - "$ref": "#/definitions/Addr" - } - }, - "definitions": { - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - } - } -} diff --git a/contracts/mars-treasury/schema/execute_msg.json b/contracts/mars-treasury/schema/execute_msg.json deleted file mode 100644 index 677381576..000000000 --- a/contracts/mars-treasury/schema/execute_msg.json +++ /dev/null @@ -1,341 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ExecuteMsg", - "anyOf": [ - { - "description": "Execute Cosmos msg", - "type": "object", - "required": [ - "execute_cosmos_msg" - ], - "properties": { - "execute_cosmos_msg": { - "$ref": "#/definitions/CosmosMsg_for_Empty" - } - }, - "additionalProperties": false - }, - { - "description": "Update contract config (only callable by owner)", - "type": "object", - "required": [ - "update_config" - ], - "properties": { - "update_config": { - "type": "object", - "properties": { - "owner": { - "type": [ - "string", - "null" - ] - } - } - } - }, - "additionalProperties": false - } - ], - "definitions": { - "BankMsg": { - "description": "The message types of the bank module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto", - "anyOf": [ - { - "description": "Sends native tokens from the contract to the given address.\n\nThis is translated to a [MsgSend](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28). `from_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "send" - ], - "properties": { - "send": { - "type": "object", - "required": [ - "amount", - "to_address" - ], - "properties": { - "amount": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "to_address": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "This will burn the given coins from the contract's account. There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper. Important if a contract controls significant token supply that must be retired.", - "type": "object", - "required": [ - "burn" - ], - "properties": { - "burn": { - "type": "object", - "required": [ - "amount" - ], - "properties": { - "amount": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - } - } - } - }, - "additionalProperties": false - } - ] - }, - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", - "type": "string" - }, - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } - }, - "CosmosMsg_for_Empty": { - "anyOf": [ - { - "type": "object", - "required": [ - "bank" - ], - "properties": { - "bank": { - "$ref": "#/definitions/BankMsg" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "custom" - ], - "properties": { - "custom": { - "$ref": "#/definitions/Empty" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "wasm" - ], - "properties": { - "wasm": { - "$ref": "#/definitions/WasmMsg" - } - }, - "additionalProperties": false - } - ] - }, - "Empty": { - "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", - "type": "object" - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - }, - "WasmMsg": { - "description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto", - "anyOf": [ - { - "description": "Dispatches a call to another contract at a known address (with known ABI).\n\nThis is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78). `sender` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "execute" - ], - "properties": { - "execute": { - "type": "object", - "required": [ - "contract_addr", - "funds", - "msg" - ], - "properties": { - "contract_addr": { - "type": "string" - }, - "funds": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "msg": { - "description": "msg is the json-encoded ExecuteMsg struct (as raw Binary)", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Instantiates a new contracts from previously uploaded Wasm code.\n\nThis is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.16.0-alpha1/x/wasm/internal/types/tx.proto#L47-L61). `sender` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "instantiate" - ], - "properties": { - "instantiate": { - "type": "object", - "required": [ - "code_id", - "funds", - "label", - "msg" - ], - "properties": { - "admin": { - "type": [ - "string", - "null" - ] - }, - "code_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "funds": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "label": { - "description": "A human-readbale label for the contract", - "type": "string" - }, - "msg": { - "description": "msg is the JSON-encoded InstantiateMsg struct (as raw Binary)", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to customize behavior.\n\nOnly the contract admin (as defined in wasmd), if any, is able to make this call.\n\nThis is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96). `sender` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "migrate" - ], - "properties": { - "migrate": { - "type": "object", - "required": [ - "contract_addr", - "msg", - "new_code_id" - ], - "properties": { - "contract_addr": { - "type": "string" - }, - "msg": { - "description": "msg is the json-encoded MigrateMsg struct that will be passed to the new code", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] - }, - "new_code_id": { - "description": "the code_id of the new logic to place in the given contract", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Sets a new admin (for migrate) on the given contract. Fails if this contract is not currently admin of the target contract.", - "type": "object", - "required": [ - "update_admin" - ], - "properties": { - "update_admin": { - "type": "object", - "required": [ - "admin", - "contract_addr" - ], - "properties": { - "admin": { - "type": "string" - }, - "contract_addr": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Clears the admin on the given contract, so no more migration possible. Fails if this contract is not currently admin of the target contract.", - "type": "object", - "required": [ - "clear_admin" - ], - "properties": { - "clear_admin": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "type": "string" - } - } - } - }, - "additionalProperties": false - } - ] - } - } -} diff --git a/contracts/mars-treasury/schema/instantiate_msg.json b/contracts/mars-treasury/schema/instantiate_msg.json deleted file mode 100644 index 98186de5c..000000000 --- a/contracts/mars-treasury/schema/instantiate_msg.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InstantiateMsg", - "type": "object", - "required": [ - "owner" - ], - "properties": { - "owner": { - "type": "string" - } - } -} diff --git a/contracts/mars-treasury/schema/query_msg.json b/contracts/mars-treasury/schema/query_msg.json deleted file mode 100644 index 6392c2a26..000000000 --- a/contracts/mars-treasury/schema/query_msg.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryMsg", - "anyOf": [ - { - "type": "object", - "required": [ - "config" - ], - "properties": { - "config": { - "type": "object" - } - }, - "additionalProperties": false - } - ] -} diff --git a/contracts/mars-treasury/src/contract.rs b/contracts/mars-treasury/src/contract.rs deleted file mode 100644 index 92c7e6d36..000000000 --- a/contracts/mars-treasury/src/contract.rs +++ /dev/null @@ -1,217 +0,0 @@ -#[cfg(not(feature = "library"))] -use cosmwasm_std::entry_point; -use cosmwasm_std::{ - to_binary, Binary, CosmosMsg, Deps, DepsMut, Env, MessageInfo, Response, StdResult, -}; - -use mars_core::error::MarsError; -use mars_core::helpers::option_string_to_addr; - -use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; -use crate::state::CONFIG; -use crate::Config; - -// INIT - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn instantiate( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - msg: InstantiateMsg, -) -> StdResult { - // initialize Config - let config = Config { - owner: deps.api.addr_validate(&msg.owner)?, - }; - - CONFIG.save(deps.storage, &config)?; - - Ok(Response::default()) -} - -// HANDLERS - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn execute( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: ExecuteMsg, -) -> Result { - match msg { - ExecuteMsg::ExecuteCosmosMsg(cosmos_msg) => { - handle_execute_cosmos_msg(deps, env, info, cosmos_msg) - } - - ExecuteMsg::UpdateConfig { owner } => execute_update_config(deps, env, info, owner), - } -} - -/// Execute Cosmos message -pub fn handle_execute_cosmos_msg( - deps: DepsMut, - _env: Env, - info: MessageInfo, - msg: CosmosMsg, -) -> Result { - let config = CONFIG.load(deps.storage)?; - - if info.sender != config.owner { - return Err(MarsError::Unauthorized {}); - } - - let res = Response::new() - .add_attribute("action", "execute_cosmos_msg") - .add_message(msg); - Ok(res) -} - -pub fn execute_update_config( - deps: DepsMut, - _env: Env, - info: MessageInfo, - owner: Option, -) -> Result { - let mut config = CONFIG.load(deps.storage)?; - - if info.sender != config.owner { - return Err(MarsError::Unauthorized {}); - }; - - config.owner = option_string_to_addr(deps.api, owner, config.owner)?; - - CONFIG.save(deps.storage, &config)?; - - let response = Response::new().add_attribute("action", "update_config"); - - Ok(response) -} - -// QUERIES - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::Config {} => to_binary(&query_config(deps)?), - } -} - -fn query_config(deps: Deps) -> StdResult { - let config = CONFIG.load(deps.storage)?; - Ok(config) -} - -// TESTS - -#[cfg(test)] -mod tests { - use super::*; - - use cosmwasm_std::testing::mock_info; - use cosmwasm_std::{attr, Addr, BankMsg, Coin, CosmosMsg, SubMsg, Uint128}; - - use mars_core::testing::{mock_dependencies, mock_env, MockEnvParams}; - - #[test] - fn test_proper_initialization() { - let mut deps = mock_dependencies(&[]); - - let msg = InstantiateMsg { - owner: String::from("owner"), - }; - let info = mock_info("owner", &[]); - - let res = - instantiate(deps.as_mut(), mock_env(MockEnvParams::default()), info, msg).unwrap(); - let empty_vec: Vec = vec![]; - assert_eq!(empty_vec, res.messages); - - let config = CONFIG.load(&deps.storage).unwrap(); - assert_eq!(Addr::unchecked("owner"), config.owner); - } - - #[test] - fn test_update_config() { - let mut deps = mock_dependencies(&[]); - - // * - // init config with valid params - // * - let msg = InstantiateMsg { - owner: String::from("owner"), - }; - let info = mock_info("owner", &[]); - let _res = - instantiate(deps.as_mut(), mock_env(MockEnvParams::default()), info, msg).unwrap(); - - // * - // non owner is not authorized - // * - let msg = ExecuteMsg::UpdateConfig { owner: None }; - let info = mock_info("somebody", &[]); - let error_res = - execute(deps.as_mut(), mock_env(MockEnvParams::default()), info, msg).unwrap_err(); - assert_eq!(error_res, MarsError::Unauthorized {}); - - // * - // update config with all new params - // * - let msg = ExecuteMsg::UpdateConfig { - owner: Some(String::from("new_owner")), - }; - let info = mock_info("owner", &[]); - // we can just call .unwrap() to assert this was a success - let res = execute(deps.as_mut(), mock_env(MockEnvParams::default()), info, msg).unwrap(); - assert_eq!(0, res.messages.len()); - - // Read config from state - let new_config = CONFIG.load(&deps.storage).unwrap(); - - assert_eq!(new_config.owner, Addr::unchecked("new_owner")); - } - - #[test] - fn test_execute_cosmos_msg() { - let mut deps = mock_dependencies(&[]); - - let msg = InstantiateMsg { - owner: String::from("owner"), - }; - let info = mock_info("owner", &[]); - let _res = - instantiate(deps.as_mut(), mock_env(MockEnvParams::default()), info, msg).unwrap(); - - let bank = BankMsg::Send { - to_address: "destination".to_string(), - amount: vec![Coin { - denom: "uluna".to_string(), - amount: Uint128::new(123456), - }], - }; - let cosmos_msg = CosmosMsg::Bank(bank); - let msg = ExecuteMsg::ExecuteCosmosMsg(cosmos_msg.clone()); - - // * - // non owner is not authorized - // * - let info = mock_info("somebody", &[]); - let error_res = execute( - deps.as_mut(), - mock_env(MockEnvParams::default()), - info, - msg.clone(), - ) - .unwrap_err(); - assert_eq!(error_res, MarsError::Unauthorized {}); - - // * - // can execute Cosmos msg - // * - let info = mock_info("owner", &[]); - let res = execute(deps.as_mut(), mock_env(MockEnvParams::default()), info, msg).unwrap(); - assert_eq!(res.messages, vec![SubMsg::new(cosmos_msg)]); - let expected_attr = vec![attr("action", "execute_cosmos_msg")]; - assert_eq!(res.attributes, expected_attr); - } -} diff --git a/contracts/mars-treasury/src/lib.rs b/contracts/mars-treasury/src/lib.rs deleted file mode 100644 index 08f549600..000000000 --- a/contracts/mars-treasury/src/lib.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod contract; -pub mod state; - -pub use mars_core::treasury::*; diff --git a/contracts/mars-treasury/src/state.rs b/contracts/mars-treasury/src/state.rs deleted file mode 100644 index 2c372da31..000000000 --- a/contracts/mars-treasury/src/state.rs +++ /dev/null @@ -1,6 +0,0 @@ -use cw_storage_plus::Item; - -use crate::Config; - -/// Stores config at the given key -pub const CONFIG: Item = Item::new("config"); diff --git a/contracts/mars-vesting/.cargo/config b/contracts/mars-vesting/.cargo/config deleted file mode 100644 index 336b618a1..000000000 --- a/contracts/mars-vesting/.cargo/config +++ /dev/null @@ -1,4 +0,0 @@ -[alias] -wasm = "build --release --target wasm32-unknown-unknown" -unit-test = "test --lib" -schema = "run --example schema" diff --git a/contracts/mars-vesting/Cargo.toml b/contracts/mars-vesting/Cargo.toml deleted file mode 100644 index 471e6cf04..000000000 --- a/contracts/mars-vesting/Cargo.toml +++ /dev/null @@ -1,40 +0,0 @@ -[package] -name = "mars-vesting" -version = "1.0.0" -authors = ["larry_0x "] -edition = "2018" - -exclude = [ - # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. - "contract.wasm", - "hash.txt", -] - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces"] - -[dependencies] -mars-core = { path = "../../packages/mars-core", version = "1.0.0" } - -terra-cosmwasm = "2.2.0" - -cw20 = "0.9.0" -cw-storage-plus = "0.9.0" - -cosmwasm-std = "0.16.2" - -schemars = "0.8.1" -serde = { version = "1.0.103", default-features = false, features = ["derive"] } -thiserror = "1.0.23" - -[dev-dependencies] -cosmwasm-schema = "0.16.2" - -[profile.release] -overflow-checks = true diff --git a/contracts/mars-vesting/examples/schema.rs b/contracts/mars-vesting/examples/schema.rs deleted file mode 100644 index d41d9054a..000000000 --- a/contracts/mars-vesting/examples/schema.rs +++ /dev/null @@ -1,21 +0,0 @@ -use std::env::current_dir; -use std::fs::create_dir_all; - -use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; - -use cosmwasm_std::Addr; -use mars_core::vesting::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; -use mars_core::vesting::Config; - -fn main() { - let mut out_dir = current_dir().unwrap(); - out_dir.push("schema"); - create_dir_all(&out_dir).unwrap(); - remove_schemas(&out_dir).unwrap(); - - export_schema(&schema_for!(InstantiateMsg), &out_dir); - export_schema(&schema_for!(ExecuteMsg), &out_dir); - export_schema(&schema_for!(QueryMsg), &out_dir); - - export_schema(&schema_for!(Config), &out_dir); -} diff --git a/contracts/mars-vesting/schema/config_for__addr.json b/contracts/mars-vesting/schema/config_for__addr.json deleted file mode 100644 index 8c2965106..000000000 --- a/contracts/mars-vesting/schema/config_for__addr.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Config_for_Addr", - "type": "object", - "required": [ - "address_provider_address", - "unlock_cliff", - "unlock_duration", - "unlock_start_time" - ], - "properties": { - "address_provider_address": { - "description": "Address provider address", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] - }, - "unlock_cliff": { - "description": "Number of seconds during which no token will be unlocked", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "unlock_duration": { - "description": "Number of seconds taken for tokens to be fully unlocked", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "unlock_start_time": { - "description": "UNIX timestamp, in seconds, of when unlocking is to be started", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "definitions": { - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - } - } -} diff --git a/contracts/mars-vesting/schema/config_for__string.json b/contracts/mars-vesting/schema/config_for__string.json deleted file mode 100644 index 4f4fe298b..000000000 --- a/contracts/mars-vesting/schema/config_for__string.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Config_for_String", - "type": "object", - "required": [ - "address_provider_address", - "unlock_cliff", - "unlock_duration", - "unlock_start_time" - ], - "properties": { - "address_provider_address": { - "description": "Address provider address", - "type": "string" - }, - "unlock_cliff": { - "description": "Number of seconds during which no token will be unlocked", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "unlock_duration": { - "description": "Number of seconds taken for tokens to be fully unlocked", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "unlock_start_time": { - "description": "UNIX timestamp, in seconds, of when unlocking is to be started", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } -} diff --git a/contracts/mars-vesting/schema/execute_msg.json b/contracts/mars-vesting/schema/execute_msg.json deleted file mode 100644 index b64805512..000000000 --- a/contracts/mars-vesting/schema/execute_msg.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ExecuteMsg", - "anyOf": [ - { - "description": "Implementation of cw20 receive msg", - "type": "object", - "required": [ - "receive" - ], - "properties": { - "receive": { - "$ref": "#/definitions/Cw20ReceiveMsg" - } - }, - "additionalProperties": false - }, - { - "description": "Withdraw unlocked MARS token", - "type": "object", - "required": [ - "withdraw" - ], - "properties": { - "withdraw": { - "type": "object" - } - }, - "additionalProperties": false - } - ], - "definitions": { - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", - "type": "string" - }, - "Cw20ReceiveMsg": { - "description": "Cw20ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg", - "type": "object", - "required": [ - "amount", - "msg", - "sender" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "msg": { - "$ref": "#/definitions/Binary" - }, - "sender": { - "type": "string" - } - } - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/mars-vesting/schema/query_msg.json b/contracts/mars-vesting/schema/query_msg.json deleted file mode 100644 index 36571092c..000000000 --- a/contracts/mars-vesting/schema/query_msg.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryMsg", - "anyOf": [ - { - "description": "Config of this contract. Returns `Config`", - "type": "object", - "required": [ - "config" - ], - "properties": { - "config": { - "type": "object" - } - }, - "additionalProperties": false - }, - { - "description": "Status of an allocation. Returns `Allocation`", - "type": "object", - "required": [ - "allocation" - ], - "properties": { - "allocation": { - "type": "object", - "required": [ - "user_address" - ], - "properties": { - "user_address": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Total amount of xMARS owned by a recipient at a certain height", - "type": "object", - "required": [ - "voting_power_at" - ], - "properties": { - "voting_power_at": { - "type": "object", - "required": [ - "block", - "user_address" - ], - "properties": { - "block": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "user_address": { - "type": "string" - } - } - } - }, - "additionalProperties": false - } - ] -} diff --git a/contracts/mars-vesting/src/contract.rs b/contracts/mars-vesting/src/contract.rs deleted file mode 100644 index f346d8f54..000000000 --- a/contracts/mars-vesting/src/contract.rs +++ /dev/null @@ -1,784 +0,0 @@ -use std::cmp; - -#[cfg(not(feature = "library"))] -use cosmwasm_std::entry_point; -use cosmwasm_std::{ - from_binary, to_binary, Addr, Binary, CosmosMsg, Deps, DepsMut, Env, MessageInfo, - QuerierWrapper, QueryRequest, Response, StdResult, Uint128, WasmMsg, WasmQuery, -}; - -use cw20::{Cw20ExecuteMsg, Cw20ReceiveMsg}; - -use mars_core::address_provider::{self, MarsContract}; -use mars_core::error::MarsError; -use mars_core::math::decimal::Decimal; -use mars_core::staking; -use mars_core::vesting::msg::{ExecuteMsg, InstantiateMsg, QueryMsg, ReceiveMsg}; -use mars_core::vesting::{Allocation, Config, Schedule}; - -use crate::error::ContractError; -use crate::snapshots::{ - capture_total_voting_power_snapshot, capture_voting_power_snapshot, - get_total_voting_power_value_at, get_voting_power_value_at, -}; -use crate::state::{ALLOCATIONS, CONFIG}; - -// INSTANTIATE - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn instantiate( - deps: DepsMut, - env: Env, - _info: MessageInfo, - msg: InstantiateMsg, -) -> Result { - validate_unlock_schedule(msg.unlock_schedule, env.block.time.seconds())?; - CONFIG.save(deps.storage, &msg.check(deps.api)?)?; - Ok(Response::default()) -} - -/// Validate the unlock schedule by applying the following criteria; throw error if any one is not -/// satisfied: -/// - Unlock start time must be no earlier than the current time -/// - Unlock cliff must be greater than zero -/// - Unlock duration must be greater than cliff -fn validate_unlock_schedule( - unlock_schedule: Schedule, - current_timestamp: u64, -) -> Result<(), ContractError> { - if unlock_schedule.start_time <= current_timestamp - || unlock_schedule.cliff == 0 - || unlock_schedule.duration <= unlock_schedule.cliff - { - return Err(ContractError::InvalidUnlockTimeSetup {}); - } - Ok(()) -} - -// EXECUTE - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn execute( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: ExecuteMsg, -) -> Result { - match msg { - ExecuteMsg::Receive(cw20_msg) => receive_cw20(deps, env, info, cw20_msg), - ExecuteMsg::Withdraw {} => execute_withdraw(deps, env, info), - } -} - -fn receive_cw20( - deps: DepsMut, - env: Env, - info: MessageInfo, - cw20_msg: Cw20ReceiveMsg, -) -> Result { - let api = deps.api; - match from_binary(&cw20_msg.msg)? { - ReceiveMsg::CreateAllocation { - user_address, - vest_schedule, - } => execute_create_allocation( - deps, - env, - info.sender, - api.addr_validate(&cw20_msg.sender)?, - api.addr_validate(&user_address)?, - cw20_msg.amount, - vest_schedule, - ), - } -} - -pub fn execute_create_allocation( - deps: DepsMut, - env: Env, - token: Addr, - creator: Addr, - user_address: Addr, - allocated_amount: Uint128, - vest_schedule: Schedule, -) -> Result { - let current_block = env.block.height; - let config = CONFIG.load(deps.storage)?; - - let mut addresses_query = address_provider::helpers::query_addresses( - &deps.querier, - config.address_provider_address, - vec![ - MarsContract::ProtocolAdmin, - MarsContract::MarsToken, - MarsContract::Staking, - ], - )?; - let staking_address = addresses_query.pop().unwrap(); - let mars_token_address = addresses_query.pop().unwrap(); - let protocol_admin_address = addresses_query.pop().unwrap(); - - // Only Mars token can be used to create allocations - if token != mars_token_address { - return Err(ContractError::InvalidTokenDeposit {}); - } - - // Only protocol admin can create allocations - if creator != protocol_admin_address { - return Err(MarsError::Unauthorized {}.into()); - } - - // Allocations can only be created when Mars:XMars ratio is 1:1 - let xmars_per_mars = get_xmars_per_mars(&deps.querier, &staking_address)?; - if xmars_per_mars != Decimal::one() { - return Err(ContractError::MarsXMarsRatioNotOne { xmars_per_mars }); - } - - // Save the user's allocation - match ALLOCATIONS.may_load(deps.storage, &user_address)? { - None => { - let allocation = Allocation { - allocated_amount, - withdrawn_amount: Uint128::zero(), - vest_schedule, - }; - ALLOCATIONS.save(deps.storage, &user_address, &allocation)? - } - Some(_) => { - return Err(ContractError::DataAlreadyExists { - user_address: user_address.to_string(), - }) - } - } - - // Save the user's voting power snapshot - capture_voting_power_snapshot(deps.storage, &user_address, current_block, allocated_amount)?; - - // Update total voting power snapshot - // If no snapshot for this block exists, save the user's allocated amount - let prev_total_voting_power = get_total_voting_power_value_at(deps.storage, current_block)?; - let total_voting_power = prev_total_voting_power.checked_add(allocated_amount)?; - capture_total_voting_power_snapshot(deps.storage, current_block, total_voting_power)?; - - Ok(Response::new() - .add_attribute("action", "create_allocation") - .add_attribute("user", user_address) - .add_attribute("allocated_amount", allocated_amount)) -} - -/// Query the amount of xMARS that will be minted when staking one unit of MARS -/// -/// NOTE: If no MARS token is staked, i.e. `total_mars_for_stakers` is zero, the query function -/// returns `Ok(None)`. In this case, staking contract mints the same amount of xMARS as the MARS -/// to be staked, i.e. a MARS:xMARS ratio of 1:1. -fn get_xmars_per_mars(querier: &QuerierWrapper, staking_address: &Addr) -> StdResult { - let xmars_per_mars_option: Option = - querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: staking_address.into(), - msg: to_binary(&staking::msg::QueryMsg::XMarsPerMars {})?, - }))?; - match xmars_per_mars_option { - Some(xmars_per_mars) => Ok(xmars_per_mars), - None => Ok(Decimal::one()), - } -} - -pub fn execute_withdraw( - deps: DepsMut, - env: Env, - info: MessageInfo, -) -> Result { - let current_block = env.block.height; - let config = CONFIG.load(deps.storage)?; - let mut allocation = ALLOCATIONS.load(deps.storage, &info.sender)?; - - let mars_token_address = address_provider::helpers::query_address( - &deps.querier, - config.address_provider_address, - MarsContract::MarsToken, - )?; - - // Calculate the withdrawable amount - // - // NOTE: We don't check whether withdrawable amount is zero, because in case it is zero, CW20 - // transfer will automatically fail - let withdrawable_amount = compute_withdrawable_amount( - allocation.allocated_amount, - allocation.withdrawn_amount, - allocation.vest_schedule, - config.unlock_schedule, - env.block.time.seconds(), - )?; - - // Update allocation - // We don't use checked math here, since we don't expect these to be over/underflow in any case - allocation.withdrawn_amount += withdrawable_amount; - ALLOCATIONS.save(deps.storage, &info.sender, &allocation)?; - - // Update the user's voting power snapshot - let prev_voting_power = get_voting_power_value_at(deps.storage, &info.sender, current_block)?; - let voting_power = prev_voting_power.checked_sub(withdrawable_amount)?; - capture_voting_power_snapshot(deps.storage, &info.sender, current_block, voting_power)?; - - // Update total voting power snapshots - let prev_total_voting_power = get_total_voting_power_value_at(deps.storage, current_block)?; - let total_voting_power = prev_total_voting_power.checked_sub(withdrawable_amount)?; - capture_total_voting_power_snapshot(deps.storage, current_block, total_voting_power)?; - - Ok(Response::new() - .add_message(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: mars_token_address.to_string(), - msg: to_binary(&Cw20ExecuteMsg::Transfer { - recipient: info.sender.to_string(), - amount: withdrawable_amount, - })?, - funds: vec![], - })) - .add_attribute("action", "withdraw") - .add_attribute("user", &info.sender) - .add_attribute("withdrawn_amount", withdrawable_amount)) -} - -/// Compute the withdrawable based on the current timestamp, the vesting schedule, and the unlock -/// schedule -/// -/// The vested amount and unlocked amount are computed separately, and the withdrawable amount is -/// whichever one is smaller, minus the amount already withdrawn. -fn compute_withdrawable_amount( - allocated_amount: Uint128, - withdrawn_amount: Uint128, - vest_schedule: Schedule, - unlock_schedule: Schedule, - current_time: u64, -) -> StdResult { - let f = |schedule: Schedule| { - // Before the end of cliff period, no token will be vested/unlocked - if current_time < schedule.start_time + schedule.cliff { - Uint128::zero() - // After the end of cliff, tokens vest/unlock linearly between start time and end time - } else if current_time < schedule.start_time + schedule.duration { - allocated_amount.multiply_ratio(current_time - schedule.start_time, schedule.duration) - // After end time, all tokens are fully vested/unlocked - } else { - allocated_amount - } - }; - - let vested_amount = f(vest_schedule); - let unlocked_amount = f(unlock_schedule); - - cmp::min(vested_amount, unlocked_amount) - .checked_sub(withdrawn_amount) - .map_err(|overflow_err| overflow_err.into()) -} - -// QUERIES - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::Config {} => to_binary(&query_config(deps)?), - QueryMsg::Allocation { user_address } => to_binary(&query_allocation(deps, user_address)?), - QueryMsg::VotingPowerAt { - user_address, - block, - } => to_binary(&query_voting_power_at(deps, user_address, block)?), - QueryMsg::TotalVotingPowerAt { block } => { - to_binary(&query_total_voting_power_at(deps, block)?) - } - } -} - -pub fn query_config(deps: Deps) -> StdResult> { - Ok(CONFIG.load(deps.storage)?.into()) -} - -pub fn query_allocation(deps: Deps, user_address: String) -> StdResult { - let address = deps.api.addr_validate(&user_address)?; - ALLOCATIONS.load(deps.storage, &address) -} - -pub fn query_voting_power_at(deps: Deps, user_address: String, block: u64) -> StdResult { - get_voting_power_value_at(deps.storage, &deps.api.addr_validate(&user_address)?, block) -} - -pub fn query_total_voting_power_at(deps: Deps, block: u64) -> StdResult { - get_total_voting_power_value_at(deps.storage, block) -} - -// TESTS - -#[cfg(test)] -mod tests { - use super::*; - use cosmwasm_std::testing::{MockApi, MockStorage}; - use cosmwasm_std::{CosmosMsg, OwnedDeps, ReplyOn, SubMsg, Timestamp, WasmMsg}; - use mars_core::testing::{ - mock_dependencies, mock_env, mock_info, MarsMockQuerier, MockEnvParams, - }; - use serde::de::DeserializeOwned; - - const MOCK_UNLOCK_SCHEDULE: Schedule = Schedule { - start_time: 1635724800, // 2021-11-01 - cliff: 31536000, // 1 year (365 days) - duration: 94608000, // 3 years (3 * 365 days) - }; - const MOCK_VEST_SCHEDULE: Schedule = Schedule { - start_time: 1614556800, // 2021-03-01 - cliff: 15552000, // 180 days - duration: 94608000, // 3 years - }; - - #[test] - fn proper_instantiation() { - let deps = th_setup(); - let env = mock_env(MockEnvParams::default()); - - let res: Config = query_helper(deps.as_ref(), env, QueryMsg::Config {}); - let expected = Config { - address_provider_address: "address_provider".to_string(), - unlock_schedule: Schedule { - start_time: 1635724800, - cliff: 31536000, - duration: 94608000, - }, - }; - assert_eq!(res, expected) - } - - #[test] - fn creating_allocation() { - let mut deps = th_setup(); - let env = mock_env(MockEnvParams::default()); - - // allocation data for alice should have been created - let query_msg = QueryMsg::Allocation { - user_address: "alice".to_string(), - }; - let res: Allocation = query_helper(deps.as_ref(), env.clone(), query_msg); - let expected = Allocation { - allocated_amount: Uint128::new(100000000), - withdrawn_amount: Uint128::zero(), - vest_schedule: MOCK_VEST_SCHEDULE, - }; - assert_eq!(res, expected); - - // try create an allocation for alice again; should fail - let msg = ExecuteMsg::Receive(Cw20ReceiveMsg { - amount: Uint128::new(100000000), // 100 Mars - sender: "protocol_admin".to_string(), - msg: to_binary(&ReceiveMsg::CreateAllocation { - user_address: "alice".to_string(), - vest_schedule: MOCK_VEST_SCHEDULE, - }) - .unwrap(), - }); - let res = execute(deps.as_mut(), env.clone(), mock_info("mars_token"), msg); - let expected = Err(ContractError::DataAlreadyExists { - user_address: "alice".to_string(), - }); - assert_eq!(res, expected); - - // non-admin try to create an allocation; should fail - let msg = ExecuteMsg::Receive(Cw20ReceiveMsg { - amount: Uint128::new(100000000), // 100 Mars - sender: "not_protocol_admin".to_string(), - msg: to_binary(&ReceiveMsg::CreateAllocation { - user_address: "bob".to_string(), - vest_schedule: MOCK_VEST_SCHEDULE, - }) - .unwrap(), - }); - let res = execute( - deps.as_mut(), - env.clone(), - mock_info("mars_token"), - msg.clone(), - ); - assert_eq!(res, Err(ContractError::Mars(MarsError::Unauthorized {}))); - - // try creating an allocation using a token rather than Mars; should fail - let res = execute(deps.as_mut(), env.clone(), mock_info("not_mars_token"), msg); - assert_eq!(res, Err(ContractError::InvalidTokenDeposit {})); - } - - #[test] - fn withdrawing() { - // deploy contract - let mut deps = th_setup(); - - //------------------------------------------------------------------------------------------ - // 2021-12-01 - // height: 10020 - // time: 1638316800 - // - // before unlock cliff, zero token should be withdrawable - // - // NOTE: the transaction should fail in this case because CW20 forbids sending zero amount - let env = mock_env(MockEnvParams { - block_height: 10010, - block_time: Timestamp::from_seconds(1638316800), - }); - let msg = ExecuteMsg::Withdraw {}; - let res = execute(deps.as_mut(), env.clone(), mock_info("alice"), msg).unwrap(); - let expected = SubMsg { - id: 0, - msg: CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: "mars_token".to_string(), - msg: to_binary(&Cw20ExecuteMsg::Transfer { - recipient: String::from("alice"), - amount: Uint128::zero(), - }) - .unwrap(), - funds: vec![], - }), - gas_limit: None, - reply_on: ReplyOn::Never, - }; - assert_eq!(res.messages.len(), 1); - assert_eq!(res.messages[0], expected); - - //------------------------------------------------------------------------------------------ - // 2022-12-01 - // height: 10030 - // time: 1669852800 - // - // vested_amount = 100000000 * (1669852800 - 1614556800) / 94608000 = 58447488 - // unlocked_amount = 100000000 * (1669852800 - 1635724800) / 94608000 = 36073059 - // withdrawable_amount = min(vested_amount, unlocked_amount) = 36073059 - let env = mock_env(MockEnvParams { - block_height: 10030, - block_time: Timestamp::from_seconds(1669852800), - }); - - let msg = ExecuteMsg::Withdraw {}; - let res = execute(deps.as_mut(), env.clone(), mock_info("alice"), msg).unwrap(); - let expected = SubMsg { - id: 0, - msg: CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: "mars_token".to_string(), - msg: to_binary(&Cw20ExecuteMsg::Transfer { - recipient: String::from("alice"), - amount: Uint128::new(36073059), - }) - .unwrap(), - funds: vec![], - }), - gas_limit: None, - reply_on: ReplyOn::Never, - }; - assert_eq!(res.messages.len(), 1); - assert_eq!(res.messages[0], expected); - - let msg = QueryMsg::Allocation { - user_address: "alice".to_string(), - }; - let res: Allocation = query_helper(deps.as_ref(), env, msg); - let expected = Allocation { - allocated_amount: Uint128::new(100000000), - withdrawn_amount: Uint128::new(36073059), - vest_schedule: MOCK_VEST_SCHEDULE, - }; - assert_eq!(res, expected); - - //------------------------------------------------------------------------------------------ - // 2024-03-01 - // height: 10040 - // time: 1709251200 - // - // vested_amount = 100000000 (fully vested) - // unlocked_amount = 100000000 * (1709251200 - 1635724800) / 94608000 = 77716894 - // withdrawable_amount = min(vested_amount, unlocked_amount) - withdrawn_amount - // = 77716894 - 36073059 = 41643835 - let env = mock_env(MockEnvParams { - block_height: 10040, - block_time: Timestamp::from_seconds(1709251200), - }); - - let msg = ExecuteMsg::Withdraw {}; - let res = execute(deps.as_mut(), env.clone(), mock_info("alice"), msg).unwrap(); - let expected = SubMsg { - id: 0, - msg: CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: "mars_token".to_string(), - msg: to_binary(&Cw20ExecuteMsg::Transfer { - recipient: String::from("alice"), - amount: Uint128::new(41643835), - }) - .unwrap(), - funds: vec![], - }), - gas_limit: None, - reply_on: ReplyOn::Never, - }; - assert_eq!(res.messages.len(), 1); - assert_eq!(res.messages[0], expected); - - let msg = QueryMsg::Allocation { - user_address: "alice".to_string(), - }; - let res: Allocation = query_helper(deps.as_ref(), env, msg); - let expected = Allocation { - allocated_amount: Uint128::new(100000000), - withdrawn_amount: Uint128::new(77716894), - vest_schedule: MOCK_VEST_SCHEDULE, - }; - assert_eq!(res, expected); - - //------------------------------------------------------------------------------------------ - // 2077-01-01 - // height: 10050 - // time: 3376684800 - // - // fully vested and unlocked - // withdrawable_amount = 100000000 - 77716894 = 22283106 - let env = mock_env(MockEnvParams { - block_height: 10050, - block_time: Timestamp::from_seconds(3376684800), - }); - - let msg = ExecuteMsg::Withdraw {}; - let res = execute(deps.as_mut(), env.clone(), mock_info("alice"), msg).unwrap(); - let expected = SubMsg { - id: 0, - msg: CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: "mars_token".to_string(), - msg: to_binary(&Cw20ExecuteMsg::Transfer { - recipient: String::from("alice"), - amount: Uint128::new(22283106), - }) - .unwrap(), - funds: vec![], - }), - gas_limit: None, - reply_on: ReplyOn::Never, - }; - assert_eq!(res.messages.len(), 1); - assert_eq!(res.messages[0], expected); - - let msg = QueryMsg::Allocation { - user_address: "alice".to_string(), - }; - let res: Allocation = query_helper(deps.as_ref(), env, msg); - let expected = Allocation { - allocated_amount: Uint128::new(100000000), - withdrawn_amount: Uint128::new(100000000), - vest_schedule: MOCK_VEST_SCHEDULE, - }; - assert_eq!(res, expected); - } - - #[test] - fn querying_voting_powers() { - // deploy contract - let mut deps = th_setup(); - - //------------------------------ - // 2023-01-01 - // timestamp: 1672531200 - // block number: 10500 - // - // vested_amount = 100000000 * (1672531200 - 1614556800) / 94608000 = 61278538 - // unlocked_amount = 100000000 * (1672531200 - 1635724800) / 94608000 = 38904109 - // withdrawable_amount = 38904109 - // available_amount = 100000000 - 38904109 = 61095891 - let env = mock_env(MockEnvParams { - block_height: 10500, - block_time: Timestamp::from_seconds(1672531200), - }); - let msg = ExecuteMsg::Withdraw {}; - execute(deps.as_mut(), env, mock_info("alice"), msg).unwrap(); - - //------------------------------ - // 2077-06-04 - // timestamp: 3389990400 - // block number: 11000 - // - // fully wihtdrawn - let env = mock_env(MockEnvParams { - block_height: 11000, - block_time: Timestamp::from_seconds(3389990400), - }); - let msg = ExecuteMsg::Withdraw {}; - execute(deps.as_mut(), env, mock_info("alice"), msg).unwrap(); - - //------------------------------ - // timestamp: 9999999999 - // block number: 69420 - // - // bob finally withdraws - let env = mock_env(MockEnvParams { - block_height: 69420, - block_time: Timestamp::from_seconds(9999999999), - }); - let msg = ExecuteMsg::Withdraw {}; - execute(deps.as_mut(), env, mock_info("bob"), msg).unwrap(); - - assert_eq!( - voting_power_at(deps.as_ref(), "alice", 10000), - Uint128::zero() - ); - assert_eq!( - voting_power_at(deps.as_ref(), "alice", 10010), - Uint128::new(100000000) - ); - assert_eq!( - voting_power_at(deps.as_ref(), "alice", 10020), - Uint128::new(100000000) - ); - assert_eq!( - voting_power_at(deps.as_ref(), "alice", 10499), - Uint128::new(100000000) - ); - assert_eq!( - voting_power_at(deps.as_ref(), "alice", 10500), - Uint128::new(61095891) - ); - assert_eq!( - voting_power_at(deps.as_ref(), "alice", 10750), - Uint128::new(61095891) - ); - assert_eq!( - voting_power_at(deps.as_ref(), "alice", 10999), - Uint128::new(61095891) - ); - assert_eq!( - voting_power_at(deps.as_ref(), "alice", 11000), - Uint128::zero() - ); - assert_eq!( - voting_power_at(deps.as_ref(), "alice", 88888), - Uint128::zero() - ); - - assert_eq!( - voting_power_at(deps.as_ref(), "bob", 10000), - Uint128::zero() - ); - assert_eq!( - voting_power_at(deps.as_ref(), "bob", 10010), - Uint128::new(100000000) - ); - assert_eq!( - voting_power_at(deps.as_ref(), "bob", 69419), - Uint128::new(100000000) - ); - assert_eq!( - voting_power_at(deps.as_ref(), "bob", 69420), - Uint128::zero() - ); - assert_eq!( - voting_power_at(deps.as_ref(), "bob", 88888), - Uint128::zero() - ); - - assert_eq!(total_voting_power_at(deps.as_ref(), 10000), Uint128::zero()); - assert_eq!( - total_voting_power_at(deps.as_ref(), 10010), - Uint128::new(200000000) - ); - assert_eq!( - total_voting_power_at(deps.as_ref(), 10020), - Uint128::new(200000000) - ); - assert_eq!( - total_voting_power_at(deps.as_ref(), 10499), - Uint128::new(200000000) - ); - assert_eq!( - total_voting_power_at(deps.as_ref(), 10500), - Uint128::new(161095891) - ); - assert_eq!( - total_voting_power_at(deps.as_ref(), 10750), - Uint128::new(161095891) - ); - assert_eq!( - total_voting_power_at(deps.as_ref(), 10999), - Uint128::new(161095891) - ); - assert_eq!( - total_voting_power_at(deps.as_ref(), 11000), - Uint128::new(100000000) - ); - assert_eq!( - total_voting_power_at(deps.as_ref(), 69419), - Uint128::new(100000000) - ); - assert_eq!(total_voting_power_at(deps.as_ref(), 69420), Uint128::zero()); - assert_eq!(total_voting_power_at(deps.as_ref(), 88888), Uint128::zero()); - } - - // TEST HELPERS - fn th_setup() -> OwnedDeps { - let mut deps = mock_dependencies(&[]); - - // deploy contract at block 10010 - let env = mock_env(MockEnvParams { - block_height: 10010, - block_time: Timestamp::from_seconds(0), - }); - - // instantiate the contract - let msg = InstantiateMsg { - address_provider_address: "address_provider".to_string(), - unlock_schedule: MOCK_UNLOCK_SCHEDULE, - }; - instantiate(deps.as_mut(), env.clone(), mock_info("deployer"), msg).unwrap(); - - let msg = |user: &str| { - ExecuteMsg::Receive(Cw20ReceiveMsg { - amount: Uint128::new(100000000), // 100 Mars - sender: "protocol_admin".to_string(), - msg: to_binary(&ReceiveMsg::CreateAllocation { - user_address: user.to_string(), - vest_schedule: MOCK_VEST_SCHEDULE, - }) - .unwrap(), - }) - }; - - // create an allocation for alice - execute( - deps.as_mut(), - env.clone(), - mock_info("mars_token"), - msg("alice"), - ) - .unwrap(); - - // create an allocation for bob - execute( - deps.as_mut(), - env.clone(), - mock_info("mars_token"), - msg("bob"), - ) - .unwrap(); - - deps - } - - fn query_helper(deps: Deps, env: Env, msg: QueryMsg) -> T { - from_binary(&query(deps, env, msg).unwrap()).unwrap() - } - - fn voting_power_at(deps: Deps, user: &str, height: u64) -> Uint128 { - query_helper( - deps, - mock_env(MockEnvParams::default()), - QueryMsg::VotingPowerAt { - user_address: user.to_string(), - block: height, - }, - ) - } - - fn total_voting_power_at(deps: Deps, height: u64) -> Uint128 { - query_helper( - deps, - mock_env(MockEnvParams::default()), - QueryMsg::TotalVotingPowerAt { block: height }, - ) - } -} diff --git a/contracts/mars-vesting/src/error.rs b/contracts/mars-vesting/src/error.rs deleted file mode 100644 index 9b46a6372..000000000 --- a/contracts/mars-vesting/src/error.rs +++ /dev/null @@ -1,35 +0,0 @@ -use cosmwasm_std::{OverflowError, StdError}; -use thiserror::Error; - -use mars_core::error::MarsError; -use mars_core::math::decimal::Decimal; - -#[derive(Error, Debug, PartialEq)] -pub enum ContractError { - #[error("{0}")] - Std(#[from] StdError), - - #[error("{0}")] - Mars(#[from] MarsError), - - #[error("{0}")] - Overflow(#[from] OverflowError), - - #[error("Only Mars token can be deposited")] - InvalidTokenDeposit {}, - - #[error("Data already exists for user: {user_address}")] - DataAlreadyExists { user_address: String }, - - #[error("Cannot find attribute: {key}")] - ReplyParseFailed { key: String }, - - #[error("Mars/xMars ratio is undefined")] - XMarsRatioUndefined {}, - - #[error("Unlock time setup is invalid")] - InvalidUnlockTimeSetup {}, - - #[error("Mars:XMars ratio is not one, is {xmars_per_mars} xMARS per MARS")] - MarsXMarsRatioNotOne { xmars_per_mars: Decimal }, -} diff --git a/contracts/mars-vesting/src/lib.rs b/contracts/mars-vesting/src/lib.rs deleted file mode 100644 index 1d3c3079e..000000000 --- a/contracts/mars-vesting/src/lib.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub mod contract; -pub mod error; -pub mod snapshots; -pub mod state; - -pub use mars_core::vesting::*; diff --git a/contracts/mars-vesting/src/snapshots.rs b/contracts/mars-vesting/src/snapshots.rs deleted file mode 100644 index 768e6b118..000000000 --- a/contracts/mars-vesting/src/snapshots.rs +++ /dev/null @@ -1,61 +0,0 @@ -use cosmwasm_std::{Addr, Order, StdResult, Storage, Uint128}; -use cw_storage_plus::{Bound, Map, Prefix, U64Key}; - -// STATE - -pub const VOTING_POWER_SNAPSHOTS: Map<(&Addr, U64Key), Uint128> = Map::new("voting_powers"); -pub const TOTAL_VOTING_POWER_SNAPSHOTS: Map = Map::new("total_voting_powers"); - -// CORE - -fn get_snapshot_value_at( - storage: &dyn Storage, - prefix: Prefix, - block: u64, -) -> StdResult { - // Look for the last value recorded before the current block (if none then value is zero) - let end = Bound::inclusive(U64Key::new(block)); - let last_value_up_to_block = prefix - .range(storage, None, Some(end), Order::Descending) - .next(); - - if let Some(value) = last_value_up_to_block { - let (_, v) = value?; - return Ok(v); - } - - Ok(Uint128::zero()) -} - -// VOTING POWER - -pub fn capture_voting_power_snapshot( - storage: &mut dyn Storage, - user_address: &Addr, - block: u64, - voting_power: Uint128, -) -> StdResult<()> { - VOTING_POWER_SNAPSHOTS.save(storage, (user_address, block.into()), &voting_power) -} - -pub fn get_voting_power_value_at( - storage: &dyn Storage, - user_address: &Addr, - block: u64, -) -> StdResult { - get_snapshot_value_at(storage, VOTING_POWER_SNAPSHOTS.prefix(user_address), block) -} - -// TOTAL VOTING POWER - -pub fn capture_total_voting_power_snapshot( - storage: &mut dyn Storage, - block: u64, - total_voting_power: Uint128, -) -> StdResult<()> { - TOTAL_VOTING_POWER_SNAPSHOTS.save(storage, block.into(), &total_voting_power) -} - -pub fn get_total_voting_power_value_at(storage: &dyn Storage, block: u64) -> StdResult { - get_snapshot_value_at(storage, TOTAL_VOTING_POWER_SNAPSHOTS.prefix(()), block) -} diff --git a/contracts/mars-vesting/src/state.rs b/contracts/mars-vesting/src/state.rs deleted file mode 100644 index e0f65c39d..000000000 --- a/contracts/mars-vesting/src/state.rs +++ /dev/null @@ -1,7 +0,0 @@ -use cosmwasm_std::Addr; -use cw_storage_plus::{Item, Map}; - -use mars_core::vesting::{Allocation, Config}; - -pub const CONFIG: Item> = Item::new("config"); -pub const ALLOCATIONS: Map<&Addr, Allocation> = Map::new("allocations"); diff --git a/contracts/mars-xmars-token/.cargo/config b/contracts/mars-xmars-token/.cargo/config deleted file mode 100755 index 8d4bc738b..000000000 --- a/contracts/mars-xmars-token/.cargo/config +++ /dev/null @@ -1,6 +0,0 @@ -[alias] -wasm = "build --release --target wasm32-unknown-unknown" -wasm-debug = "build --target wasm32-unknown-unknown" -unit-test = "test --lib" -integration-test = "test --test integration" -schema = "run --example schema" diff --git a/contracts/mars-xmars-token/.gitignore b/contracts/mars-xmars-token/.gitignore deleted file mode 100644 index d94423ad9..000000000 --- a/contracts/mars-xmars-token/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -# Build results -/target - -# Cargo+Git helper file (https://github.com/rust-lang/cargo/blob/0.44.1/src/cargo/sources/git/utils.rs#L320-L327) -.cargo-ok - -# Text file backups -**/*.rs.bk - -# macOS -.DS_Store - -# IDEs -*.iml -.idea - -# Environment -*.env - -# Scripts -node_modules/ - -# Artifacts -artifacts/ diff --git a/contracts/mars-xmars-token/Cargo.toml b/contracts/mars-xmars-token/Cargo.toml deleted file mode 100755 index 07f98c364..000000000 --- a/contracts/mars-xmars-token/Cargo.toml +++ /dev/null @@ -1,40 +0,0 @@ -[package] -name = "mars-xmars-token" -version = "1.0.0" -authors = ["Spike Spiegel "] -edition = "2018" - -exclude = [ - # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. - "contract.wasm", - "hash.txt", -] - -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -backtraces = ["cosmwasm-std/backtraces"] -# use library feature to disable all init/handle/query exports -library = [] - -[dependencies] -mars-core = { path = "../../packages/mars-core", version = "1.0.0" } - -cw0 = "0.9.0" -cw2 = "0.9.0" -cw20 = "0.9.0" -cw20-base = { version = "0.9.0", features = ["library"] } -cw-storage-plus = "0.9.0" - -cosmwasm-std = "0.16.2" - -schemars = "0.8.1" -serde = { version = "1.0.103", default-features = false, features = ["derive"] } -thiserror = "1.0.23" - -[dev-dependencies] -cosmwasm-schema = "0.16.2" - -[profile.release] -overflow-checks = true diff --git a/contracts/mars-xmars-token/README.md b/contracts/mars-xmars-token/README.md deleted file mode 100755 index 83b975d73..000000000 --- a/contracts/mars-xmars-token/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# xMARS Token - -Cw20 with added snapshot functionality in order to compute voting power for proposals. xMARS are minted by the Staking contract when addresses stake MARS. diff --git a/contracts/mars-xmars-token/examples/schema.rs b/contracts/mars-xmars-token/examples/schema.rs deleted file mode 100755 index 06092da05..000000000 --- a/contracts/mars-xmars-token/examples/schema.rs +++ /dev/null @@ -1,29 +0,0 @@ -use std::env::current_dir; -use std::fs::create_dir_all; - -use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; - -use cw20::{ - AllAccountsResponse, AllAllowancesResponse, AllowanceResponse, BalanceResponse, - TokenInfoResponse, -}; -use mars_xmars_token::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; -use mars_xmars_token::TotalSupplyResponse; - -fn main() { - let mut out_dir = current_dir().unwrap(); - out_dir.push("schema"); - create_dir_all(&out_dir).unwrap(); - remove_schemas(&out_dir).unwrap(); - - export_schema(&schema_for!(InstantiateMsg), &out_dir); - export_schema(&schema_for!(ExecuteMsg), &out_dir); - export_schema(&schema_for!(QueryMsg), &out_dir); - - export_schema(&schema_for!(AllowanceResponse), &out_dir); - export_schema(&schema_for!(BalanceResponse), &out_dir); - export_schema(&schema_for!(TokenInfoResponse), &out_dir); - export_schema(&schema_for!(AllAllowancesResponse), &out_dir); - export_schema(&schema_for!(AllAccountsResponse), &out_dir); - export_schema(&schema_for!(TotalSupplyResponse), &out_dir); -} diff --git a/contracts/mars-xmars-token/schema/all_accounts_response.json b/contracts/mars-xmars-token/schema/all_accounts_response.json deleted file mode 100644 index cea50fba4..000000000 --- a/contracts/mars-xmars-token/schema/all_accounts_response.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "AllAccountsResponse", - "type": "object", - "required": [ - "accounts" - ], - "properties": { - "accounts": { - "type": "array", - "items": { - "type": "string" - } - } - } -} diff --git a/contracts/mars-xmars-token/schema/all_allowances_response.json b/contracts/mars-xmars-token/schema/all_allowances_response.json deleted file mode 100644 index a8a11a557..000000000 --- a/contracts/mars-xmars-token/schema/all_allowances_response.json +++ /dev/null @@ -1,99 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "AllAllowancesResponse", - "type": "object", - "required": [ - "allowances" - ], - "properties": { - "allowances": { - "type": "array", - "items": { - "$ref": "#/definitions/AllowanceInfo" - } - } - }, - "definitions": { - "AllowanceInfo": { - "type": "object", - "required": [ - "allowance", - "expires", - "spender" - ], - "properties": { - "allowance": { - "$ref": "#/definitions/Uint128" - }, - "expires": { - "$ref": "#/definitions/Expiration" - }, - "spender": { - "type": "string" - } - } - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "anyOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object" - } - }, - "additionalProperties": false - } - ] - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/mars-xmars-token/schema/allowance_response.json b/contracts/mars-xmars-token/schema/allowance_response.json deleted file mode 100644 index 71b49adf1..000000000 --- a/contracts/mars-xmars-token/schema/allowance_response.json +++ /dev/null @@ -1,81 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "AllowanceResponse", - "type": "object", - "required": [ - "allowance", - "expires" - ], - "properties": { - "allowance": { - "$ref": "#/definitions/Uint128" - }, - "expires": { - "$ref": "#/definitions/Expiration" - } - }, - "definitions": { - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "anyOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object" - } - }, - "additionalProperties": false - } - ] - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/mars-xmars-token/schema/balance_response.json b/contracts/mars-xmars-token/schema/balance_response.json deleted file mode 100644 index 4e1a0be2b..000000000 --- a/contracts/mars-xmars-token/schema/balance_response.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "BalanceResponse", - "type": "object", - "required": [ - "balance" - ], - "properties": { - "balance": { - "$ref": "#/definitions/Uint128" - } - }, - "definitions": { - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/mars-xmars-token/schema/cw20_execute_msg.json b/contracts/mars-xmars-token/schema/cw20_execute_msg.json deleted file mode 100644 index 4dd7b8ad1..000000000 --- a/contracts/mars-xmars-token/schema/cw20_execute_msg.json +++ /dev/null @@ -1,442 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Cw20ExecuteMsg", - "anyOf": [ - { - "description": "Transfer is a base message to move tokens to another account without triggering actions", - "type": "object", - "required": [ - "transfer" - ], - "properties": { - "transfer": { - "type": "object", - "required": [ - "amount", - "recipient" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "recipient": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Burn is a base message to destroy tokens forever", - "type": "object", - "required": [ - "burn" - ], - "properties": { - "burn": { - "type": "object", - "required": [ - "amount" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Send is a base message to transfer tokens to a contract and trigger an action on the receiving contract.", - "type": "object", - "required": [ - "send" - ], - "properties": { - "send": { - "type": "object", - "required": [ - "amount", - "contract", - "msg" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "contract": { - "type": "string" - }, - "msg": { - "$ref": "#/definitions/Binary" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Only with \"approval\" extension. Allows spender to access an additional amount tokens from the owner's (env.sender) account. If expires is Some(), overwrites current allowance expiration with this one.", - "type": "object", - "required": [ - "increase_allowance" - ], - "properties": { - "increase_allowance": { - "type": "object", - "required": [ - "amount", - "spender" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "expires": { - "anyOf": [ - { - "$ref": "#/definitions/Expiration" - }, - { - "type": "null" - } - ] - }, - "spender": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Only with \"approval\" extension. Lowers the spender's access of tokens from the owner's (env.sender) account by amount. If expires is Some(), overwrites current allowance expiration with this one.", - "type": "object", - "required": [ - "decrease_allowance" - ], - "properties": { - "decrease_allowance": { - "type": "object", - "required": [ - "amount", - "spender" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "expires": { - "anyOf": [ - { - "$ref": "#/definitions/Expiration" - }, - { - "type": "null" - } - ] - }, - "spender": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Only with \"approval\" extension. Transfers amount tokens from owner -> recipient if `env.sender` has sufficient pre-approval.", - "type": "object", - "required": [ - "transfer_from" - ], - "properties": { - "transfer_from": { - "type": "object", - "required": [ - "amount", - "owner", - "recipient" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "owner": { - "type": "string" - }, - "recipient": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Only with \"approval\" extension. Sends amount tokens from owner -> contract if `env.sender` has sufficient pre-approval.", - "type": "object", - "required": [ - "send_from" - ], - "properties": { - "send_from": { - "type": "object", - "required": [ - "amount", - "contract", - "msg", - "owner" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "contract": { - "type": "string" - }, - "msg": { - "$ref": "#/definitions/Binary" - }, - "owner": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Only with \"approval\" extension. Destroys tokens forever", - "type": "object", - "required": [ - "burn_from" - ], - "properties": { - "burn_from": { - "type": "object", - "required": [ - "amount", - "owner" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "owner": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Only with the \"mintable\" extension. If authorized, creates amount new tokens and adds to the recipient balance.", - "type": "object", - "required": [ - "mint" - ], - "properties": { - "mint": { - "type": "object", - "required": [ - "amount", - "recipient" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "recipient": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Only with the \"marketing\" extension. If authorized, updates marketing metadata. Setting None/null for any of these will leave it unchanged. Setting Some(\"\") will clear this field on the contract storage", - "type": "object", - "required": [ - "update_marketing" - ], - "properties": { - "update_marketing": { - "type": "object", - "properties": { - "description": { - "description": "A longer description of the token and it's utility. Designed for tooltips or such", - "type": [ - "string", - "null" - ] - }, - "marketing": { - "description": "The address (if any) who can update this data structure", - "type": [ - "string", - "null" - ] - }, - "project": { - "description": "A URL pointing to the project behind this token.", - "type": [ - "string", - "null" - ] - } - } - } - }, - "additionalProperties": false - }, - { - "description": "If set as the \"marketing\" role on the contract, upload a new URL, SVG, or PNG for the token", - "type": "object", - "required": [ - "upload_logo" - ], - "properties": { - "upload_logo": { - "$ref": "#/definitions/Logo" - } - }, - "additionalProperties": false - } - ], - "definitions": { - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", - "type": "string" - }, - "EmbeddedLogo": { - "description": "This is used to store the logo on the blockchain in an accepted format. Enforce maximum size of 5KB on all variants.", - "anyOf": [ - { - "description": "Store the Logo as an SVG file. The content must conform to the spec at https://en.wikipedia.org/wiki/Scalable_Vector_Graphics (The contract should do some light-weight sanity-check validation)", - "type": "object", - "required": [ - "svg" - ], - "properties": { - "svg": { - "$ref": "#/definitions/Binary" - } - }, - "additionalProperties": false - }, - { - "description": "Store the Logo as a PNG file. This will likely only support up to 64x64 or so within the 5KB limit.", - "type": "object", - "required": [ - "png" - ], - "properties": { - "png": { - "$ref": "#/definitions/Binary" - } - }, - "additionalProperties": false - } - ] - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "anyOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object" - } - }, - "additionalProperties": false - } - ] - }, - "Logo": { - "description": "This is used for uploading logo data, or setting it in InstantiateData", - "anyOf": [ - { - "description": "A reference to an externally hosted logo. Must be a valid HTTP or HTTPS URL.", - "type": "object", - "required": [ - "url" - ], - "properties": { - "url": { - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Logo content stored on the blockchain. Enforce maximum size of 5KB on all variants", - "type": "object", - "required": [ - "embedded" - ], - "properties": { - "embedded": { - "$ref": "#/definitions/EmbeddedLogo" - } - }, - "additionalProperties": false - } - ] - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/mars-xmars-token/schema/instantiate_msg.json b/contracts/mars-xmars-token/schema/instantiate_msg.json deleted file mode 100644 index c8a3fcaf0..000000000 --- a/contracts/mars-xmars-token/schema/instantiate_msg.json +++ /dev/null @@ -1,192 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InstantiateMsg", - "type": "object", - "required": [ - "decimals", - "initial_balances", - "name", - "symbol" - ], - "properties": { - "decimals": { - "type": "integer", - "format": "uint8", - "minimum": 0.0 - }, - "initial_balances": { - "type": "array", - "items": { - "$ref": "#/definitions/Cw20Coin" - } - }, - "marketing": { - "anyOf": [ - { - "$ref": "#/definitions/InstantiateMarketingInfo" - }, - { - "type": "null" - } - ] - }, - "mint": { - "anyOf": [ - { - "$ref": "#/definitions/MinterResponse" - }, - { - "type": "null" - } - ] - }, - "name": { - "type": "string" - }, - "symbol": { - "type": "string" - } - }, - "definitions": { - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", - "type": "string" - }, - "Cw20Coin": { - "type": "object", - "required": [ - "address", - "amount" - ], - "properties": { - "address": { - "type": "string" - }, - "amount": { - "$ref": "#/definitions/Uint128" - } - } - }, - "EmbeddedLogo": { - "description": "This is used to store the logo on the blockchain in an accepted format. Enforce maximum size of 5KB on all variants.", - "anyOf": [ - { - "description": "Store the Logo as an SVG file. The content must conform to the spec at https://en.wikipedia.org/wiki/Scalable_Vector_Graphics (The contract should do some light-weight sanity-check validation)", - "type": "object", - "required": [ - "svg" - ], - "properties": { - "svg": { - "$ref": "#/definitions/Binary" - } - }, - "additionalProperties": false - }, - { - "description": "Store the Logo as a PNG file. This will likely only support up to 64x64 or so within the 5KB limit.", - "type": "object", - "required": [ - "png" - ], - "properties": { - "png": { - "$ref": "#/definitions/Binary" - } - }, - "additionalProperties": false - } - ] - }, - "InstantiateMarketingInfo": { - "type": "object", - "properties": { - "description": { - "type": [ - "string", - "null" - ] - }, - "logo": { - "anyOf": [ - { - "$ref": "#/definitions/Logo" - }, - { - "type": "null" - } - ] - }, - "marketing": { - "type": [ - "string", - "null" - ] - }, - "project": { - "type": [ - "string", - "null" - ] - } - } - }, - "Logo": { - "description": "This is used for uploading logo data, or setting it in InstantiateData", - "anyOf": [ - { - "description": "A reference to an externally hosted logo. Must be a valid HTTP or HTTPS URL.", - "type": "object", - "required": [ - "url" - ], - "properties": { - "url": { - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Logo content stored on the blockchain. Enforce maximum size of 5KB on all variants", - "type": "object", - "required": [ - "embedded" - ], - "properties": { - "embedded": { - "$ref": "#/definitions/EmbeddedLogo" - } - }, - "additionalProperties": false - } - ] - }, - "MinterResponse": { - "type": "object", - "required": [ - "minter" - ], - "properties": { - "cap": { - "description": "cap is a hard cap on total supply that can be achieved by minting. Note that this refers to total_supply. If None, there is unlimited cap.", - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - }, - "minter": { - "type": "string" - } - } - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/mars-xmars-token/schema/query_msg.json b/contracts/mars-xmars-token/schema/query_msg.json deleted file mode 100644 index f900a8f06..000000000 --- a/contracts/mars-xmars-token/schema/query_msg.json +++ /dev/null @@ -1,217 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryMsg", - "anyOf": [ - { - "description": "Returns the current balance of the given address, 0 if unset. Return type: BalanceResponse.", - "type": "object", - "required": [ - "balance" - ], - "properties": { - "balance": { - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Returns the balance of the given address at a given block Return type: BalanceResponse.", - "type": "object", - "required": [ - "balance_at" - ], - "properties": { - "balance_at": { - "type": "object", - "required": [ - "address", - "block" - ], - "properties": { - "address": { - "type": "string" - }, - "block": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Returns metadata on the contract - name, decimals, supply, etc. Return type: TokenInfoResponse.", - "type": "object", - "required": [ - "token_info" - ], - "properties": { - "token_info": { - "type": "object" - } - }, - "additionalProperties": false - }, - { - "description": "Total Supply at a given block Return type: TotalSupplyResponse", - "type": "object", - "required": [ - "total_supply_at" - ], - "properties": { - "total_supply_at": { - "type": "object", - "required": [ - "block" - ], - "properties": { - "block": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "minter" - ], - "properties": { - "minter": { - "type": "object" - } - }, - "additionalProperties": false - }, - { - "description": "Only with \"allowance\" extension. Returns how much spender can use from owner account, 0 if unset. Return type: AllowanceResponse.", - "type": "object", - "required": [ - "allowance" - ], - "properties": { - "allowance": { - "type": "object", - "required": [ - "owner", - "spender" - ], - "properties": { - "owner": { - "type": "string" - }, - "spender": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Only with \"enumerable\" extension (and \"allowances\") Returns all allowances this owner has approved. Supports pagination. Return type: AllAllowancesResponse.", - "type": "object", - "required": [ - "all_allowances" - ], - "properties": { - "all_allowances": { - "type": "object", - "required": [ - "owner" - ], - "properties": { - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "owner": { - "type": "string" - }, - "start_after": { - "type": [ - "string", - "null" - ] - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Only with \"enumerable\" extension Returns all accounts that have balances. Supports pagination. Return type: AllAccountsResponse.", - "type": "object", - "required": [ - "all_accounts" - ], - "properties": { - "all_accounts": { - "type": "object", - "properties": { - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "start_after": { - "type": [ - "string", - "null" - ] - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Only with \"marketing\" extension Returns more metadata on the contract to display in the client: - description, logo, project url, etc. Return type: MarketingInfoResponse", - "type": "object", - "required": [ - "marketing_info" - ], - "properties": { - "marketing_info": { - "type": "object" - } - }, - "additionalProperties": false - }, - { - "description": "Only with \"marketing\" extension Downloads the mbeded logo data (if stored on chain). Errors if no logo data ftored for this contract. Return type: DownloadLogoResponse.", - "type": "object", - "required": [ - "download_logo" - ], - "properties": { - "download_logo": { - "type": "object" - } - }, - "additionalProperties": false - } - ] -} diff --git a/contracts/mars-xmars-token/schema/token_info_response.json b/contracts/mars-xmars-token/schema/token_info_response.json deleted file mode 100644 index 9920c841f..000000000 --- a/contracts/mars-xmars-token/schema/token_info_response.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "TokenInfoResponse", - "type": "object", - "required": [ - "decimals", - "name", - "symbol", - "total_supply" - ], - "properties": { - "decimals": { - "type": "integer", - "format": "uint8", - "minimum": 0.0 - }, - "name": { - "type": "string" - }, - "symbol": { - "type": "string" - }, - "total_supply": { - "$ref": "#/definitions/Uint128" - } - }, - "definitions": { - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/mars-xmars-token/schema/total_supply_response.json b/contracts/mars-xmars-token/schema/total_supply_response.json deleted file mode 100644 index 9b4c4e0b2..000000000 --- a/contracts/mars-xmars-token/schema/total_supply_response.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "TotalSupplyResponse", - "type": "object", - "required": [ - "total_supply" - ], - "properties": { - "total_supply": { - "$ref": "#/definitions/Uint128" - } - }, - "definitions": { - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/mars-xmars-token/src/allowances.rs b/contracts/mars-xmars-token/src/allowances.rs deleted file mode 100755 index 0c9ba26e6..000000000 --- a/contracts/mars-xmars-token/src/allowances.rs +++ /dev/null @@ -1,441 +0,0 @@ -use cosmwasm_std::{attr, Binary, DepsMut, Env, MessageInfo, Response, Uint128}; -use cw20::Cw20ReceiveMsg; -use cw20_base::allowances::deduct_allowance; -use cw20_base::ContractError; - -use crate::core; - -pub fn execute_transfer_from( - deps: DepsMut, - env: Env, - info: MessageInfo, - owner: String, - recipient: String, - amount: Uint128, -) -> Result { - let rcpt_addr = deps.api.addr_validate(&recipient)?; - let owner_addr = deps.api.addr_validate(&owner)?; - - // deduct allowance before doing anything else have enough allowance - deduct_allowance(deps.storage, &owner_addr, &info.sender, &env.block, amount)?; - - core::transfer( - deps.storage, - &env, - Some(&owner_addr), - Some(&rcpt_addr), - amount, - )?; - - let res = Response::new().add_attributes(vec![ - attr("action", "transfer_from"), - attr("from", owner), - attr("to", recipient), - attr("by", info.sender), - attr("amount", amount), - ]); - Ok(res) -} - -pub fn execute_burn_from( - deps: DepsMut, - env: Env, - info: MessageInfo, - owner: String, - amount: Uint128, -) -> Result { - let owner_addr = deps.api.addr_validate(&owner)?; - - // deduct allowance before doing anything else have enough allowance - deduct_allowance(deps.storage, &owner_addr, &info.sender, &env.block, amount)?; - - core::burn(deps.storage, &env, &owner_addr, amount)?; - - let res = Response::new().add_attributes(vec![ - attr("action", "burn_from"), - attr("from", owner), - attr("by", info.sender), - attr("amount", amount), - ]); - Ok(res) -} - -pub fn execute_send_from( - deps: DepsMut, - env: Env, - info: MessageInfo, - owner: String, - contract: String, - amount: Uint128, - msg: Binary, -) -> Result { - let rcpt_addr = deps.api.addr_validate(&contract)?; - let owner_addr = deps.api.addr_validate(&owner)?; - - // deduct allowance before doing anything else have enough allowance - deduct_allowance(deps.storage, &owner_addr, &info.sender, &env.block, amount)?; - - // move the tokens to the contract - core::transfer( - deps.storage, - &env, - Some(&owner_addr), - Some(&rcpt_addr), - amount, - )?; - - let attrs = vec![ - attr("action", "send_from"), - attr("from", &owner), - attr("to", &contract), - attr("by", &info.sender), - attr("amount", amount), - ]; - - // create a send message - let msg = Cw20ReceiveMsg { - sender: info.sender.into(), - amount, - msg, - } - .into_cosmos_msg(contract)?; - - let res = Response::new().add_message(msg).add_attributes(attrs); - Ok(res) -} - -#[cfg(test)] -mod tests { - use super::*; - - use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; - use cosmwasm_std::{CosmosMsg, Deps, StdError, SubMsg, WasmMsg}; - use cw20::{AllowanceResponse, Cw20Coin, Expiration, TokenInfoResponse}; - use cw20_base::allowances::query_allowance; - use cw20_base::contract::{query_balance, query_token_info}; - - use crate::contract::{execute, instantiate, query_balance_at, query_total_supply_at}; - use crate::msg::{ExecuteMsg, InstantiateMsg}; - - fn get_balance>(deps: Deps, address: T) -> Uint128 { - query_balance(deps, address.into()).unwrap().balance - } - - // this will set up the instantiation for other tests - fn do_instantiate>( - mut deps: DepsMut, - addr: T, - amount: Uint128, - ) -> TokenInfoResponse { - let instantiate_msg = InstantiateMsg { - name: "Auto Gen".to_string(), - symbol: "AUTO".to_string(), - decimals: 3, - initial_balances: vec![Cw20Coin { - address: addr.into(), - amount, - }], - mint: None, - marketing: None, - }; - let info = mock_info("creator", &[]); - let env = mock_env(); - instantiate(deps.branch(), env, info, instantiate_msg).unwrap(); - query_token_info(deps.as_ref()).unwrap() - } - - #[test] - fn transfer_from_respects_limits() { - let mut deps = mock_dependencies(&[]); - let owner = String::from("addr0001"); - let spender = String::from("addr0002"); - let rcpt = String::from("addr0003"); - - let start = Uint128::new(999999); - do_instantiate(deps.as_mut(), &owner, start); - - // provide an allowance - let allow1 = Uint128::new(77777); - let msg = ExecuteMsg::IncreaseAllowance { - spender: spender.clone(), - amount: allow1, - expires: None, - }; - let info = mock_info(owner.as_ref(), &[]); - let env = mock_env(); - execute(deps.as_mut(), env, info, msg).unwrap(); - - // valid transfer of part of the allowance - let transfer = Uint128::new(44444); - let msg = ExecuteMsg::TransferFrom { - owner: owner.clone(), - recipient: rcpt.clone(), - amount: transfer, - }; - let info = mock_info(spender.as_ref(), &[]); - let env = mock_env(); - let res = execute(deps.as_mut(), env.clone(), info, msg).unwrap(); - assert_eq!(res.attributes[0], attr("action", "transfer_from")); - // make sure money arrived - assert_eq!( - get_balance(deps.as_ref(), owner.clone()), - start.checked_sub(transfer).unwrap() - ); - assert_eq!(get_balance(deps.as_ref(), rcpt.clone()), transfer); - assert_eq!( - query_balance_at(deps.as_ref(), owner.clone(), env.block.height) - .unwrap() - .balance, - start.checked_sub(transfer).unwrap() - ); - assert_eq!( - query_balance_at(deps.as_ref(), rcpt.clone(), env.block.height) - .unwrap() - .balance, - transfer - ); - - // ensure it looks good - let allowance = query_allowance(deps.as_ref(), owner.clone(), spender.clone()).unwrap(); - let expect = AllowanceResponse { - allowance: allow1.checked_sub(transfer).unwrap(), - expires: Expiration::Never {}, - }; - assert_eq!(expect, allowance); - - // cannot send more than the allowance - let msg = ExecuteMsg::TransferFrom { - owner: owner.clone(), - recipient: rcpt.clone(), - amount: Uint128::new(33443), - }; - let info = mock_info(spender.as_ref(), &[]); - let env = mock_env(); - let err = execute(deps.as_mut(), env, info, msg).unwrap_err(); - assert!(matches!(err, ContractError::Std(StdError::Overflow { .. }))); - - // let us increase limit, but set the expiration (default env height is 12_345) - let info = mock_info(owner.as_ref(), &[]); - let env = mock_env(); - let msg = ExecuteMsg::IncreaseAllowance { - spender: spender.clone(), - amount: Uint128::new(1000), - expires: Some(Expiration::AtHeight(env.block.height)), - }; - execute(deps.as_mut(), env, info, msg).unwrap(); - - // we should now get the expiration error - let msg = ExecuteMsg::TransferFrom { - owner, - recipient: rcpt, - amount: Uint128::new(33443), - }; - let info = mock_info(spender.as_ref(), &[]); - let env = mock_env(); - let err = execute(deps.as_mut(), env, info, msg).unwrap_err(); - assert_eq!(err, ContractError::Expired {}); - } - - #[test] - fn burn_from_respects_limits() { - let mut deps = mock_dependencies(&[]); - let owner = String::from("addr0001"); - let spender = String::from("addr0002"); - - let start = Uint128::new(999999); - do_instantiate(deps.as_mut(), &owner, start); - - // provide an allowance - let allow1 = Uint128::new(77777); - let msg = ExecuteMsg::IncreaseAllowance { - spender: spender.clone(), - amount: allow1, - expires: None, - }; - let info = mock_info(owner.as_ref(), &[]); - let env = mock_env(); - execute(deps.as_mut(), env, info, msg).unwrap(); - - // valid burn of part of the allowance - let transfer = Uint128::new(44444); - let msg = ExecuteMsg::BurnFrom { - owner: owner.clone(), - amount: transfer, - }; - let info = mock_info(spender.as_ref(), &[]); - let env = mock_env(); - let res = execute(deps.as_mut(), env.clone(), info, msg).unwrap(); - assert_eq!(res.attributes[0], attr("action", "burn_from")); - - // make sure money burnt - assert_eq!( - get_balance(deps.as_ref(), owner.clone()), - start.checked_sub(transfer).unwrap() - ); - assert_eq!( - query_balance_at(deps.as_ref(), owner.clone(), env.block.height) - .unwrap() - .balance, - start.checked_sub(transfer).unwrap() - ); - assert_eq!( - query_total_supply_at(deps.as_ref(), env.block.height) - .unwrap() - .total_supply, - start.checked_sub(transfer).unwrap() - ); - - // ensure it looks good - let allowance = query_allowance(deps.as_ref(), owner.clone(), spender.clone()).unwrap(); - let expect = AllowanceResponse { - allowance: allow1.checked_sub(transfer).unwrap(), - expires: Expiration::Never {}, - }; - assert_eq!(expect, allowance); - - // cannot burn more than the allowance - let msg = ExecuteMsg::BurnFrom { - owner: owner.clone(), - amount: Uint128::new(33443), - }; - let info = mock_info(spender.as_ref(), &[]); - let env = mock_env(); - let err = execute(deps.as_mut(), env, info, msg).unwrap_err(); - assert!(matches!(err, ContractError::Std(StdError::Overflow { .. }))); - - // let us increase limit, but set the expiration (default env height is 12_345) - let info = mock_info(owner.as_ref(), &[]); - let env = mock_env(); - let msg = ExecuteMsg::IncreaseAllowance { - spender: spender.clone(), - amount: Uint128::new(1000), - expires: Some(Expiration::AtHeight(env.block.height)), - }; - execute(deps.as_mut(), env, info, msg).unwrap(); - - // we should now get the expiration error - let msg = ExecuteMsg::BurnFrom { - owner, - amount: Uint128::new(33443), - }; - let info = mock_info(spender.as_ref(), &[]); - let env = mock_env(); - let err = execute(deps.as_mut(), env, info, msg).unwrap_err(); - assert_eq!(err, ContractError::Expired {}); - } - - #[test] - fn send_from_respects_limits() { - let mut deps = mock_dependencies(&[]); - let owner = String::from("addr0001"); - let spender = String::from("addr0002"); - let contract = String::from("cool-dex"); - let send_msg = Binary::from(r#"{"some":123}"#.as_bytes()); - - let start = Uint128::new(999999); - do_instantiate(deps.as_mut(), &owner, start); - - // provide an allowance - let allow1 = Uint128::new(77777); - let msg = ExecuteMsg::IncreaseAllowance { - spender: spender.clone(), - amount: allow1, - expires: None, - }; - let info = mock_info(owner.as_ref(), &[]); - let env = mock_env(); - execute(deps.as_mut(), env, info, msg).unwrap(); - - // valid send of part of the allowance - let transfer = Uint128::new(44444); - let msg = ExecuteMsg::SendFrom { - owner: owner.clone(), - amount: transfer, - contract: contract.clone(), - msg: send_msg.clone(), - }; - let info = mock_info(spender.as_ref(), &[]); - let env = mock_env(); - let res = execute(deps.as_mut(), env.clone(), info, msg).unwrap(); - assert_eq!(res.attributes[0], attr("action", "send_from")); - assert_eq!(1, res.messages.len()); - - // we record this as sent by the one who requested, not the one who was paying - let binary_msg = Cw20ReceiveMsg { - sender: spender.clone(), - amount: transfer, - msg: send_msg.clone(), - } - .into_binary() - .unwrap(); - assert_eq!( - res.messages[0], - SubMsg::new(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: contract.clone(), - msg: binary_msg, - funds: vec![], - })) - ); - - // make sure money sent - assert_eq!( - get_balance(deps.as_ref(), owner.clone()), - start.checked_sub(transfer).unwrap() - ); - assert_eq!(get_balance(deps.as_ref(), contract.clone()), transfer); - assert_eq!( - query_balance_at(deps.as_ref(), owner.clone(), env.block.height) - .unwrap() - .balance, - start.checked_sub(transfer).unwrap() - ); - assert_eq!( - query_balance_at(deps.as_ref(), contract.clone(), env.block.height) - .unwrap() - .balance, - transfer - ); - - // ensure it looks good - let allowance = query_allowance(deps.as_ref(), owner.clone(), spender.clone()).unwrap(); - let expect = AllowanceResponse { - allowance: allow1.checked_sub(transfer).unwrap(), - expires: Expiration::Never {}, - }; - assert_eq!(expect, allowance); - - // cannot send more than the allowance - let msg = ExecuteMsg::SendFrom { - owner: owner.clone(), - amount: Uint128::new(33443), - contract: contract.clone(), - msg: send_msg.clone(), - }; - let info = mock_info(spender.as_ref(), &[]); - let env = mock_env(); - let err = execute(deps.as_mut(), env, info, msg).unwrap_err(); - assert!(matches!(err, ContractError::Std(StdError::Overflow { .. }))); - - // let us increase limit, but set the expiration to current block (expired) - let info = mock_info(owner.as_ref(), &[]); - let env = mock_env(); - let msg = ExecuteMsg::IncreaseAllowance { - spender: spender.clone(), - amount: Uint128::new(1000), - expires: Some(Expiration::AtHeight(env.block.height)), - }; - execute(deps.as_mut(), env, info, msg).unwrap(); - - // we should now get the expiration error - let msg = ExecuteMsg::SendFrom { - owner, - amount: Uint128::new(33443), - contract, - msg: send_msg, - }; - let info = mock_info(spender.as_ref(), &[]); - let env = mock_env(); - let err = execute(deps.as_mut(), env, info, msg).unwrap_err(); - assert_eq!(err, ContractError::Expired {}); - } -} diff --git a/contracts/mars-xmars-token/src/contract.rs b/contracts/mars-xmars-token/src/contract.rs deleted file mode 100755 index 1083f7c47..000000000 --- a/contracts/mars-xmars-token/src/contract.rs +++ /dev/null @@ -1,1067 +0,0 @@ -#[cfg(not(feature = "library"))] -use cosmwasm_std::entry_point; -use cosmwasm_std::{ - to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, Uint128, -}; -use cw2::set_contract_version; -use cw20::{BalanceResponse, Cw20Coin, Cw20ReceiveMsg}; -use cw20_base::allowances::{ - execute_decrease_allowance, execute_increase_allowance, query_allowance, -}; -use cw20_base::contract::{ - execute_update_marketing, execute_upload_logo, query_balance, query_download_logo, - query_marketing_info, query_minter, query_token_info, -}; -use cw20_base::enumerable::{query_all_accounts, query_all_allowances}; -use cw20_base::state::{BALANCES, TOKEN_INFO}; -use cw20_base::ContractError; - -use mars_core::cw20_core::instantiate_token_info_and_marketing; - -use crate::allowances::{execute_burn_from, execute_send_from, execute_transfer_from}; -use crate::core; -use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; -use crate::snapshots::{ - capture_balance_snapshot, capture_total_supply_snapshot, get_balance_snapshot_value_at, - get_total_supply_snapshot_value_at, -}; -use crate::TotalSupplyResponse; - -// version info for migration info -const CONTRACT_NAME: &str = "crates.io:xmars-token"; -const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn instantiate( - mut deps: DepsMut, - env: Env, - _info: MessageInfo, - msg: InstantiateMsg, -) -> Result { - set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - - msg.validate()?; - let total_supply = create_accounts(&mut deps, &env, &msg.initial_balances)?; - - if total_supply > Uint128::zero() { - capture_total_supply_snapshot(deps.storage, &env, total_supply)?; - } - - instantiate_token_info_and_marketing(&mut deps, msg, total_supply)?; - - Ok(Response::default()) -} - -pub fn create_accounts(deps: &mut DepsMut, env: &Env, accounts: &[Cw20Coin]) -> StdResult { - let mut total_supply = Uint128::zero(); - for row in accounts { - let address = deps.api.addr_validate(&row.address)?; - BALANCES.save(deps.storage, &address, &row.amount)?; - capture_balance_snapshot(deps.storage, env, &address, row.amount)?; - total_supply += row.amount; - } - Ok(total_supply) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn execute( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: ExecuteMsg, -) -> Result { - match msg { - ExecuteMsg::Transfer { recipient, amount } => { - execute_transfer(deps, env, info, recipient, amount) - } - ExecuteMsg::Burn { amount } => execute_burn(deps, env, info, amount), - ExecuteMsg::Send { - contract, - amount, - msg, - } => execute_send(deps, env, info, contract, amount, msg), - ExecuteMsg::Mint { recipient, amount } => execute_mint(deps, env, info, recipient, amount), - ExecuteMsg::IncreaseAllowance { - spender, - amount, - expires, - } => execute_increase_allowance(deps, env, info, spender, amount, expires), - ExecuteMsg::DecreaseAllowance { - spender, - amount, - expires, - } => execute_decrease_allowance(deps, env, info, spender, amount, expires), - ExecuteMsg::TransferFrom { - owner, - recipient, - amount, - } => execute_transfer_from(deps, env, info, owner, recipient, amount), - ExecuteMsg::BurnFrom { owner, amount } => execute_burn_from(deps, env, info, owner, amount), - ExecuteMsg::SendFrom { - owner, - contract, - amount, - msg, - } => execute_send_from(deps, env, info, owner, contract, amount, msg), - ExecuteMsg::UpdateMarketing { - project, - description, - marketing, - } => execute_update_marketing(deps, env, info, project, description, marketing), - ExecuteMsg::UploadLogo(logo) => execute_upload_logo(deps, env, info, logo), - } -} - -pub fn execute_transfer( - deps: DepsMut, - env: Env, - info: MessageInfo, - recipient: String, - amount: Uint128, -) -> Result { - let recipient_addr = deps.api.addr_validate(&recipient)?; - - core::transfer( - deps.storage, - &env, - Some(&info.sender), - Some(&recipient_addr), - amount, - )?; - - let res = Response::new() - .add_attribute("action", "transfer") - .add_attribute("from", info.sender) - .add_attribute("to", recipient) - .add_attribute("amount", amount); - Ok(res) -} - -pub fn execute_burn( - deps: DepsMut, - env: Env, - info: MessageInfo, - amount: Uint128, -) -> Result { - core::burn(deps.storage, &env, &info.sender, amount)?; - - let res = Response::new() - .add_attribute("action", "burn") - .add_attribute("user", info.sender) - .add_attribute("amount", amount); - Ok(res) -} - -pub fn execute_mint( - deps: DepsMut, - env: Env, - info: MessageInfo, - recipient: String, - amount: Uint128, -) -> Result { - if amount.is_zero() { - return Err(ContractError::InvalidZeroAmount {}); - } - - let mut config = TOKEN_INFO.load(deps.storage)?; - if config.mint.is_none() || config.mint.as_ref().unwrap().minter != info.sender { - return Err(ContractError::Unauthorized {}); - } - - // update supply and enforce cap - config.total_supply += amount; - if let Some(limit) = config.get_cap() { - if config.total_supply > limit { - return Err(ContractError::CannotExceedCap {}); - } - } - TOKEN_INFO.save(deps.storage, &config)?; - capture_total_supply_snapshot(deps.storage, &env, config.total_supply)?; - - // add amount to recipient balance - let rcpt_addr = deps.api.addr_validate(&recipient)?; - core::transfer(deps.storage, &env, None, Some(&rcpt_addr), amount)?; - - let res = Response::new() - .add_attribute("action", "mint") - .add_attribute("to", recipient) - .add_attribute("amount", amount); - Ok(res) -} - -pub fn execute_send( - deps: DepsMut, - env: Env, - info: MessageInfo, - contract: String, - amount: Uint128, - msg: Binary, -) -> Result { - let rcpt_addr = deps.api.addr_validate(&contract)?; - - // move the tokens to the contract - core::transfer( - deps.storage, - &env, - Some(&info.sender), - Some(&rcpt_addr), - amount, - )?; - - let res = Response::new() - .add_attribute("action", "send") - .add_attribute("from", info.sender.to_string()) - .add_attribute("to", &contract) - .add_attribute("amount", amount) - .add_message( - Cw20ReceiveMsg { - sender: info.sender.to_string(), - amount, - msg, - } - .into_cosmos_msg(contract)?, - ); - - Ok(res) -} - -// QUERY - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::Balance { address } => to_binary(&query_balance(deps, address)?), - QueryMsg::BalanceAt { address, block } => { - to_binary(&query_balance_at(deps, address, block)?) - } - QueryMsg::TokenInfo {} => to_binary(&query_token_info(deps)?), - QueryMsg::TotalSupplyAt { block } => to_binary(&query_total_supply_at(deps, block)?), - QueryMsg::Minter {} => to_binary(&query_minter(deps)?), - QueryMsg::Allowance { owner, spender } => { - to_binary(&query_allowance(deps, owner, spender)?) - } - QueryMsg::AllAllowances { - owner, - start_after, - limit, - } => to_binary(&query_all_allowances(deps, owner, start_after, limit)?), - QueryMsg::AllAccounts { start_after, limit } => { - to_binary(&query_all_accounts(deps, start_after, limit)?) - } - QueryMsg::MarketingInfo {} => to_binary(&query_marketing_info(deps)?), - QueryMsg::DownloadLogo {} => to_binary(&query_download_logo(deps)?), - } -} - -pub fn query_balance_at(deps: Deps, address: String, block: u64) -> StdResult { - let addr = deps.api.addr_validate(&address)?; - let balance = get_balance_snapshot_value_at(deps.storage, &addr, block)?; - Ok(BalanceResponse { balance }) -} - -pub fn query_total_supply_at(deps: Deps, block: u64) -> StdResult { - let total_supply = get_total_supply_snapshot_value_at(deps.storage, block)?; - Ok(TotalSupplyResponse { total_supply }) -} - -#[cfg(test)] -mod tests { - use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; - use cosmwasm_std::{coins, Addr, CosmosMsg, StdError, SubMsg, WasmMsg}; - use cw20::{ - Cw20Coin, Logo, LogoInfo, MarketingInfoResponse, MinterResponse, TokenInfoResponse, - }; - use cw20_base::msg::InstantiateMarketingInfo; - - use mars_core::testing::MockEnvParams; - - use super::*; - - fn get_balance>(deps: Deps, address: T) -> Uint128 { - query_balance(deps, address.into()).unwrap().balance - } - - // this will set up the instantiation for other tests - fn do_instantiate_with_minter( - deps: DepsMut, - addr: &str, - amount: Uint128, - minter: &str, - cap: Option, - ) -> TokenInfoResponse { - _do_instantiate( - deps, - addr, - amount, - Some(MinterResponse { - minter: minter.to_string(), - cap, - }), - ) - } - - // this will set up the instantiation for other tests - fn do_instantiate(deps: DepsMut, addr: &str, amount: Uint128) -> TokenInfoResponse { - _do_instantiate(deps, addr, amount, None) - } - - // this will set up the instantiation for other tests - fn _do_instantiate( - mut deps: DepsMut, - addr: &str, - amount: Uint128, - mint: Option, - ) -> TokenInfoResponse { - let instantiate_msg = InstantiateMsg { - name: "Auto Gen".to_string(), - symbol: "AUTO".to_string(), - decimals: 3, - initial_balances: vec![Cw20Coin { - address: addr.to_string(), - amount, - }], - mint: mint.clone(), - marketing: None, - }; - let info = mock_info("creator", &[]); - let env = mock_env(); - let res = instantiate(deps.branch(), env, info, instantiate_msg).unwrap(); - assert_eq!(0, res.messages.len()); - - let meta = query_token_info(deps.as_ref()).unwrap(); - assert_eq!( - meta, - TokenInfoResponse { - name: "Auto Gen".to_string(), - symbol: "AUTO".to_string(), - decimals: 3, - total_supply: amount, - } - ); - assert_eq!(get_balance(deps.as_ref(), addr), amount); - assert_eq!(query_minter(deps.as_ref()).unwrap(), mint,); - meta - } - - mod instantiate { - use super::*; - - #[test] - fn basic() { - let mut deps = mock_dependencies(&[]); - let amount = Uint128::from(11223344u128); - let instantiate_msg = InstantiateMsg { - name: "Cash Token".to_string(), - symbol: "CASH".to_string(), - decimals: 9, - initial_balances: vec![Cw20Coin { - address: String::from("addr0000"), - amount, - }], - mint: None, - marketing: None, - }; - let info = mock_info("creator", &[]); - let env = mock_env(); - let res = instantiate(deps.as_mut(), env, info, instantiate_msg).unwrap(); - assert_eq!(0, res.messages.len()); - - assert_eq!( - query_token_info(deps.as_ref()).unwrap(), - TokenInfoResponse { - name: "Cash Token".to_string(), - symbol: "CASH".to_string(), - decimals: 9, - total_supply: amount, - } - ); - assert_eq!( - get_balance(deps.as_ref(), "addr0000"), - Uint128::new(11223344) - ); - } - - #[test] - fn mintable() { - let mut deps = mock_dependencies(&[]); - let amount = Uint128::new(11223344); - let minter = String::from("asmodat"); - let limit = Uint128::new(511223344); - let instantiate_msg = InstantiateMsg { - name: "Cash Token".to_string(), - symbol: "CASH".to_string(), - decimals: 9, - initial_balances: vec![Cw20Coin { - address: "addr0000".into(), - amount, - }], - mint: Some(MinterResponse { - minter: minter.clone(), - cap: Some(limit), - }), - marketing: None, - }; - let info = mock_info("creator", &[]); - let env = mock_env(); - let res = instantiate(deps.as_mut(), env, info, instantiate_msg).unwrap(); - assert_eq!(0, res.messages.len()); - - assert_eq!( - query_token_info(deps.as_ref()).unwrap(), - TokenInfoResponse { - name: "Cash Token".to_string(), - symbol: "CASH".to_string(), - decimals: 9, - total_supply: amount, - } - ); - assert_eq!( - get_balance(deps.as_ref(), "addr0000"), - Uint128::new(11223344) - ); - assert_eq!( - query_minter(deps.as_ref()).unwrap(), - Some(MinterResponse { - minter, - cap: Some(limit), - }), - ); - } - - #[test] - fn mintable_over_cap() { - let mut deps = mock_dependencies(&[]); - let amount = Uint128::new(11223344); - let minter = String::from("asmodat"); - let limit = Uint128::new(11223300); - let instantiate_msg = InstantiateMsg { - name: "Cash Token".to_string(), - symbol: "CASH".to_string(), - decimals: 9, - initial_balances: vec![Cw20Coin { - address: String::from("addr0000"), - amount, - }], - mint: Some(MinterResponse { - minter, - cap: Some(limit), - }), - marketing: None, - }; - let info = mock_info("creator", &[]); - let env = mock_env(); - let err = instantiate(deps.as_mut(), env, info, instantiate_msg).unwrap_err(); - assert_eq!( - err, - StdError::generic_err("Initial supply greater than cap").into() - ); - } - - mod marketing { - use super::*; - - #[test] - fn basic() { - let mut deps = mock_dependencies(&[]); - let instantiate_msg = InstantiateMsg { - name: "Cash Token".to_string(), - symbol: "CASH".to_string(), - decimals: 9, - initial_balances: vec![], - mint: None, - marketing: Some(InstantiateMarketingInfo { - project: Some("Project".to_owned()), - description: Some("Description".to_owned()), - marketing: Some("marketing".to_owned()), - logo: Some(Logo::Url("url".to_owned())), - }), - }; - - let info = mock_info("creator", &[]); - let env = mock_env(); - let res = instantiate(deps.as_mut(), env, info, instantiate_msg).unwrap(); - assert_eq!(0, res.messages.len()); - - assert_eq!( - query_marketing_info(deps.as_ref()).unwrap(), - MarketingInfoResponse { - project: Some("Project".to_owned()), - description: Some("Description".to_owned()), - marketing: Some(Addr::unchecked("marketing")), - logo: Some(LogoInfo::Url("url".to_owned())), - } - ); - - let err = query_download_logo(deps.as_ref()).unwrap_err(); - assert!( - matches!(err, StdError::NotFound { .. }), - "Expected StdError::NotFound, received {}", - err - ); - } - - #[test] - fn invalid_marketing() { - let mut deps = mock_dependencies(&[]); - let instantiate_msg = InstantiateMsg { - name: "Cash Token".to_string(), - symbol: "CASH".to_string(), - decimals: 9, - initial_balances: vec![], - mint: None, - marketing: Some(InstantiateMarketingInfo { - project: Some("Project".to_owned()), - description: Some("Description".to_owned()), - marketing: Some("m".to_owned()), - logo: Some(Logo::Url("url".to_owned())), - }), - }; - - let info = mock_info("creator", &[]); - let env = mock_env(); - instantiate(deps.as_mut(), env, info, instantiate_msg).unwrap_err(); - - let err = query_download_logo(deps.as_ref()).unwrap_err(); - assert!( - matches!(err, StdError::NotFound { .. }), - "Expected StdError::NotFound, received {}", - err - ); - } - } - } - - #[test] - fn can_mint_by_minter() { - let mut deps = mock_dependencies(&[]); - - let genesis = String::from("genesis"); - let amount = Uint128::new(11223344); - let minter = String::from("asmodat"); - let limit = Uint128::new(511223344); - do_instantiate_with_minter(deps.as_mut(), &genesis, amount, &minter, Some(limit)); - - // minter can mint coins to some winner - let winner = String::from("lucky"); - let prize = Uint128::new(222_222_222); - let msg = ExecuteMsg::Mint { - recipient: winner.clone(), - amount: prize, - }; - - let info = mock_info(minter.as_ref(), &[]); - let env = mock_env(); - let res = execute(deps.as_mut(), env, info, msg).unwrap(); - assert_eq!(0, res.messages.len()); - assert_eq!(get_balance(deps.as_ref(), genesis), amount); - assert_eq!(get_balance(deps.as_ref(), winner.clone()), prize); - - // but cannot mint nothing - let msg = ExecuteMsg::Mint { - recipient: winner.clone(), - amount: Uint128::zero(), - }; - let info = mock_info(minter.as_ref(), &[]); - let env = mock_env(); - let err = execute(deps.as_mut(), env, info, msg).unwrap_err(); - assert_eq!(err, ContractError::InvalidZeroAmount {}); - - // but if it exceeds cap (even over multiple rounds), it fails - // cap is enforced - let msg = ExecuteMsg::Mint { - recipient: winner, - amount: Uint128::new(333_222_222), - }; - let info = mock_info(minter.as_ref(), &[]); - let env = mock_env(); - let err = execute(deps.as_mut(), env, info, msg).unwrap_err(); - assert_eq!(err, ContractError::CannotExceedCap {}); - } - - #[test] - fn others_cannot_mint() { - let mut deps = mock_dependencies(&[]); - do_instantiate_with_minter( - deps.as_mut(), - &String::from("genesis"), - Uint128::new(1234), - &String::from("minter"), - None, - ); - - let msg = ExecuteMsg::Mint { - recipient: String::from("lucky"), - amount: Uint128::new(222), - }; - let info = mock_info("anyone else", &[]); - let env = mock_env(); - let err = execute(deps.as_mut(), env, info, msg).unwrap_err(); - assert_eq!(err, ContractError::Unauthorized {}); - } - - #[test] - fn no_one_mints_if_minter_unset() { - let mut deps = mock_dependencies(&[]); - do_instantiate(deps.as_mut(), &String::from("genesis"), Uint128::new(1234)); - - let msg = ExecuteMsg::Mint { - recipient: String::from("lucky"), - amount: Uint128::new(222), - }; - let info = mock_info("genesis", &[]); - let env = mock_env(); - let err = execute(deps.as_mut(), env, info, msg).unwrap_err(); - assert_eq!(err, ContractError::Unauthorized {}); - } - - #[test] - fn instantiate_multiple_accounts() { - let mut deps = mock_dependencies(&[]); - let amount1 = Uint128::from(11223344u128); - let addr1 = String::from("addr0001"); - let amount2 = Uint128::from(7890987u128); - let addr2 = String::from("addr0002"); - let instantiate_msg = InstantiateMsg { - name: "Bash Shell".to_string(), - symbol: "BASH".to_string(), - decimals: 6, - initial_balances: vec![ - Cw20Coin { - address: addr1.clone(), - amount: amount1, - }, - Cw20Coin { - address: addr2.clone(), - amount: amount2, - }, - ], - mint: None, - marketing: None, - }; - let info = mock_info("creator", &[]); - let env = mock_env(); - let res = instantiate(deps.as_mut(), env, info, instantiate_msg).unwrap(); - assert_eq!(0, res.messages.len()); - - assert_eq!( - query_token_info(deps.as_ref()).unwrap(), - TokenInfoResponse { - name: "Bash Shell".to_string(), - symbol: "BASH".to_string(), - decimals: 6, - total_supply: amount1 + amount2, - } - ); - assert_eq!(get_balance(deps.as_ref(), addr1), amount1); - assert_eq!(get_balance(deps.as_ref(), addr2), amount2); - } - - #[test] - fn transfer() { - let mut deps = mock_dependencies(&coins(2, "token")); - let addr1 = String::from("addr0001"); - let addr2 = String::from("addr0002"); - let amount1 = Uint128::from(12340000u128); - let transfer = Uint128::from(76543u128); - let too_much = Uint128::from(12340321u128); - - do_instantiate(deps.as_mut(), &addr1, amount1); - - // cannot transfer nothing - let info = mock_info(addr1.as_ref(), &[]); - let env = mock_env(); - let msg = ExecuteMsg::Transfer { - recipient: addr2.clone(), - amount: Uint128::zero(), - }; - let err = execute(deps.as_mut(), env, info, msg).unwrap_err(); - assert_eq!(err, ContractError::InvalidZeroAmount {}); - - // cannot send more than we have - let info = mock_info(addr1.as_ref(), &[]); - let env = mock_env(); - let msg = ExecuteMsg::Transfer { - recipient: addr2.clone(), - amount: too_much, - }; - let err = execute(deps.as_mut(), env, info, msg).unwrap_err(); - assert!(matches!(err, ContractError::Std(StdError::Overflow { .. }))); - - // cannot send from empty account - let info = mock_info(addr2.as_ref(), &[]); - let env = mock_env(); - let msg = ExecuteMsg::Transfer { - recipient: addr1.clone(), - amount: transfer, - }; - let err = execute(deps.as_mut(), env, info, msg).unwrap_err(); - assert!(matches!(err, ContractError::Std(StdError::Overflow { .. }))); - - // valid transfer - let info = mock_info(addr1.as_ref(), &[]); - let env = mars_core::testing::mock_env(MockEnvParams { - block_height: 100_000, - ..Default::default() - }); - let msg = ExecuteMsg::Transfer { - recipient: addr2.clone(), - amount: transfer, - }; - let res = execute(deps.as_mut(), env, info, msg).unwrap(); - assert_eq!(res.messages.len(), 0); - - let remainder = amount1.checked_sub(transfer).unwrap(); - assert_eq!(get_balance(deps.as_ref(), addr1.clone()), remainder); - assert_eq!(get_balance(deps.as_ref(), addr2.clone()), transfer); - assert_eq!( - query_balance_at(deps.as_ref(), addr1, 100_000) - .unwrap() - .balance, - remainder - ); - assert_eq!( - query_balance_at(deps.as_ref(), addr2, 100_000) - .unwrap() - .balance, - transfer - ); - assert_eq!( - query_token_info(deps.as_ref()).unwrap().total_supply, - amount1 - ); - } - - #[test] - fn burn() { - let mut deps = mock_dependencies(&coins(2, "token")); - let addr1 = String::from("addr0001"); - let amount1 = Uint128::from(12340000u128); - let burn = Uint128::from(76543u128); - let too_much = Uint128::from(12340321u128); - - do_instantiate(deps.as_mut(), &addr1, amount1); - - // cannot burn nothing - let info = mock_info(addr1.as_ref(), &[]); - let env = mock_env(); - let msg = ExecuteMsg::Burn { - amount: Uint128::zero(), - }; - let err = execute(deps.as_mut(), env, info, msg).unwrap_err(); - assert_eq!(err, ContractError::InvalidZeroAmount {}); - assert_eq!( - query_token_info(deps.as_ref()).unwrap().total_supply, - amount1 - ); - - // cannot burn more than we have - let info = mock_info(addr1.as_ref(), &[]); - let env = mock_env(); - let msg = ExecuteMsg::Burn { amount: too_much }; - let err = execute(deps.as_mut(), env, info, msg).unwrap_err(); - assert!(matches!(err, ContractError::Std(StdError::Overflow { .. }))); - assert_eq!( - query_token_info(deps.as_ref()).unwrap().total_supply, - amount1 - ); - - // valid burn reduces total supply - let info = mock_info(addr1.as_ref(), &[]); - let env = mars_core::testing::mock_env(MockEnvParams { - block_height: 200_000, - ..Default::default() - }); - let msg = ExecuteMsg::Burn { amount: burn }; - execute(deps.as_mut(), env, info, msg).unwrap(); - - let remainder = amount1.checked_sub(burn).unwrap(); - assert_eq!(get_balance(deps.as_ref(), addr1.clone()), remainder); - assert_eq!( - query_balance_at(deps.as_ref(), addr1, 200_000) - .unwrap() - .balance, - remainder - ); - assert_eq!( - query_token_info(deps.as_ref()).unwrap().total_supply, - remainder - ); - assert_eq!( - query_total_supply_at(deps.as_ref(), 200_000) - .unwrap() - .total_supply, - remainder - ); - } - - #[test] - fn send() { - let mut deps = mock_dependencies(&coins(2, "token")); - let addr1 = String::from("addr0001"); - let contract = String::from("addr0002"); - let amount1 = Uint128::from(12340000u128); - let transfer = Uint128::from(76543u128); - let too_much = Uint128::from(12340321u128); - let send_msg = Binary::from(r#"{"some":123}"#.as_bytes()); - - do_instantiate(deps.as_mut(), &addr1, amount1); - - // cannot send nothing - let info = mock_info(addr1.as_ref(), &[]); - let env = mock_env(); - let msg = ExecuteMsg::Send { - contract: contract.clone(), - amount: Uint128::zero(), - msg: send_msg.clone(), - }; - let err = execute(deps.as_mut(), env, info, msg).unwrap_err(); - assert_eq!(err, ContractError::InvalidZeroAmount {}); - - // cannot send more than we have - let info = mock_info(addr1.as_ref(), &[]); - let env = mock_env(); - let msg = ExecuteMsg::Send { - contract: contract.clone(), - amount: too_much, - msg: send_msg.clone(), - }; - let err = execute(deps.as_mut(), env, info, msg).unwrap_err(); - assert!(matches!(err, ContractError::Std(StdError::Overflow { .. }))); - - // valid transfer - let info = mock_info(addr1.as_ref(), &[]); - let env = mock_env(); - let msg = ExecuteMsg::Send { - contract: contract.clone(), - amount: transfer, - msg: send_msg.clone(), - }; - let res = execute(deps.as_mut(), env.clone(), info, msg).unwrap(); - assert_eq!(res.messages.len(), 1); - - // ensure proper send message sent - // this is the message we want delivered to the other side - let binary_msg = Cw20ReceiveMsg { - sender: addr1.clone(), - amount: transfer, - msg: send_msg, - } - .into_binary() - .unwrap(); - // and this is how it must be wrapped for the vm to process it - assert_eq!( - res.messages[0], - SubMsg::new(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: contract.clone(), - msg: binary_msg, - funds: vec![], - })) - ); - - // ensure balance is properly transferred - let remainder = amount1.checked_sub(transfer).unwrap(); - assert_eq!(get_balance(deps.as_ref(), addr1.clone()), remainder); - assert_eq!(get_balance(deps.as_ref(), contract.clone()), transfer); - assert_eq!( - query_token_info(deps.as_ref()).unwrap().total_supply, - amount1 - ); - assert_eq!( - query_balance_at(deps.as_ref(), addr1, env.block.height) - .unwrap() - .balance, - remainder - ); - assert_eq!( - query_balance_at(deps.as_ref(), contract, env.block.height) - .unwrap() - .balance, - transfer - ); - } - - #[test] - fn snapshots_are_taken_and_retrieved_correctly() { - let mut deps = mock_dependencies(&[]); - - let addr1 = String::from("addr1"); - let addr2 = String::from("addr2"); - - let mut current_total_supply = Uint128::new(100_000); - let mut current_block = 12_345; - let mut current_addr1_balance = current_total_supply; - let mut current_addr2_balance = Uint128::zero(); - - let minter = String::from("minter"); - do_instantiate_with_minter(deps.as_mut(), &addr1, current_total_supply, &minter, None); - - let mut expected_total_supplies = vec![(current_block, current_total_supply)]; - let mut expected_addr1_balances = vec![(current_block, current_addr1_balance)]; - let mut expected_addr2_balances: Vec<(u64, Uint128)> = vec![]; - - // Mint to addr2 3 times - for _i in 0..3 { - current_block += 100_000; - - let mint_amount = Uint128::new(20_000); - current_total_supply += mint_amount; - current_addr2_balance += mint_amount; - - let info = mock_info(minter.as_str(), &[]); - let env = mars_core::testing::mock_env(MockEnvParams { - block_height: current_block, - ..Default::default() - }); - - let msg = ExecuteMsg::Mint { - recipient: addr2.clone(), - amount: mint_amount, - }; - - execute(deps.as_mut(), env, info, msg).unwrap(); - - expected_total_supplies.push((current_block, current_total_supply)); - expected_addr2_balances.push((current_block, current_addr2_balance)); - } - - // Transfer from addr1 to addr2 4 times - for _i in 0..4 { - current_block += 60_000; - - let transfer_amount = Uint128::new(10_000); - current_addr1_balance = current_addr1_balance - transfer_amount; - current_addr2_balance += transfer_amount; - - let info = mock_info(addr1.as_str(), &[]); - let env = mars_core::testing::mock_env(MockEnvParams { - block_height: current_block, - ..Default::default() - }); - - let msg = ExecuteMsg::Transfer { - recipient: addr2.clone(), - amount: transfer_amount, - }; - - execute(deps.as_mut(), env, info, msg).unwrap(); - - expected_addr1_balances.push((current_block, current_addr1_balance)); - expected_addr2_balances.push((current_block, current_addr2_balance)); - } - - // Burn from addr2 3 times - for _i in 0..3 { - current_block += 50_000; - - let burn_amount = Uint128::new(20_000); - current_total_supply = current_total_supply - burn_amount; - current_addr2_balance = current_addr2_balance - burn_amount; - - let info = mock_info(addr2.as_str(), &[]); - - let env = mars_core::testing::mock_env(MockEnvParams { - block_height: current_block, - ..Default::default() - }); - - let msg = ExecuteMsg::Burn { - amount: burn_amount, - }; - - execute(deps.as_mut(), env, info, msg).unwrap(); - - expected_total_supplies.push((current_block, current_total_supply)); - expected_addr2_balances.push((current_block, current_addr2_balance)); - } - - // Check total supplies; - let mut total_supply_previous_value = Uint128::zero(); - for (block, expected_total_supply) in expected_total_supplies { - // block before gives previous value - assert_eq!( - query_total_supply_at(deps.as_ref(), block - 1) - .unwrap() - .total_supply, - total_supply_previous_value - ); - - // block gives expected value - assert_eq!( - query_total_supply_at(deps.as_ref(), block) - .unwrap() - .total_supply, - expected_total_supply, - ); - - // block after still gives expected value - assert_eq!( - query_total_supply_at(deps.as_ref(), block + 10) - .unwrap() - .total_supply, - expected_total_supply, - ); - - total_supply_previous_value = expected_total_supply; - } - - // Check addr1 balances - let mut balance_previous_value = Uint128::zero(); - for (block, expected_balance) in expected_addr1_balances { - // block before gives previous value - assert_eq!( - query_balance_at(deps.as_ref(), addr1.clone(), block - 10) - .unwrap() - .balance, - balance_previous_value - ); - - // block gives expected value - assert_eq!( - query_balance_at(deps.as_ref(), addr1.clone(), block) - .unwrap() - .balance, - expected_balance - ); - - // block after still gives expected value - assert_eq!( - query_balance_at(deps.as_ref(), addr1.clone(), block + 1) - .unwrap() - .balance, - expected_balance - ); - - balance_previous_value = expected_balance; - } - - // Check addr2 balances - let mut balance_previous_value = Uint128::zero(); - for (block, expected_balance) in expected_addr2_balances { - // block before gives previous value - assert_eq!( - query_balance_at(deps.as_ref(), addr2.clone(), block - 10) - .unwrap() - .balance, - balance_previous_value - ); - - // block gives expected value - assert_eq!( - query_balance_at(deps.as_ref(), addr2.clone(), block) - .unwrap() - .balance, - expected_balance - ); - - // block after still gives expected value - assert_eq!( - query_balance_at(deps.as_ref(), addr2.clone(), block + 1) - .unwrap() - .balance, - expected_balance - ); - - balance_previous_value = expected_balance; - } - } -} diff --git a/contracts/mars-xmars-token/src/core.rs b/contracts/mars-xmars-token/src/core.rs deleted file mode 100644 index 30f18f34d..000000000 --- a/contracts/mars-xmars-token/src/core.rs +++ /dev/null @@ -1,58 +0,0 @@ -use cosmwasm_std::{Addr, Env, StdResult, Storage, Uint128}; -use cw20_base::state::{BALANCES, TOKEN_INFO}; -use cw20_base::ContractError; - -use crate::snapshots::{capture_balance_snapshot, capture_total_supply_snapshot}; - -pub fn transfer( - storage: &mut dyn Storage, - env: &Env, - option_sender: Option<&Addr>, - option_recipient: Option<&Addr>, - amount: Uint128, -) -> Result<(), ContractError> { - if amount.is_zero() { - return Err(ContractError::InvalidZeroAmount {}); - } - - if let Some(sender_addr) = option_sender { - let sender_balance_new = BALANCES.update( - storage, - sender_addr, - |balance: Option| -> StdResult<_> { - Ok(balance.unwrap_or_default().checked_sub(amount)?) - }, - )?; - capture_balance_snapshot(storage, env, sender_addr, sender_balance_new)?; - }; - - if let Some(recipient_addr) = option_recipient { - let recipient_balance_new = BALANCES.update( - storage, - recipient_addr, - |balance: Option| -> StdResult<_> { Ok(balance.unwrap_or_default() + amount) }, - )?; - capture_balance_snapshot(storage, env, recipient_addr, recipient_balance_new)?; - } - - Ok(()) -} - -pub fn burn( - storage: &mut dyn Storage, - env: &Env, - sender_addr: &Addr, - amount: Uint128, -) -> Result<(), ContractError> { - // lower balance - transfer(storage, env, Some(sender_addr), None, amount)?; - - // reduce total_supply - let new_token_info = TOKEN_INFO.update(storage, |mut info| -> StdResult<_> { - info.total_supply = info.total_supply.checked_sub(amount)?; - Ok(info) - })?; - - capture_total_supply_snapshot(storage, env, new_token_info.total_supply)?; - Ok(()) -} diff --git a/contracts/mars-xmars-token/src/lib.rs b/contracts/mars-xmars-token/src/lib.rs deleted file mode 100755 index ec7db9051..000000000 --- a/contracts/mars-xmars-token/src/lib.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub mod allowances; -pub mod contract; -pub mod core; -pub mod snapshots; - -pub use mars_core::xmars_token::*; diff --git a/contracts/mars-xmars-token/src/snapshots.rs b/contracts/mars-xmars-token/src/snapshots.rs deleted file mode 100644 index 6841defac..000000000 --- a/contracts/mars-xmars-token/src/snapshots.rs +++ /dev/null @@ -1,60 +0,0 @@ -use cosmwasm_std::{Addr, Env, Order, StdResult, Storage, Uint128}; -use cw_storage_plus::{Bound, Map, Prefix, U64Key}; - -// STATE -pub const TOTAL_SUPPLY_SNAPSHOTS: Map = Map::new("total_supply_snapshots"); -pub const BALANCE_SNAPSHOTS: Map<(&Addr, U64Key), Uint128> = Map::new("balance_snapshots"); - -// CORE - -fn get_snapshot_value_at( - storage: &dyn Storage, - prefix: Prefix, - block: u64, -) -> StdResult { - // Look for the last value recorded before the current block (if none then value is zero) - let end = Bound::inclusive(U64Key::new(block)); - let last_value_up_to_block = prefix - .range(storage, None, Some(end), Order::Descending) - .next(); - - if let Some(value) = last_value_up_to_block { - let (_, v) = value?; - return Ok(v); - } - - Ok(Uint128::zero()) -} - -// BALANCE - -pub fn capture_balance_snapshot( - storage: &mut dyn Storage, - env: &Env, - addr: &Addr, - balance: Uint128, -) -> StdResult<()> { - BALANCE_SNAPSHOTS.save(storage, (addr, U64Key::new(env.block.height)), &balance) -} - -pub fn get_balance_snapshot_value_at( - storage: &dyn Storage, - addr: &Addr, - block: u64, -) -> StdResult { - get_snapshot_value_at(storage, BALANCE_SNAPSHOTS.prefix(addr), block) -} - -// TOTAL SUPPLY - -pub fn capture_total_supply_snapshot( - storage: &mut dyn Storage, - env: &Env, - total_supply: Uint128, -) -> StdResult<()> { - TOTAL_SUPPLY_SNAPSHOTS.save(storage, U64Key::new(env.block.height), &total_supply) -} - -pub fn get_total_supply_snapshot_value_at(storage: &dyn Storage, block: u64) -> StdResult { - get_snapshot_value_at(storage, TOTAL_SUPPLY_SNAPSHOTS.prefix(()), block) -} diff --git a/packages/mars-core/Cargo.toml b/packages/mars-core/Cargo.toml index b2eee691a..5cd7d1f8f 100644 --- a/packages/mars-core/Cargo.toml +++ b/packages/mars-core/Cargo.toml @@ -1,15 +1,21 @@ [package] -name = "mars-core" -version = "1.0.2" -authors = ["Spike Spiegel "] +name = "mars-outpost" +version = "0.1.0" +authors = [ + "Ahmad Kaouk ", + "larry_0x ", + "Piotr Babel", + "Harry Scholes" +] edition = "2018" -description = "Mars is a fully automated, on-chain credit protocol built on Terra and governed by a decentralised community of users and developers" +description = "Mars is a fully automated, on-chain credit protocol governed by a decentralised community of users and developers" license = "GPL-3.0" repository = "https://github.com/mars-protocol/mars-core" homepage = "https://marsprotocol.io" documentation = "https://docs.marsprotocol.io/mars-protocol/developers/protocol-overview" readme = "README.md" -keywords = ["terra", "cosmwasm"] +keywords = ["mars", "cosmwasm"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -31,11 +37,5 @@ schemars = "0.8.1" serde = { version = "1.0.103", default-features = false, features = ["derive"] } thiserror = "1.0.23" -astroport = "1.0" - -basset = { git = "https://github.com/lidofinance/lido-terra-contracts", tag = "v1.0.2" } - -stader = { git = "https://github.com/stader-labs/stader-liquid-token", package = "staking", tag = "v0.2.1", features = ["library"] } - [profile.release] overflow-checks = true diff --git a/packages/mars-core/src/asset.rs b/packages/mars-core/src/asset.rs index 26b12b66e..a1d83ffd1 100644 --- a/packages/mars-core/src/asset.rs +++ b/packages/mars-core/src/asset.rs @@ -5,7 +5,6 @@ use serde::{Deserialize, Serialize}; use crate::helpers::cw20_get_balance; use crate::tax::deduct_tax; -use astroport::asset::AssetInfo as AstroportAssetInfo; /// Represents either a native asset or a cw20. Meant to be used as part of a msg /// in a contract call and not to be used internally @@ -43,20 +42,6 @@ impl Asset { } } -// Cast astroport::asset::AssetInfo into mars_core::asset::Asset so that they can be compared -impl From<&AstroportAssetInfo> for Asset { - fn from(info: &AstroportAssetInfo) -> Self { - match info { - AstroportAssetInfo::Token { contract_addr } => Asset::Cw20 { - contract_addr: contract_addr.to_string(), - }, - AstroportAssetInfo::NativeToken { denom } => Asset::Native { - denom: denom.clone(), - }, - } - } -} - #[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum AssetType { diff --git a/packages/mars-core/src/council.rs b/packages/mars-core/src/council.rs deleted file mode 100644 index f84ccd97c..000000000 --- a/packages/mars-core/src/council.rs +++ /dev/null @@ -1,295 +0,0 @@ -use cosmwasm_std::{Addr, CosmosMsg, Uint128}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use crate::error::MarsError; -use crate::helpers::decimal_param_le_one; -use crate::math::decimal::Decimal; - -use self::error::ContractError; - -pub const MINIMUM_PROPOSAL_REQUIRED_THRESHOLD_PERCENTAGE: u64 = 50; -pub const MAXIMUM_PROPOSAL_REQUIRED_THRESHOLD_PERCENTAGE: u64 = 100; - -/// Council global configuration -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct Config { - /// Address provider returns addresses for all protocol contracts - pub address_provider_address: Addr, - /// Blocks during which a proposal is active since being submitted - pub proposal_voting_period: u64, - /// Blocks that need to pass since a proposal succeeds in order for it to be available to be - /// executed - pub proposal_effective_delay: u64, - /// Blocks after the effective_delay during which a successful proposal can be activated before it expires - pub proposal_expiration_period: u64, - /// Number of Mars needed to make a proposal. Will be returned if successful. Will be - /// distributed between stakers if rejected. - pub proposal_required_deposit: Uint128, - /// % of total voting power required to participate in the proposal in order to consider it successfull - pub proposal_required_quorum: Decimal, - /// % of for votes required in order to consider the proposal successful - pub proposal_required_threshold: Decimal, -} - -impl Config { - pub fn validate(&self) -> Result<(), ContractError> { - decimal_param_le_one(&self.proposal_required_quorum, "proposal_required_quorum")?; - - let minimum_proposal_required_threshold = - Decimal::percent(MINIMUM_PROPOSAL_REQUIRED_THRESHOLD_PERCENTAGE); - let maximum_proposal_required_threshold = - Decimal::percent(MAXIMUM_PROPOSAL_REQUIRED_THRESHOLD_PERCENTAGE); - - if !(self.proposal_required_threshold >= minimum_proposal_required_threshold - && self.proposal_required_threshold <= maximum_proposal_required_threshold) - { - return Err(MarsError::InvalidParam { - param_name: "proposal_required_threshold".to_string(), - invalid_value: self.proposal_required_threshold.to_string(), - predicate: format!( - ">= {} and <= {}", - minimum_proposal_required_threshold, maximum_proposal_required_threshold - ), - } - .into()); - } - - Ok(()) - } -} - -/// Global state -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct GlobalState { - /// Number of proposals - pub proposal_count: u64, -} - -/// Proposal metadata stored in state -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct Proposal { - pub proposal_id: u64, - /// Address submitting the proposal - pub submitter_address: Addr, - /// Wether the proposal is Active, Passed, Rejected or Executed - pub status: ProposalStatus, - /// Number of for votes - pub for_votes: Uint128, - /// Number of against votes - pub against_votes: Uint128, - /// Block at which voting for the porposal starts - pub start_height: u64, - /// Block at which voting for the porposal ends - pub end_height: u64, - /// Title for the proposal - pub title: String, - /// Description for the proposal - pub description: String, - /// Link provided for cases where the proposal description is too large or - /// some other external resource is intended to be associated with the proposal - pub link: Option, - /// Set of messages available to get executed if the proposal passes - pub messages: Option>, - /// MARS tokens deposited on the proposal submission. Will be returned to - /// submitter if proposal passes and sent to xMars stakers otherwise - pub deposit_amount: Uint128, -} - -/// Execute call that will be executed by the DAO if the proposal succeeds -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct ProposalMessage { - /// Determines order of execution lower order will be executed first - pub execution_order: u64, - /// CosmosMsg that will be executed by the council - pub msg: CosmosMsg, -} - -/// Proposal Status -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum ProposalStatus { - /// Proposal is being voted on - Active, - /// Proposal has been approved but has not been executed yet - Passed, - /// Proposal was rejected - Rejected, - /// Proposal has been approved and executed - Executed, -} - -/// Single vote made by an address -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct ProposalVote { - /// For or Against the proposal - pub option: ProposalVoteOption, - /// Voting power - pub power: Uint128, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum ProposalVoteOption { - For, - Against, -} - -impl std::fmt::Display for ProposalVoteOption { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - let display_str = match self { - ProposalVoteOption::For => "for", - ProposalVoteOption::Against => "against", - }; - write!(f, "{}", display_str) - } -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct ProposalsListResponse { - /// Total proposals submitted - pub proposal_count: u64, - /// List of proposals (paginated by query) - pub proposal_list: Vec, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct ProposalVotesResponse { - pub proposal_id: u64, - pub votes: Vec, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct ProposalVoteResponse { - pub voter_address: String, - pub option: ProposalVoteOption, - pub power: Uint128, -} - -pub mod msg { - use cosmwasm_std::Uint128; - use cw20::Cw20ReceiveMsg; - use schemars::JsonSchema; - use serde::{Deserialize, Serialize}; - - use crate::math::decimal::Decimal; - - use super::{ProposalMessage, ProposalVoteOption}; - - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] - pub struct InstantiateMsg { - pub config: CreateOrUpdateConfig, - } - - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, Default)] - pub struct CreateOrUpdateConfig { - pub address_provider_address: Option, - - pub proposal_voting_period: Option, - pub proposal_effective_delay: Option, - pub proposal_expiration_period: Option, - pub proposal_required_deposit: Option, - pub proposal_required_quorum: Option, - pub proposal_required_threshold: Option, - } - - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] - #[serde(rename_all = "snake_case")] - pub enum ExecuteMsg { - /// Implementation cw20 receive msg - Receive(Cw20ReceiveMsg), - - /// Vote for a proposal - CastVote { - proposal_id: u64, - vote: ProposalVoteOption, - }, - - /// End proposal after voting period has passed - EndProposal { proposal_id: u64 }, - - /// Execute a successful proposal - ExecuteProposal { proposal_id: u64 }, - - /// Update config - UpdateConfig { config: CreateOrUpdateConfig }, - } - - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] - #[serde(rename_all = "snake_case")] - pub enum ReceiveMsg { - /// Submit a proposal to be voted - /// Requires a Mars deposit equal or greater than the proposal_required_deposit - SubmitProposal { - title: String, - description: String, - link: Option, - messages: Option>, - }, - } - - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] - #[serde(rename_all = "snake_case")] - pub enum QueryMsg { - Config {}, - Proposals { - start: Option, - limit: Option, - }, - Proposal { - proposal_id: u64, - }, - ProposalVotes { - proposal_id: u64, - start_after: Option, - limit: Option, - }, - } -} - -pub mod error { - use cosmwasm_std::StdError; - use thiserror::Error; - - use crate::error::MarsError; - - #[derive(Error, Debug, PartialEq)] - pub enum ContractError { - #[error("{0}")] - Std(#[from] StdError), - - #[error("{0}")] - Mars(#[from] MarsError), - - #[error("Invalid Proposal: {error:?}")] - InvalidProposal { error: String }, - - #[error("Proposal is not active")] - ProposalNotActive {}, - - #[error("User has already voted on this proposal")] - VoteUserAlreadyVoted {}, - #[error("User has no voting power at block: {block:?}")] - VoteNoVotingPower { block: u64 }, - #[error("Voting period has ended")] - VoteVotingPeriodEnded {}, - - #[error("Voting period has not ended")] - EndProposalVotingPeriodNotEnded {}, - - #[error("Proposal has not passed or has already been executed")] - ExecuteProposalNotPassed {}, - #[error("Proposal must end it's delay period in order to be executed")] - ExecuteProposalDelayNotEnded {}, - #[error("Proposal has expired")] - ExecuteProposalExpired {}, - } - - impl ContractError { - pub fn invalid_proposal>(error: S) -> ContractError { - ContractError::InvalidProposal { - error: error.into(), - } - } - } -} diff --git a/packages/mars-core/src/lib.rs b/packages/mars-core/src/lib.rs index c231706c3..e56ded2d1 100644 --- a/packages/mars-core/src/lib.rs +++ b/packages/mars-core/src/lib.rs @@ -1,6 +1,5 @@ // Contracts pub mod address_provider; -pub mod council; pub mod cw20_core; pub mod incentives; pub mod ma_token; @@ -8,10 +7,6 @@ pub mod oracle; pub mod protocol_rewards_collector; pub mod red_bank; pub mod safety_fund; -pub mod staking; -pub mod treasury; -pub mod vesting; -pub mod xmars_token; // Types pub mod asset; @@ -20,7 +15,6 @@ pub mod math; // Helpers pub mod error; pub mod helpers; -pub mod swapping; pub mod tax; #[cfg(not(target_arch = "wasm32"))] diff --git a/packages/mars-core/src/oracle.rs b/packages/mars-core/src/oracle.rs index 6748034cc..42ccf1cf5 100644 --- a/packages/mars-core/src/oracle.rs +++ b/packages/mars-core/src/oracle.rs @@ -3,109 +3,33 @@ use std::fmt; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use cosmwasm_std::{Addr, Api, StdResult, Uint128}; +use cosmwasm_std::{Addr, Api, StdResult}; use crate::math::decimal::Decimal; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] -pub enum PriceSource { - /// Returns a fixed value; used for UST +pub enum PriceSource { + /// Returns a fixed value; used for OSMO Fixed { price: Decimal }, - /// Native Terra stablecoins transaction rate quoted in UST - Native { denom: String }, - /// Astroport spot price quoted in UST - /// - /// NOTE: `pair_address` must point to an astroport pair consists of the asset of intereset and UST - AstroportSpot { - /// Address of the Astroport pair - pair_address: A, - }, - /// Astroport TWAP price quoted in UST - /// - /// NOTE: `pair_address` must point to an astroport pair consists of the asset of intereset and UST - AstroportTwap { - /// Address of the Astroport pair - pair_address: A, - /// Address of the asset of interest - /// - /// NOTE: Spot price in intended for CW20 tokens. Terra native tokens should use Fixed or - /// Native price sources. - window_size: u64, - /// When calculating averaged price, we take the most recent TWAP snapshot and find a second - /// snapshot in the range of window_size +/- tolerance. For example, if window size is 5 minutes - /// and tolerance is 1 minute, we look for snapshots that are 4 - 6 minutes back in time from - /// the most recent snapshot. - /// - /// If there are multiple snapshots within the range, we take the one that is closest to the - /// desired window size. - tolerance: u64, - }, - /// Astroport liquidity token - /// - /// NOTE: Astroport's pair contract does not have a query command to check the address of the LP - /// token associated with a pair. Therefore, we can't implement relevant checks in the contract. - /// The owner must make sure the addresses supplied are accurate - AstroportLiquidityToken { - /// Address of the asset of interest - pair_address: A, - }, - /// stLuna price calculated from stLuna/Luna exchange rate from Lido hub contract and Luna price from current price source - Stluna { hub_address: A }, - /// Lunax price calculated from Lunax/Luna exchange rate from Stader staking contract and Luna price from current price source - Lunax { staking_address: A }, } -impl fmt::Display for PriceSource { +impl fmt::Display for PriceSource { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let label = match self { PriceSource::Fixed { .. } => "fixed", - PriceSource::Native { .. } => "native", - PriceSource::AstroportSpot { .. } => "astroport_spot", - PriceSource::AstroportTwap { .. } => "astroport_twap", - PriceSource::AstroportLiquidityToken { .. } => "astroport_liquidity_token", - PriceSource::Stluna { .. } => "stluna", - PriceSource::Lunax { .. } => "lunax", }; write!(f, "{}", label) } } -pub type PriceSourceUnchecked = PriceSource; -pub type PriceSourceChecked = PriceSource; +pub type PriceSourceUnchecked = PriceSource; +pub type PriceSourceChecked = PriceSource; impl PriceSourceUnchecked { - pub fn to_checked(&self, api: &dyn Api) -> StdResult { + pub fn to_checked(&self, _api: &dyn Api) -> StdResult { Ok(match self { PriceSourceUnchecked::Fixed { price } => PriceSourceChecked::Fixed { price: *price }, - PriceSourceUnchecked::Native { denom } => PriceSourceChecked::Native { - denom: denom.clone(), - }, - PriceSourceUnchecked::AstroportSpot { pair_address } => { - PriceSourceChecked::AstroportSpot { - pair_address: api.addr_validate(pair_address)?, - } - } - PriceSourceUnchecked::AstroportTwap { - pair_address, - window_size, - tolerance, - } => PriceSourceChecked::AstroportTwap { - pair_address: api.addr_validate(pair_address)?, - window_size: *window_size, - tolerance: *tolerance, - }, - PriceSourceUnchecked::AstroportLiquidityToken { pair_address } => { - PriceSourceChecked::AstroportLiquidityToken { - pair_address: api.addr_validate(pair_address)?, - } - } - PriceSourceUnchecked::Stluna { hub_address } => PriceSourceChecked::Stluna { - hub_address: api.addr_validate(hub_address)?, - }, - PriceSourceUnchecked::Lunax { staking_address } => PriceSourceChecked::Lunax { - staking_address: api.addr_validate(staking_address)?, - }, }) } } @@ -116,14 +40,6 @@ pub struct Config { pub owner: Addr, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct AstroportTwapSnapshot { - /// Timestamp of the most recent TWAP data update - pub timestamp: u64, - /// Cumulative price of the asset retrieved by the most recent TWAP data update - pub price_cumulative: Uint128, -} - pub mod msg { use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -146,8 +62,6 @@ pub mod msg { asset: Asset, price_source: PriceSourceUnchecked, }, - /// Fetch cumulative prices from Astroport pairs and record in contract storage - RecordTwapSnapshots { assets: Vec }, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] diff --git a/packages/mars-core/src/protocol_rewards_collector.rs b/packages/mars-core/src/protocol_rewards_collector.rs index a9942251c..ccea2c49a 100644 --- a/packages/mars-core/src/protocol_rewards_collector.rs +++ b/packages/mars-core/src/protocol_rewards_collector.rs @@ -2,7 +2,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use thiserror::Error; -use cosmwasm_std::{Addr, Decimal as StdDecimal}; +use cosmwasm_std::Addr; use crate::error::MarsError; use crate::helpers::decimal_param_le_one; @@ -19,10 +19,6 @@ pub struct Config { pub safety_fund_fee_share: Decimal, /// Percentage of fees that are sent to the treasury pub treasury_fee_share: Decimal, - /// Astroport factory contract address - pub astroport_factory_address: Addr, - /// Astroport max spread - pub astroport_max_spread: StdDecimal, } impl Config { @@ -67,9 +63,7 @@ pub mod msg { use schemars::JsonSchema; use serde::{Deserialize, Serialize}; - use cosmwasm_std::{CosmosMsg, Decimal as StdDecimal, Uint128}; - - use astroport::asset::AssetInfo; + use cosmwasm_std::{CosmosMsg, Uint128}; use crate::asset::Asset; use crate::math::decimal::Decimal; @@ -85,8 +79,6 @@ pub mod msg { pub address_provider_address: Option, pub safety_fund_fee_share: Option, pub treasury_fee_share: Option, - pub astroport_factory_address: Option, - pub astroport_max_spread: Option, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -114,12 +106,6 @@ pub mod msg { amount: Option, }, - /// Swap any asset on the contract to uusd - SwapAssetToUusd { - offer_asset_info: AssetInfo, - amount: Option, - }, - /// Execute Cosmos msg (only callable by owner) ExecuteCosmosMsg(CosmosMsg), } diff --git a/packages/mars-core/src/staking.rs b/packages/mars-core/src/staking.rs deleted file mode 100644 index 8c742d7f3..000000000 --- a/packages/mars-core/src/staking.rs +++ /dev/null @@ -1,134 +0,0 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use crate::math::decimal::Decimal; -use cosmwasm_std::{Addr, Decimal as StdDecimal, Uint128}; - -/// Protocol configuration -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct Config { - /// Contract owner - pub owner: Addr, - - /// Address provider address - pub address_provider_address: Addr, - - /// Astroport factory contract address - pub astroport_factory_address: Addr, - /// Astroport max spread - pub astroport_max_spread: StdDecimal, - - /// Cooldown duration in seconds - pub cooldown_duration: u64, -} - -/// Global State -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct GlobalState { - /// Total amount of Mars belonging to open claims - pub total_mars_for_claimers: Uint128, -} - -/// Unstaking cooldown data -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct Claim { - /// Block when the claim was created (Used to apply slash events when claiming) - pub created_at_block: u64, - /// Timestamp (in seconds) after which the claim is unlocked - pub cooldown_end_timestamp: u64, - /// Amount of Mars that the user is allowed to claim - pub amount: Uint128, -} - -/// Event where funds are taken from the Mars pool to cover a shortfall. The loss is covered -/// proportionally by all owners of the Mars pool (xMars holders and users with an open claim) -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct SlashEvent { - /// Percentage of total Mars slashed - pub slash_percentage: Decimal, -} - -/// Response to Claim query -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct ClaimResponse { - /// Existing claim for a given address. Will return None if it doesn't exist - pub claim: Option, -} - -pub mod msg { - use cosmwasm_std::{Decimal as StdDecimal, Uint128}; - - use cw20::Cw20ReceiveMsg; - use schemars::JsonSchema; - use serde::{Deserialize, Serialize}; - - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] - pub struct InstantiateMsg { - pub config: CreateOrUpdateConfig, - } - - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] - pub struct CreateOrUpdateConfig { - pub owner: Option, - pub address_provider_address: Option, - pub astroport_factory_address: Option, - pub astroport_max_spread: Option, - pub cooldown_duration: Option, - } - - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] - #[serde(rename_all = "snake_case")] - pub enum ExecuteMsg { - /// Update staking config - UpdateConfig { config: CreateOrUpdateConfig }, - - /// Implementation for cw20 receive msg - Receive(Cw20ReceiveMsg), - - /// Close claim sending the claimable Mars to the specified address (sender is the default) - Claim { recipient: Option }, - - /// Transfer Mars, deducting it proportionally from both xMars holders and addresses - /// with an open claim - TransferMars { amount: Uint128, recipient: String }, - - /// Swap uusd on the contract to Mars. Meant for received protocol rewards in order - /// for them to belong to xMars holders as underlying Mars. - SwapUusdToMars { amount: Option }, - } - - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] - #[serde(rename_all = "snake_case")] - pub enum ReceiveMsg { - /// Stake Mars and mint xMars in return - Stake { - /// Address to receive the xMars tokens. Set to sender if not specified - recipient: Option, - }, - - /// Burn xMars and initiate a cooldown period on which the underlying Mars - /// will be claimable. Only one open claim per address is allowed. - Unstake { - /// Address to claim the Mars tokens after cooldown. Set to sender is not specified - recipient: Option, - }, - } - - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] - #[serde(rename_all = "snake_case")] - pub enum QueryMsg { - /// Get contract config - Config {}, - /// Get contract global state - GlobalState {}, - /// Compute the amount of xMars token to be minted by staking 1 unit of Mars token. - /// The ratio may be undefined, in which case we return `Ok(None)` - XMarsPerMars {}, - /// Compute the amount of Mars token to be claimed by burning 1 unit of xMars token. - /// The ratio may be undefined, in which case we return `Ok(None)` - MarsPerXMars {}, - /// Get open claim for given user. If claim exists, slash events are applied to the amount - /// so actual amount of Mars received is given. - Claim { user_address: String }, - } -} diff --git a/packages/mars-core/src/swapping.rs b/packages/mars-core/src/swapping.rs deleted file mode 100644 index fb748865e..000000000 --- a/packages/mars-core/src/swapping.rs +++ /dev/null @@ -1,385 +0,0 @@ -use crate::helpers::cw20_get_balance; -use astroport::{ - asset::{Asset as AstroportAsset, AssetInfo, PairInfo}, - pair::ExecuteMsg as AstroportPairExecuteMsg, - querier::query_pair_info, -}; -use cosmwasm_std::{ - attr, to_binary, Addr, Coin, CosmosMsg, Decimal as StdDecimal, DepsMut, Empty, Env, Response, - StdError, StdResult, Uint128, WasmMsg, -}; -use cw20::Cw20ExecuteMsg; - -/// Swap assets via Astroport -pub fn execute_swap( - deps: DepsMut, - env: Env, - offer_asset_info: AssetInfo, - ask_asset_info: AssetInfo, - amount: Option, - astroport_factory_addr: Addr, - astroport_max_spread: Option, -) -> StdResult { - // Having the same asset as offer and ask asset doesn't make any sense - if offer_asset_info == ask_asset_info { - return Err(StdError::generic_err(format!( - "Cannot swap an asset into itself. Both offer and ask assets were specified as {}", - offer_asset_info - ))); - } - - let (contract_offer_asset_balance, offer_asset_label) = match offer_asset_info.clone() { - AssetInfo::NativeToken { denom } => ( - deps.querier - .query_balance(env.contract.address, denom.as_str())? - .amount, - denom, - ), - AssetInfo::Token { contract_addr } => { - let asset_label = String::from(contract_addr.as_str()); - ( - cw20_get_balance( - &deps.querier, - deps.api.addr_validate(&contract_addr.to_string())?, - env.contract.address, - )?, - asset_label, - ) - } - }; - - let ask_asset_label = match ask_asset_info.clone() { - AssetInfo::NativeToken { denom } => denom, - AssetInfo::Token { contract_addr } => contract_addr.to_string(), - }; - - if contract_offer_asset_balance.is_zero() { - return Err(StdError::generic_err(format!( - "Contract has no balance for the asset {}", - offer_asset_label - ))); - } - - let amount_to_swap = match amount { - Some(amount) if amount > contract_offer_asset_balance => { - return Err(StdError::generic_err(format!( - "The amount requested for swap exceeds contract balance for the asset {}", - offer_asset_label - ))); - } - Some(amount) => amount, - None => contract_offer_asset_balance, - }; - - let pair_info: PairInfo = query_pair_info( - &deps.querier, - astroport_factory_addr, - &[offer_asset_info.clone(), ask_asset_info], - )?; - - let offer_asset = AstroportAsset { - info: offer_asset_info, - amount: amount_to_swap, - }; - let send_msg = asset_into_swap_msg( - deps.api - .addr_validate(&pair_info.contract_addr.to_string())?, - offer_asset, - astroport_max_spread, - )?; - - let response = Response::new().add_message(send_msg).add_attributes(vec![ - attr("action", "swap"), - attr("offer_asset", offer_asset_label), - attr("ask_asset", ask_asset_label), - attr("offer_asset_amount", amount_to_swap), - ]); - - Ok(response) -} - -/// Construct Astroport message in order to swap assets -fn asset_into_swap_msg( - pair_contract: Addr, - offer_asset: AstroportAsset, - max_spread: Option, -) -> StdResult> { - let message = match offer_asset.info.clone() { - AssetInfo::NativeToken { denom } => CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: pair_contract.to_string(), - msg: to_binary(&AstroportPairExecuteMsg::Swap { - offer_asset: offer_asset.clone(), - belief_price: None, - max_spread, - to: None, - })?, - funds: vec![Coin { - denom, - amount: offer_asset.amount, - }], - }), - AssetInfo::Token { contract_addr } => CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: contract_addr.to_string(), - msg: to_binary(&Cw20ExecuteMsg::Send { - contract: pair_contract.to_string(), - amount: offer_asset.amount, - msg: to_binary(&AstroportPairExecuteMsg::Swap { - offer_asset, - belief_price: None, - max_spread, - to: None, - })?, - })?, - funds: vec![], - }), - }; - Ok(message) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::testing::{ - assert_generic_error_message, mock_dependencies, mock_env, MockEnvParams, - }; - use astroport::factory::PairType; - use cosmwasm_std::testing::MOCK_CONTRACT_ADDR; - use cosmwasm_std::SubMsg; - - #[test] - fn test_cannot_swap_same_assets() { - let mut deps = mock_dependencies(&[]); - let env = mock_env(MockEnvParams::default()); - - let assets = vec![ - ( - "somecoin_addr", - AssetInfo::Token { - contract_addr: Addr::unchecked("somecoin_addr"), - }, - ), - ( - "uluna", - AssetInfo::NativeToken { - denom: "uluna".to_string(), - }, - ), - ]; - for (asset_name, asset_info) in assets { - let response = execute_swap( - deps.as_mut(), - env.clone(), - asset_info.clone(), - asset_info, - None, - Addr::unchecked("astroport_factory"), - None, - ); - assert_generic_error_message( - response, - &format!("Cannot swap an asset into itself. Both offer and ask assets were specified as {}", asset_name), - ); - } - } - - #[test] - fn test_cannot_swap_asset_with_zero_balance() { - let mut deps = mock_dependencies(&[]); - let env = mock_env(MockEnvParams::default()); - - let cw20_contract_address = Addr::unchecked("cw20_zero"); - deps.querier.set_cw20_balances( - cw20_contract_address.clone(), - &[(Addr::unchecked(MOCK_CONTRACT_ADDR), Uint128::zero())], - ); - - let offer_asset_info = AssetInfo::Token { - contract_addr: cw20_contract_address, - }; - let ask_asset_info = AssetInfo::NativeToken { - denom: "uusd".to_string(), - }; - - let response = execute_swap( - deps.as_mut(), - env, - offer_asset_info, - ask_asset_info, - None, - Addr::unchecked("astroport_factory"), - None, - ); - assert_generic_error_message(response, "Contract has no balance for the asset cw20_zero") - } - - #[test] - fn test_cannot_swap_more_than_contract_balance() { - let mut deps = mock_dependencies(&[Coin { - denom: "somecoin".to_string(), - amount: Uint128::new(1_000_000), - }]); - let env = mock_env(MockEnvParams::default()); - - let offer_asset_info = AssetInfo::NativeToken { - denom: "somecoin".to_string(), - }; - let ask_asset_info = AssetInfo::Token { - contract_addr: Addr::unchecked("cw20_token"), - }; - - let response = execute_swap( - deps.as_mut(), - env, - offer_asset_info, - ask_asset_info, - Some(Uint128::new(1_000_001)), - Addr::unchecked("astroport_factory"), - None, - ); - assert_generic_error_message( - response, - "The amount requested for swap exceeds contract balance for the asset somecoin", - ) - } - - #[test] - fn test_swap_contract_token_partial_balance() { - let mut deps = mock_dependencies(&[]); - let env = mock_env(MockEnvParams::default()); - - let cw20_contract_address = Addr::unchecked("cw20"); - let contract_asset_balance = Uint128::new(1_000_000); - deps.querier.set_cw20_balances( - cw20_contract_address.clone(), - &[(Addr::unchecked(MOCK_CONTRACT_ADDR), contract_asset_balance)], - ); - - let offer_asset_info = AssetInfo::Token { - contract_addr: cw20_contract_address.clone(), - }; - let ask_asset_info = AssetInfo::Token { - contract_addr: Addr::unchecked("mars"), - }; - - deps.querier.set_astroport_pair(PairInfo { - asset_infos: [offer_asset_info.clone(), ask_asset_info.clone()], - contract_addr: Addr::unchecked("pair_cw20_mars"), - liquidity_token: Addr::unchecked("lp_cw20_mars"), - pair_type: PairType::Xyk {}, - }); - - let res = execute_swap( - deps.as_mut(), - env, - offer_asset_info, - ask_asset_info, - Some(Uint128::new(999)), - Addr::unchecked("astroport_factory"), - None, - ) - .unwrap(); - - assert_eq!( - res.messages, - vec![SubMsg::new(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: cw20_contract_address.to_string(), - msg: to_binary(&Cw20ExecuteMsg::Send { - contract: String::from("pair_cw20_mars"), - amount: Uint128::new(999), - msg: to_binary(&AstroportPairExecuteMsg::Swap { - offer_asset: AstroportAsset { - info: AssetInfo::Token { - contract_addr: cw20_contract_address.clone(), - }, - amount: Uint128::new(999), - }, - belief_price: None, - max_spread: None, - to: None, - }) - .unwrap(), - }) - .unwrap(), - funds: vec![], - }))] - ); - - assert_eq!( - res.attributes, - vec![ - attr("action", "swap"), - attr("offer_asset", cw20_contract_address.as_str()), - attr("ask_asset", "mars"), - attr("offer_asset_amount", "999"), - ] - ); - } - - #[test] - fn test_swap_native_token_total_balance() { - let contract_asset_balance = Uint128::new(1_234_567); - let mut deps = mock_dependencies(&[Coin { - denom: "uusd".to_string(), - amount: contract_asset_balance, - }]); - let env = mock_env(MockEnvParams::default()); - - let offer_asset_info = AssetInfo::NativeToken { - denom: "uusd".to_string(), - }; - let ask_asset_info = AssetInfo::Token { - contract_addr: Addr::unchecked("mars"), - }; - - deps.querier.set_astroport_pair(PairInfo { - asset_infos: [offer_asset_info.clone(), ask_asset_info.clone()], - contract_addr: Addr::unchecked("pair_uusd_mars"), - liquidity_token: Addr::unchecked("lp_uusd_mars"), - pair_type: PairType::Xyk {}, - }); - - let res = execute_swap( - deps.as_mut(), - env, - offer_asset_info, - ask_asset_info, - None, - Addr::unchecked("astroport_factory"), - Some(StdDecimal::from_ratio(1u128, 100u128)), - ) - .unwrap(); - - assert_eq!( - res.messages, - vec![SubMsg::new(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: String::from("pair_uusd_mars"), - msg: to_binary(&AstroportPairExecuteMsg::Swap { - offer_asset: AstroportAsset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: contract_asset_balance, - }, - belief_price: None, - max_spread: Some(StdDecimal::from_ratio(1u128, 100u128)), - to: None, - }) - .unwrap(), - funds: vec![Coin { - denom: "uusd".to_string(), - amount: contract_asset_balance, - }], - }))] - ); - - assert_eq!( - res.attributes, - vec![ - attr("action", "swap"), - attr("offer_asset", "uusd"), - attr("ask_asset", "mars"), - attr("offer_asset_amount", contract_asset_balance.to_string()), - ] - ); - } -} diff --git a/packages/mars-core/src/testing/astroport_factory_querier.rs b/packages/mars-core/src/testing/astroport_factory_querier.rs deleted file mode 100644 index b04115bdb..000000000 --- a/packages/mars-core/src/testing/astroport_factory_querier.rs +++ /dev/null @@ -1,31 +0,0 @@ -use std::collections::HashMap; - -use cosmwasm_std::{to_binary, Binary, ContractResult, QuerierResult, SystemError}; - -use astroport::{asset::PairInfo, factory::QueryMsg}; - -#[derive(Clone, Default)] -pub struct AstroportFactoryQuerier { - pub pairs: HashMap, -} - -impl AstroportFactoryQuerier { - pub fn handle_query(&self, request: &QueryMsg) -> QuerierResult { - let ret: ContractResult = match &request { - QueryMsg::Pair { asset_infos } => { - let key = format!("{}-{}", asset_infos[0], asset_infos[1]); - match self.pairs.get(&key) { - Some(pair_info) => to_binary(&pair_info).into(), - None => Err(SystemError::InvalidRequest { - error: format!("PairInfo is not found for {}", key), - request: Default::default(), - }) - .into(), - } - } - _ => panic!("[mock]: Unsupported Astroport factory query"), - }; - - Ok(ret).into() - } -} diff --git a/packages/mars-core/src/testing/astroport_pair_querier.rs b/packages/mars-core/src/testing/astroport_pair_querier.rs deleted file mode 100644 index 01e691799..000000000 --- a/packages/mars-core/src/testing/astroport_pair_querier.rs +++ /dev/null @@ -1,49 +0,0 @@ -use std::collections::HashMap; - -use cosmwasm_std::{to_binary, Addr, Binary, ContractResult, QuerierResult, SystemError}; - -use astroport::pair::{CumulativePricesResponse, PoolResponse, QueryMsg, SimulationResponse}; - -#[derive(Clone, Default)] -pub struct AstroportPairQuerier { - pub pairs: HashMap, - pub simulations: HashMap, - pub cumulative_prices: HashMap, -} - -impl AstroportPairQuerier { - pub fn handle_query(&self, contract_addr: &Addr, request: &QueryMsg) -> QuerierResult { - let key = contract_addr.to_string(); - let ret: ContractResult = match &request { - QueryMsg::Pool {} => match self.pairs.get(&key) { - Some(pool_response) => to_binary(&pool_response).into(), - None => Err(SystemError::InvalidRequest { - error: format!("PoolResponse is not found for {}", key), - request: Default::default(), - }) - .into(), - }, - QueryMsg::CumulativePrices {} => match self.cumulative_prices.get(&key) { - Some(cumulative_prices_response) => to_binary(&cumulative_prices_response).into(), - None => Err(SystemError::InvalidRequest { - error: format!("CumulativePricesResponse is not found for {}", key), - request: Default::default(), - }) - .into(), - }, - QueryMsg::Simulation { .. } => match self.simulations.get(&key) { - Some(simulation_response) => to_binary(&simulation_response).into(), - None => Err(SystemError::InvalidRequest { - error: format!("SimulationResponse is not found for {}", key), - request: Default::default(), - }) - .into(), - }, - _ => { - panic!("[mock]: Unsupported Astroport pair query"); - } - }; - - Ok(ret).into() - } -} diff --git a/packages/mars-core/src/testing/basset_querier.rs b/packages/mars-core/src/testing/basset_querier.rs deleted file mode 100644 index 2c19fb04c..000000000 --- a/packages/mars-core/src/testing/basset_querier.rs +++ /dev/null @@ -1,22 +0,0 @@ -use cosmwasm_std::{to_binary, Binary, ContractResult, QuerierResult}; - -use basset::hub::{QueryMsg, StateResponse}; - -#[derive(Clone, Default)] -pub struct BAssetQuerier { - pub state_response: Option, -} - -impl BAssetQuerier { - pub fn handle_query(&self, request: &QueryMsg) -> QuerierResult { - let ret: ContractResult = match &request { - QueryMsg::State {} => match self.state_response.as_ref() { - Some(resp) => to_binary(resp).into(), - None => panic!("[mock]: StateResponse is not provided for query"), - }, - _ => Err("[mock]: Unsupported basset query").into(), - }; - - Ok(ret).into() - } -} diff --git a/packages/mars-core/src/testing/mars_mock_querier.rs b/packages/mars-core/src/testing/mars_mock_querier.rs index 25724966e..e62b1dcb6 100644 --- a/packages/mars-core/src/testing/mars_mock_querier.rs +++ b/packages/mars-core/src/testing/mars_mock_querier.rs @@ -1,51 +1,27 @@ use cosmwasm_std::{ from_binary, from_slice, testing::{MockQuerier, MOCK_CONTRACT_ADDR}, - Addr, Coin, Fraction, Querier, QuerierResult, QueryRequest, StdResult, SystemError, Uint128, - WasmQuery, + Addr, Coin, Querier, QuerierResult, QueryRequest, StdResult, SystemError, Uint128, WasmQuery, }; use cw20::Cw20QueryMsg; use terra_cosmwasm::TerraQueryWrapper; -use crate::{ - address_provider, incentives, ma_token, oracle, staking, testing::mock_address_provider, - vesting, xmars_token, -}; -use astroport::{ - asset::{Asset, PairInfo}, - pair::{CumulativePricesResponse, PoolResponse, SimulationResponse}, -}; +use crate::{address_provider, incentives, ma_token, oracle, testing::mock_address_provider}; use super::{ - astroport_factory_querier::AstroportFactoryQuerier, - astroport_pair_querier::AstroportPairQuerier, cw20_querier::{mock_token_info_response, Cw20Querier}, incentives_querier::IncentivesQuerier, native_querier::NativeQuerier, oracle_querier::OracleQuerier, - staking_querier::StakingQuerier, - vesting_querier::VestingQuerier, - xmars_querier::XMarsQuerier, }; use crate::math::decimal::Decimal; -use crate::testing::basset_querier::BAssetQuerier; -use crate::testing::stader_querier::StaderQuerier; -use basset::hub::StateResponse; -use stader::msg::QueryStateResponse as LunaxStateResponse; pub struct MarsMockQuerier { base: MockQuerier, native_querier: NativeQuerier, cw20_querier: Cw20Querier, - xmars_querier: XMarsQuerier, - astroport_factory_querier: AstroportFactoryQuerier, - astroport_pair_querier: AstroportPairQuerier, oracle_querier: OracleQuerier, - staking_querier: StakingQuerier, - vesting_querier: VestingQuerier, incentives_querier: IncentivesQuerier, - basset_querier: BAssetQuerier, - stader_querier: StaderQuerier, } impl Querier for MarsMockQuerier { @@ -71,15 +47,8 @@ impl MarsMockQuerier { base, native_querier: NativeQuerier::default(), cw20_querier: Cw20Querier::default(), - xmars_querier: XMarsQuerier::default(), - astroport_factory_querier: AstroportFactoryQuerier::default(), - astroport_pair_querier: AstroportPairQuerier::default(), oracle_querier: OracleQuerier::default(), - staking_querier: StakingQuerier::default(), - vesting_querier: VestingQuerier::default(), incentives_querier: IncentivesQuerier::default(), - basset_querier: BAssetQuerier::default(), - stader_querier: StaderQuerier::default(), } } @@ -143,98 +112,6 @@ impl MarsMockQuerier { self.oracle_querier.prices.insert(asset_reference, price); } - pub fn set_staking_xmars_per_mars(&mut self, xmars_per_mars: Decimal) { - self.staking_querier.xmars_per_mars = xmars_per_mars; - self.staking_querier.mars_per_xmars = xmars_per_mars.inv().unwrap(); - } - - pub fn set_staking_mars_per_xmars(&mut self, mars_per_xmars: Decimal) { - self.staking_querier.mars_per_xmars = mars_per_xmars; - self.staking_querier.xmars_per_mars = mars_per_xmars.inv().unwrap(); - } - - pub fn set_xmars_address(&mut self, address: Addr) { - self.xmars_querier.xmars_address = address; - } - - pub fn set_xmars_balance_at(&mut self, address: Addr, block: u64, balance: Uint128) { - self.xmars_querier - .balances_at - .insert((address, block), balance); - } - - pub fn set_xmars_total_supply_at(&mut self, block: u64, balance: Uint128) { - self.xmars_querier.total_supplies_at.insert(block, balance); - } - - pub fn set_vesting_address(&mut self, address: Addr) { - self.vesting_querier.vesting_address = address; - } - - pub fn set_vesting_voting_power_at( - &mut self, - address: Addr, - block: u64, - voting_power: Uint128, - ) { - self.vesting_querier - .voting_power_at - .insert((address, block), voting_power); - } - - pub fn set_vesting_total_voting_power_at(&mut self, block: u64, total_voting_power: Uint128) { - self.vesting_querier - .total_voting_power_at - .insert(block, total_voting_power); - } - - pub fn set_astroport_pair(&mut self, pair_info: PairInfo) { - let asset_infos = &pair_info.asset_infos; - - // factory - let key = format!("{}-{}", asset_infos[0], asset_infos[1]); - self.astroport_factory_querier - .pairs - .insert(key, pair_info.clone()); - - // pair - let pool_response = PoolResponse { - assets: [ - Asset { - info: asset_infos[0].clone(), - amount: Uint128::zero(), - }, - Asset { - info: asset_infos[1].clone(), - amount: Uint128::zero(), - }, - ], - total_share: Uint128::zero(), - }; - let key = pair_info.contract_addr.to_string(); - self.astroport_pair_querier.pairs.insert(key, pool_response); - } - - pub fn set_astroport_pair_cumulative_prices( - &mut self, - contract_addr: String, - cumulative_prices: CumulativePricesResponse, - ) { - self.astroport_pair_querier - .cumulative_prices - .insert(contract_addr, cumulative_prices); - } - - pub fn set_astroport_pair_simulation( - &mut self, - contract_addr: String, - simulation: SimulationResponse, - ) { - self.astroport_pair_querier - .simulations - .insert(contract_addr, simulation); - } - pub fn set_incentives_address(&mut self, address: Addr) { self.incentives_querier.incentives_address = address; } @@ -245,14 +122,6 @@ impl MarsMockQuerier { .insert(Addr::unchecked(user_address), unclaimed_rewards); } - pub fn set_basset_state_response(&mut self, state_response: StateResponse) { - self.basset_querier.state_response = Some(state_response); - } - - pub fn set_stader_state_response(&mut self, state_response: LunaxStateResponse) { - self.stader_querier.state_response = Some(state_response); - } - pub fn handle_query(&self, request: &QueryRequest) -> QuerierResult { match &request { QueryRequest::Custom(TerraQueryWrapper { route, query_data }) => { @@ -278,12 +147,6 @@ impl MarsMockQuerier { .handle_ma_token_query(&contract_addr, ma_token_query); } - // XMars Queries - let parse_xmars_query: StdResult = from_binary(msg); - if let Ok(xmars_query) = parse_xmars_query { - return self.xmars_querier.handle_query(&contract_addr, xmars_query); - } - // Address Provider Queries let parse_address_provider_query: StdResult = from_binary(msg); @@ -302,28 +165,6 @@ impl MarsMockQuerier { .handle_query(&contract_addr, oracle_query); } - // Staking Queries - let parse_staking_query: StdResult = from_binary(msg); - if let Ok(staking_query) = parse_staking_query { - return self - .staking_querier - .handle_query(&contract_addr, staking_query); - } - - // Astroport Queries - let astroport_factory_query: StdResult = - from_binary(msg); - if let Ok(factory_query) = astroport_factory_query { - return self.astroport_factory_querier.handle_query(&factory_query); - } - - let astroport_pair_query: StdResult = from_binary(msg); - if let Ok(pair_query) = astroport_pair_query { - return self - .astroport_pair_querier - .handle_query(&contract_addr, &pair_query); - } - // Incentives Queries let parse_incentives_query: StdResult = from_binary(msg); if let Ok(incentives_query) = parse_incentives_query { @@ -332,35 +173,6 @@ impl MarsMockQuerier { .handle_query(&contract_addr, incentives_query); } - // Vesting Queries - let parse_vesting_query: StdResult = from_binary(msg); - if let Ok(vesting_query) = parse_vesting_query { - return self - .vesting_querier - .handle_query(&contract_addr, vesting_query); - } - - // NOTE: basset and stader queries have the same schema (causing panic on basset for stader query mocked) - // so we use state_response to check if there is some setup - - // bAsset Queries - let basset_query: StdResult = from_binary(msg); - match basset_query { - Ok(query) if self.basset_querier.state_response.is_some() => { - return self.basset_querier.handle_query(&query); - } - _ => {} - } - - // Stader Queries - let stader_query: StdResult = from_binary(msg); - match stader_query { - Ok(query) if self.stader_querier.state_response.is_some() => { - return self.stader_querier.handle_query(&query); - } - _ => {} - } - panic!("[mock]: Unsupported wasm query: {:?}", msg); } diff --git a/packages/mars-core/src/testing/mod.rs b/packages/mars-core/src/testing/mod.rs index a5ef12abd..56f98a34e 100644 --- a/packages/mars-core/src/testing/mod.rs +++ b/packages/mars-core/src/testing/mod.rs @@ -1,6 +1,3 @@ -mod astroport_factory_querier; -mod astroport_pair_querier; -mod basset_querier; mod cw20_querier; /// cosmwasm_std::testing overrides and custom test helpers mod helpers; @@ -10,10 +7,6 @@ mod mock_address_provider; mod mocks; mod native_querier; mod oracle_querier; -mod stader_querier; -mod staking_querier; -mod vesting_querier; -mod xmars_querier; pub use helpers::*; pub use mars_mock_querier::MarsMockQuerier; diff --git a/packages/mars-core/src/testing/stader_querier.rs b/packages/mars-core/src/testing/stader_querier.rs deleted file mode 100644 index 4554cb9c4..000000000 --- a/packages/mars-core/src/testing/stader_querier.rs +++ /dev/null @@ -1,22 +0,0 @@ -use cosmwasm_std::{to_binary, Binary, ContractResult, QuerierResult}; - -use stader::msg::{QueryMsg, QueryStateResponse}; - -#[derive(Clone, Default)] -pub struct StaderQuerier { - pub state_response: Option, -} - -impl StaderQuerier { - pub fn handle_query(&self, request: &QueryMsg) -> QuerierResult { - let ret: ContractResult = match &request { - QueryMsg::State {} => match self.state_response.as_ref() { - Some(resp) => to_binary(resp).into(), - None => panic!("[mock]: StateResponse is not provided for query"), - }, - _ => Err("[mock]: Unsupported stader query").into(), - }; - - Ok(ret).into() - } -} diff --git a/packages/mars-core/src/testing/staking_querier.rs b/packages/mars-core/src/testing/staking_querier.rs deleted file mode 100644 index a0f099aa6..000000000 --- a/packages/mars-core/src/testing/staking_querier.rs +++ /dev/null @@ -1,38 +0,0 @@ -use cosmwasm_std::{to_binary, Addr, Binary, ContractResult, QuerierResult}; - -use crate::math::decimal::Decimal; -use crate::staking::msg::QueryMsg; - -pub struct StakingQuerier { - pub xmars_per_mars: Decimal, - pub mars_per_xmars: Decimal, -} - -impl Default for StakingQuerier { - fn default() -> Self { - StakingQuerier { - xmars_per_mars: Decimal::one(), - mars_per_xmars: Decimal::one(), - } - } -} - -impl StakingQuerier { - pub fn handle_query(&self, contract_addr: &Addr, query: QueryMsg) -> QuerierResult { - let staking = Addr::unchecked("staking"); - if *contract_addr != staking { - panic!( - "[mock]: Staking request made to {} shoud be {}", - contract_addr, staking - ); - } - - let ret: ContractResult = match query { - QueryMsg::XMarsPerMars {} => to_binary(&self.xmars_per_mars).into(), - QueryMsg::MarsPerXMars {} => to_binary(&self.mars_per_xmars).into(), - _ => Err("[mock]: Unsupported staking query").into(), - }; - - Ok(ret).into() - } -} diff --git a/packages/mars-core/src/testing/vesting_querier.rs b/packages/mars-core/src/testing/vesting_querier.rs deleted file mode 100644 index fd914e6f6..000000000 --- a/packages/mars-core/src/testing/vesting_querier.rs +++ /dev/null @@ -1,66 +0,0 @@ -use std::collections::HashMap; - -use cosmwasm_std::{to_binary, Addr, QuerierResult, Uint128}; - -use crate::vesting; - -pub struct VestingQuerier { - /// vesting contract address to be used in queries - pub vesting_address: Addr, - /// maps human address and a block to a specific voting power - pub voting_power_at: HashMap<(Addr, u64), Uint128>, - /// maps a block to a specific voting power - pub total_voting_power_at: HashMap, -} - -impl Default for VestingQuerier { - fn default() -> Self { - VestingQuerier { - vesting_address: Addr::unchecked(""), - voting_power_at: HashMap::new(), - total_voting_power_at: HashMap::new(), - } - } -} - -impl VestingQuerier { - pub fn handle_query( - &self, - contract_addr: &Addr, - query: vesting::msg::QueryMsg, - ) -> QuerierResult { - if contract_addr != &self.vesting_address { - panic!( - "[mock]: made an vesting query but incentive contract address is incorrect, was: {}, should be {}", - contract_addr, - self.vesting_address - ); - } - - match query { - vesting::msg::QueryMsg::VotingPowerAt { - user_address, - block, - } => { - match self - .voting_power_at - .get(&(Addr::unchecked(user_address), block)) - { - Some(voting_power) => Ok(to_binary(voting_power).into()).into(), - // If voting power is not set, return zero - None => Ok(to_binary(&Uint128::zero()).into()).into(), - } - } - - vesting::msg::QueryMsg::TotalVotingPowerAt { block } => { - match self.total_voting_power_at.get(&block) { - Some(total_voting_power) => Ok(to_binary(total_voting_power).into()).into(), - // If voting power is not set, return zero - None => Ok(to_binary(&Uint128::zero()).into()).into(), - } - } - - _ => panic!("[mock]: unimplemented"), - } - } -} diff --git a/packages/mars-core/src/testing/xmars_querier.rs b/packages/mars-core/src/testing/xmars_querier.rs deleted file mode 100644 index 4a829fb81..000000000 --- a/packages/mars-core/src/testing/xmars_querier.rs +++ /dev/null @@ -1,83 +0,0 @@ -use std::collections::HashMap; - -use cosmwasm_std::{to_binary, Addr, QuerierResult, SystemError, Uint128}; -use cw20::BalanceResponse; - -use crate::xmars_token; - -#[derive(Clone, Debug)] -pub struct XMarsQuerier { - /// xmars token address to be used in queries - pub xmars_address: Addr, - /// maps human address and a block to a specific xmars balance - pub balances_at: HashMap<(Addr, u64), Uint128>, - /// maps block to a specific xmars balance - pub total_supplies_at: HashMap, -} - -impl XMarsQuerier { - pub fn handle_query( - &self, - contract_addr: &Addr, - query: xmars_token::msg::QueryMsg, - ) -> QuerierResult { - if contract_addr != &self.xmars_address { - panic!( - "[mock]: made an xmars query but xmars address is incorrect, was: {}, should be {}", - contract_addr, self.xmars_address - ); - } - - match query { - xmars_token::msg::QueryMsg::BalanceAt { address, block } => { - match self - .balances_at - .get(&(Addr::unchecked(address.clone()), block)) - { - Some(balance) => { - Ok(to_binary(&BalanceResponse { balance: *balance }).into()).into() - } - None => Err(SystemError::InvalidRequest { - error: format!( - "[mock]: no balance at block {} for account address {}", - block, &address - ), - request: Default::default(), - }) - .into(), - } - } - - xmars_token::msg::QueryMsg::TotalSupplyAt { block } => { - match self.total_supplies_at.get(&block) { - Some(balance) => Ok(to_binary(&xmars_token::TotalSupplyResponse { - total_supply: *balance, - }) - .into()) - .into(), - None => Err(SystemError::InvalidRequest { - error: format!("[mock]: no total supply at block {}", block), - request: Default::default(), - }) - .into(), - } - } - - other_query => Err(SystemError::InvalidRequest { - error: format!("[mock]: query not supported {:?}", other_query), - request: Default::default(), - }) - .into(), - } - } -} - -impl Default for XMarsQuerier { - fn default() -> Self { - XMarsQuerier { - xmars_address: Addr::unchecked(""), - balances_at: HashMap::new(), - total_supplies_at: HashMap::new(), - } - } -} diff --git a/packages/mars-core/src/treasury.rs b/packages/mars-core/src/treasury.rs deleted file mode 100644 index b8007304d..000000000 --- a/packages/mars-core/src/treasury.rs +++ /dev/null @@ -1,38 +0,0 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use cosmwasm_std::Addr; - -/// Treasury global configuration -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct Config { - pub owner: Addr, -} - -pub mod msg { - use schemars::JsonSchema; - use serde::{Deserialize, Serialize}; - - use cosmwasm_std::CosmosMsg; - - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] - pub struct InstantiateMsg { - pub owner: String, - } - - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] - #[serde(rename_all = "snake_case")] - pub enum ExecuteMsg { - /// Execute Cosmos msg - ExecuteCosmosMsg(CosmosMsg), - - /// Update contract config (only callable by owner) - UpdateConfig { owner: Option }, - } - - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] - #[serde(rename_all = "snake_case")] - pub enum QueryMsg { - Config {}, - } -} diff --git a/packages/mars-core/src/vesting.rs b/packages/mars-core/src/vesting.rs deleted file mode 100644 index 849c5ff0b..000000000 --- a/packages/mars-core/src/vesting.rs +++ /dev/null @@ -1,94 +0,0 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use cosmwasm_std::{Addr, Api, StdResult, Uint128}; -use cw20::Cw20ReceiveMsg; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, Copy)] -pub struct Schedule { - /// Time when vesting/unlocking starts - pub start_time: u64, - /// Time before with no token is to be vested/unlocked - pub cliff: u64, - /// Duration of the vesting/unlocking process. At time `start_time + duration`, the tokens are - /// vested/unlocked in full - pub duration: u64, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct Config { - /// Address provider address - /// T is to be `String` for the unchecked type, or `cosmwasm_std::Addr` for the checked type - pub address_provider_address: T, - /// Schedule for token unlocking; this schedule is the same for all users - pub unlock_schedule: Schedule, -} - -impl From> for Config { - fn from(config: Config) -> Self { - Config { - address_provider_address: config.address_provider_address.to_string(), - unlock_schedule: config.unlock_schedule, - } - } -} - -impl Config { - pub fn check(&self, api: &dyn Api) -> StdResult> { - Ok(Config { - address_provider_address: api.addr_validate(&self.address_provider_address)?, - unlock_schedule: self.unlock_schedule, - }) - } -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct Allocation { - /// Total amount of MARS allocated - pub allocated_amount: Uint128, - /// Amount of MARS already withdrawn - pub withdrawn_amount: Uint128, - /// The user's vesting schedule - pub vest_schedule: Schedule, -} - -pub mod msg { - use super::*; - - pub type InstantiateMsg = Config; - - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] - #[serde(rename_all = "snake_case")] - pub enum ExecuteMsg { - /// Implementation of cw20 receive msg - Receive(Cw20ReceiveMsg), - /// Withdraw unlocked MARS token - Withdraw {}, - } - - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] - #[serde(rename_all = "snake_case")] - pub enum ReceiveMsg { - /// Create a new allocation for a recipeint - CreateAllocation { - user_address: String, - vest_schedule: Schedule, - }, - } - - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] - #[serde(rename_all = "snake_case")] - pub enum QueryMsg { - /// Config of this contract. Returns `Config` - Config {}, - /// Status of an allocation. Returns `Allocation` - Allocation { user_address: String }, - /// A user's locked voting power at a certain height, which equals the user's total allocated - /// Mars token amount minus the amount they have already withdrawn up to that height. - /// Returns `Uint128` - VotingPowerAt { user_address: String, block: u64 }, - /// Total locked voting power owned by the vesting contract at a certain height. Used by - /// Martian Council to calculate a governance proposal's quorum. Returns `Uint128` - TotalVotingPowerAt { block: u64 }, - } -} diff --git a/packages/mars-core/src/xmars_token.rs b/packages/mars-core/src/xmars_token.rs deleted file mode 100644 index c940b6ddc..000000000 --- a/packages/mars-core/src/xmars_token.rs +++ /dev/null @@ -1,73 +0,0 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use cosmwasm_std::Uint128; - -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug, Default)] -pub struct TotalSupplyResponse { - pub total_supply: Uint128, -} - -pub mod msg { - use schemars::JsonSchema; - use serde::{Deserialize, Serialize}; - - pub use cw20_base::msg::{ExecuteMsg, InstantiateMsg}; - - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] - #[serde(rename_all = "snake_case")] - pub enum QueryMsg { - /// Returns the current balance of the given address, 0 if unset. - /// Return type: BalanceResponse. - Balance { - address: String, - }, - /// Returns the balance of the given address at a given block - /// Return type: BalanceResponse. - BalanceAt { - address: String, - block: u64, - }, - /// Returns metadata on the contract - name, decimals, supply, etc. - /// Return type: TokenInfoResponse. - TokenInfo {}, - /// Total Supply at a given block - /// Return type: TotalSupplyResponse - TotalSupplyAt { - block: u64, - }, - Minter {}, - /// Only with "allowance" extension. - /// Returns how much spender can use from owner account, 0 if unset. - /// Return type: AllowanceResponse. - Allowance { - owner: String, - spender: String, - }, - /// Only with "enumerable" extension (and "allowances") - /// Returns all allowances this owner has approved. Supports pagination. - /// Return type: AllAllowancesResponse. - AllAllowances { - owner: String, - start_after: Option, - limit: Option, - }, - /// Only with "enumerable" extension - /// Returns all accounts that have balances. Supports pagination. - /// Return type: AllAccountsResponse. - AllAccounts { - start_after: Option, - limit: Option, - }, - /// Only with "marketing" extension - /// Returns more metadata on the contract to display in the client: - /// - description, logo, project url, etc. - /// Return type: MarketingInfoResponse - MarketingInfo {}, - /// Only with "marketing" extension - /// Downloads the mbeded logo data (if stored on chain). Errors if no logo data ftored for this - /// contract. - /// Return type: DownloadLogoResponse. - DownloadLogo {}, - } -}