diff --git a/Cargo.lock b/Cargo.lock index 1327a77..cf8dc17 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -508,7 +508,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b81b8c4647f13be38baea7345885fcd97cffe8ad4e0454a43f975be9609127ed" dependencies = [ "cosmwasm-std", - "cw-storage-plus", + "cw-storage-plus 0.14.0", "cw-utils 0.14.0", "schemars", "serde", @@ -524,7 +524,7 @@ dependencies = [ "anyhow", "cosmwasm-std", "cosmwasm-storage", - "cw-storage-plus", + "cw-storage-plus 0.14.0", "cw-utils 0.14.0", "derivative", "itertools", @@ -534,6 +534,34 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cw-neuron-booster" +version = "0.1.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.14.0", + "cw-utils 0.14.0", + "cw1155", + "cw2 0.14.0", + "cw20-bonding", + "schemars", + "semver", + "serde", + "thiserror", +] + +[[package]] +name = "cw-storage-plus" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d7ee1963302b0ac2a9d42fe0faec826209c17452bfd36fbfd9d002a88929261" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + [[package]] name = "cw-storage-plus" version = "0.14.0" @@ -545,6 +573,18 @@ dependencies = [ "serde", ] +[[package]] +name = "cw-utils" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef842a1792e4285beff7b3b518705f760fa4111dc1e296e53f3e92d1ef7f6220" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "cw-utils" version = "0.13.4" @@ -564,7 +604,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "414b91f3d7a619bb26c835119d7095804596a1382ddc1d184c33c1d2c17f6c5e" dependencies = [ "cosmwasm-std", - "cw2", + "cw2 0.14.0", "schemars", "semver", "serde", @@ -588,11 +628,11 @@ version = "0.14.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus", + "cw-storage-plus 0.14.0", "cw-utils 0.14.0", "cw1", "cw1-whitelist", - "cw2", + "cw2 0.14.0", "cyber-std 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "schemars", "semver", @@ -609,10 +649,10 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", - "cw-storage-plus", + "cw-storage-plus 0.14.0", "cw-utils 0.14.0", "cw1", - "cw2", + "cw2 0.14.0", "cyber-std 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "derivative", "schemars", @@ -620,6 +660,30 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cw1155" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4bcc36af7f1d8874ecdbe0b156b20daec0eddc49ec14f17d82612d9b578eaf" +dependencies = [ + "cosmwasm-std", + "cw-utils 0.14.0", + "schemars", + "serde", +] + +[[package]] +name = "cw2" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1d81d7c359d6c1fba3aa83dad7ec6f999e512571380ae62f81257c3db569743" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus 0.11.1", + "schemars", + "serde", +] + [[package]] name = "cw2" version = "0.14.0" @@ -627,7 +691,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa74c324af8e3506fd8d50759a265bead3f87402e413c840042af5d2808463d6" dependencies = [ "cosmwasm-std", - "cw-storage-plus", + "cw-storage-plus 0.14.0", + "schemars", + "serde", +] + +[[package]] +name = "cw20" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9671d7edef5608acaf5b2f1e473ee3f501eced2cd4f7392e2106c8cf02ba0720" +dependencies = [ + "cosmwasm-std", + "cw-utils 0.11.1", "schemars", "serde", ] @@ -644,6 +720,22 @@ dependencies = [ "serde", ] +[[package]] +name = "cw20-base" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f6fc8c4cd451b418fa4f1ac2ea70595811fa9d8b4033617fe47953d7a93ceb" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus 0.11.1", + "cw-utils 0.11.1", + "cw2 0.11.1", + "cw20 0.11.1", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "cw20-base" version = "0.14.0" @@ -651,16 +743,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e39bf97c985a50f2e340833137b3f14999f58708c4ca9928cd8f87d530c4109c" dependencies = [ "cosmwasm-std", - "cw-storage-plus", + "cw-storage-plus 0.14.0", "cw-utils 0.14.0", - "cw2", - "cw20", + "cw2 0.14.0", + "cw20 0.14.0", "schemars", "semver", "serde", "thiserror", ] +[[package]] +name = "cw20-bonding" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6826bf0fc5402342cb2ca5f378fb39e5c3215a234acd3d9f77738372574ee89b" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus 0.11.1", + "cw-utils 0.11.1", + "cw2 0.11.1", + "cw20 0.11.1", + "cw20-base 0.11.1", + "integer-cbrt", + "integer-sqrt", + "rust_decimal", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "cw3" version = "0.14.0" @@ -680,11 +792,11 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", - "cw-storage-plus", + "cw-storage-plus 0.14.0", "cw-utils 0.14.0", - "cw2", - "cw20", - "cw20-base", + "cw2 0.14.0", + "cw20 0.14.0", + "cw20-base 0.14.0", "cw3", "cyber-std 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "schemars", @@ -699,9 +811,9 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", - "cw-storage-plus", + "cw-storage-plus 0.14.0", "cw-utils 0.14.0", - "cw2", + "cw2 0.14.0", "cw3", "cw3-fixed-multisig", "cw4", @@ -719,7 +831,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95dcc5320bdee5475aadfe7ebce70be5d98040bf91b44b7c22d3acd0c133c329" dependencies = [ "cosmwasm-std", - "cw-storage-plus", + "cw-storage-plus 0.14.0", "schemars", "serde", ] @@ -732,9 +844,9 @@ checksum = "93204a7aa587466636d001b21831c4b4ac87fccd40c7b39f78ad95dee452309e" dependencies = [ "cosmwasm-std", "cw-controllers", - "cw-storage-plus", + "cw-storage-plus 0.14.0", "cw-utils 0.14.0", - "cw2", + "cw2 0.14.0", "cw4", "schemars", "serde", @@ -787,7 +899,7 @@ dependencies = [ "anyhow", "cosmwasm-std", "cw-multi-test", - "cw-storage-plus", + "cw-storage-plus 0.14.0", "cyber-std 0.2.2", "schemars", "serde", @@ -1030,10 +1142,10 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", - "cw-storage-plus", + "cw-storage-plus 0.14.0", "cw-utils 0.14.0", - "cw20", - "cw20-base", + "cw20 0.14.0", + "cw20-base 0.14.0", "schemars", "serde", "thiserror", @@ -1046,10 +1158,10 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", - "cw-storage-plus", + "cw-storage-plus 0.14.0", "cw-utils 0.14.0", - "cw20", - "cw20-base", + "cw20 0.14.0", + "cw20-base 0.14.0", "schemars", "serde", "thiserror", @@ -1062,10 +1174,10 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", - "cw-storage-plus", + "cw-storage-plus 0.14.0", "cw-utils 0.14.0", - "cw20", - "cw20-base", + "cw20 0.14.0", + "cw20-base 0.14.0", "schemars", "serde", "thiserror", @@ -1078,10 +1190,10 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", - "cw-storage-plus", + "cw-storage-plus 0.14.0", "cw-utils 0.14.0", - "cw20", - "cw20-base", + "cw20 0.14.0", + "cw20-base 0.14.0", "schemars", "serde", "thiserror", @@ -1275,6 +1387,24 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "integer-cbrt" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "151bce4481ba7da831c7d12c32353cc79c73bf79732e343b92786e4cbbb2948c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "integer-sqrt" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770" +dependencies = [ + "num-traits", +] + [[package]] name = "itertools" version = "0.10.3" @@ -1466,6 +1596,15 @@ dependencies = [ "synstructure", ] +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.13.1" @@ -1771,6 +1910,17 @@ dependencies = [ "syn", ] +[[package]] +name = "rust_decimal" +version = "1.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee9164faf726e4f3ece4978b25ca877ddc6802fa77f38cdccb32c7f805ecd70c" +dependencies = [ + "arrayvec", + "num-traits", + "serde", +] + [[package]] name = "rustc-demangle" version = "0.1.21" diff --git a/contracts/cw-neuron-booster/.cargo/config b/contracts/cw-neuron-booster/.cargo/config new file mode 100644 index 0000000..7d1a066 --- /dev/null +++ b/contracts/cw-neuron-booster/.cargo/config @@ -0,0 +1,5 @@ +[alias] +wasm = "build --release --target wasm32-unknown-unknown" +wasm-debug = "build --target wasm32-unknown-unknown" +unit-test = "test --lib" +schema = "run --example schema" diff --git a/contracts/cw-neuron-booster/Cargo.toml b/contracts/cw-neuron-booster/Cargo.toml new file mode 100644 index 0000000..2ee644e --- /dev/null +++ b/contracts/cw-neuron-booster/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "cw-neuron-booster" +version = "0.1.0" +edition = "2018" +authors = ["CyberHead"] +description = "Neuron Booster" + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all init/handle/query exports +library = [] + +[dependencies] +cosmwasm-schema = { version = "1.0.0" } +cw-utils = { version = "0.14.0" } +cw2 = { version = "0.14.0" } +cw1155 = { version = "0.14.0" } +cw-storage-plus = { version = "0.14.0" } +cw20-bonding = { version = "0.11.1", features = ["library"] } +cosmwasm-std = { version = "1.0.0", features = ["iterator", "abort"] } +schemars = "0.8.10" +serde = { version = "1.0.139", default-features = false, features = ["derive"] } +thiserror = { version = "1.0.31" } +semver = "1" diff --git a/contracts/cw-neuron-booster/examples/schema.rs b/contracts/cw-neuron-booster/examples/schema.rs new file mode 100644 index 0000000..0bec320 --- /dev/null +++ b/contracts/cw-neuron-booster/examples/schema.rs @@ -0,0 +1,38 @@ +use std::env::current_dir; +use std::fs::create_dir_all; + +use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; + +use cw1155::{ + ApprovedForAllResponse, BalanceResponse, BatchBalanceResponse, Cw1155BatchReceiveMsg, + Cw1155ReceiveMsg, IsApprovedForAllResponse, + TokenInfoResponse, TokensResponse, +}; +use cw_neuron_booster::msg::{InstantiateMsg, ExecuteMsg, QueryMsg, TokenStateResponse, NeuronVestingsResponse, FundsFromNeuronResponse, FundsForNeuronResponse, SwapResponse, FundsResponse, SpotPriceResponse, FundPriceResponse}; + +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!(Cw1155ReceiveMsg), &out_dir); + export_schema(&schema_for!(Cw1155BatchReceiveMsg), &out_dir); + export_schema(&schema_for!(BalanceResponse), &out_dir); + export_schema(&schema_for!(BatchBalanceResponse), &out_dir); + export_schema(&schema_for!(ApprovedForAllResponse), &out_dir); + export_schema(&schema_for!(IsApprovedForAllResponse), &out_dir); + export_schema(&schema_for!(TokenInfoResponse), &out_dir); + export_schema(&schema_for!(TokensResponse), &out_dir); + export_schema(&schema_for!(TokenStateResponse), &out_dir); + export_schema(&schema_for!(NeuronVestingsResponse), &out_dir); + export_schema(&schema_for!(FundsResponse), &out_dir); + export_schema(&schema_for!(FundsFromNeuronResponse), &out_dir); + export_schema(&schema_for!(FundsForNeuronResponse), &out_dir); + export_schema(&schema_for!(SwapResponse), &out_dir); + export_schema(&schema_for!(SpotPriceResponse), &out_dir); + export_schema(&schema_for!(FundPriceResponse), &out_dir); +} diff --git a/contracts/cw-neuron-booster/schema/approved_for_all_response.json b/contracts/cw-neuron-booster/schema/approved_for_all_response.json new file mode 100644 index 0000000..453f17b --- /dev/null +++ b/contracts/cw-neuron-booster/schema/approved_for_all_response.json @@ -0,0 +1,97 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ApprovedForAllResponse", + "type": "object", + "required": [ + "operators" + ], + "properties": { + "operators": { + "type": "array", + "items": { + "$ref": "#/definitions/Approval" + } + } + }, + "definitions": { + "Approval": { + "type": "object", + "required": [ + "expires", + "spender" + ], + "properties": { + "expires": { + "description": "When the Approval expires (maybe Expiration::never)", + "allOf": [ + { + "$ref": "#/definitions/Expiration" + } + ] + }, + "spender": { + "description": "Account that can transfer/send the token", + "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)", + "oneOf": [ + { + "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" + } + ] + }, + "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/cw-neuron-booster/schema/balance_response.json b/contracts/cw-neuron-booster/schema/balance_response.json new file mode 100644 index 0000000..4e1a0be --- /dev/null +++ b/contracts/cw-neuron-booster/schema/balance_response.json @@ -0,0 +1,19 @@ +{ + "$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/cw-neuron-booster/schema/batch_balance_response.json b/contracts/cw-neuron-booster/schema/batch_balance_response.json new file mode 100644 index 0000000..39c8bd0 --- /dev/null +++ b/contracts/cw-neuron-booster/schema/batch_balance_response.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "BatchBalanceResponse", + "type": "object", + "required": [ + "balances" + ], + "properties": { + "balances": { + "type": "array", + "items": { + "$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/cw-neuron-booster/schema/cw1155_batch_receive_msg.json b/contracts/cw-neuron-booster/schema/cw1155_batch_receive_msg.json new file mode 100644 index 0000000..3982028 --- /dev/null +++ b/contracts/cw-neuron-booster/schema/cw1155_batch_receive_msg.json @@ -0,0 +1,51 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Cw1155BatchReceiveMsg", + "description": "Cw1155BatchReceiveMsg should be de/serialized under `BatchReceive()` variant in a ExecuteMsg", + "type": "object", + "required": [ + "batch", + "msg", + "operator" + ], + "properties": { + "batch": { + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/Uint128" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "from": { + "type": [ + "string", + "null" + ] + }, + "msg": { + "$ref": "#/definitions/Binary" + }, + "operator": { + "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" + }, + "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/cw-neuron-booster/schema/cw1155_receive_msg.json b/contracts/cw-neuron-booster/schema/cw1155_receive_msg.json new file mode 100644 index 0000000..1bf693c --- /dev/null +++ b/contracts/cw-neuron-booster/schema/cw1155_receive_msg.json @@ -0,0 +1,44 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Cw1155ReceiveMsg", + "description": "Cw1155ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg", + "type": "object", + "required": [ + "amount", + "msg", + "operator", + "token_id" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "from": { + "description": "The account that the token transfered from", + "type": [ + "string", + "null" + ] + }, + "msg": { + "$ref": "#/definitions/Binary" + }, + "operator": { + "description": "The account that executed the send message", + "type": "string" + }, + "token_id": { + "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" + }, + "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/cw-neuron-booster/schema/execute_msg.json b/contracts/cw-neuron-booster/schema/execute_msg.json new file mode 100644 index 0000000..42253c3 --- /dev/null +++ b/contracts/cw-neuron-booster/schema/execute_msg.json @@ -0,0 +1,453 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "mint" + ], + "properties": { + "mint": { + "type": "object", + "required": [ + "locked", + "reward" + ], + "properties": { + "locked": { + "type": "boolean" + }, + "msg": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + }, + "reward": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "fund" + ], + "properties": { + "fund": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "token_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "claim" + ], + "properties": { + "claim": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "token_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "buy" + ], + "properties": { + "buy": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "msg": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + }, + "token_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "sell" + ], + "properties": { + "sell": { + "type": "object", + "required": [ + "from", + "token_id", + "value" + ], + "properties": { + "from": { + "type": "string" + }, + "token_id": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Uint128" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "lock_token" + ], + "properties": { + "lock_token": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "token_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_reward" + ], + "properties": { + "update_reward": { + "type": "object", + "required": [ + "reward", + "token_id" + ], + "properties": { + "reward": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "token_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "swap_out_in" + ], + "properties": { + "swap_out_in": { + "type": "object", + "required": [ + "from", + "to", + "value" + ], + "properties": { + "from": { + "type": "string" + }, + "to": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Uint128" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "swap_in_out" + ], + "properties": { + "swap_in_out": { + "type": "object", + "required": [ + "from", + "to", + "value" + ], + "properties": { + "from": { + "type": "string" + }, + "to": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Uint128" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "send_from" + ], + "properties": { + "send_from": { + "type": "object", + "required": [ + "from", + "to", + "token_id", + "value" + ], + "properties": { + "from": { + "type": "string" + }, + "msg": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + }, + "to": { + "type": "string" + }, + "token_id": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Uint128" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "batch_send_from" + ], + "properties": { + "batch_send_from": { + "type": "object", + "required": [ + "batch", + "from", + "to" + ], + "properties": { + "batch": { + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/Uint128" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "from": { + "type": "string" + }, + "msg": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + }, + "to": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "approve_all" + ], + "properties": { + "approve_all": { + "type": "object", + "required": [ + "operator" + ], + "properties": { + "expires": { + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "operator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "revoke_all" + ], + "properties": { + "revoke_all": { + "type": "object", + "required": [ + "operator" + ], + "properties": { + "operator": { + "type": "string" + } + } + } + }, + "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" + }, + "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)", + "oneOf": [ + { + "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/cw-neuron-booster/schema/fund_price_response.json b/contracts/cw-neuron-booster/schema/fund_price_response.json new file mode 100644 index 0000000..94e2697 --- /dev/null +++ b/contracts/cw-neuron-booster/schema/fund_price_response.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FundPriceResponse", + "type": "object", + "required": [ + "fund_price" + ], + "properties": { + "fund_price": { + "$ref": "#/definitions/Decimal" + } + }, + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + } + } +} diff --git a/contracts/cw-neuron-booster/schema/funds_for_neuron_response.json b/contracts/cw-neuron-booster/schema/funds_for_neuron_response.json new file mode 100644 index 0000000..4d1e142 --- /dev/null +++ b/contracts/cw-neuron-booster/schema/funds_for_neuron_response.json @@ -0,0 +1,37 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FundsForNeuronResponse", + "type": "object", + "required": [ + "funds" + ], + "properties": { + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/FundForNeuron" + } + } + }, + "definitions": { + "FundForNeuron": { + "type": "object", + "required": [ + "address", + "amount" + ], + "properties": { + "address": { + "type": "string" + }, + "amount": { + "$ref": "#/definitions/Uint128" + } + } + }, + "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/cw-neuron-booster/schema/funds_from_neuron_response.json b/contracts/cw-neuron-booster/schema/funds_from_neuron_response.json new file mode 100644 index 0000000..f10b422 --- /dev/null +++ b/contracts/cw-neuron-booster/schema/funds_from_neuron_response.json @@ -0,0 +1,37 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FundsFromNeuronResponse", + "type": "object", + "required": [ + "funds" + ], + "properties": { + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/FundFromNeuron" + } + } + }, + "definitions": { + "FundFromNeuron": { + "type": "object", + "required": [ + "amount", + "token_id" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "token_id": { + "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/cw-neuron-booster/schema/funds_response.json b/contracts/cw-neuron-booster/schema/funds_response.json new file mode 100644 index 0000000..aa86f1d --- /dev/null +++ b/contracts/cw-neuron-booster/schema/funds_response.json @@ -0,0 +1,35 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FundsResponse", + "type": "object", + "required": [ + "funds" + ], + "properties": { + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Fund" + } + } + }, + "definitions": { + "Fund": { + "type": "object", + "required": [ + "height", + "token_id" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "token_id": { + "type": "string" + } + } + } + } +} diff --git a/contracts/cw-neuron-booster/schema/instantiate_msg.json b/contracts/cw-neuron-booster/schema/instantiate_msg.json new file mode 100644 index 0000000..44588cf --- /dev/null +++ b/contracts/cw-neuron-booster/schema/instantiate_msg.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object" +} diff --git a/contracts/cw-neuron-booster/schema/is_approved_for_all_response.json b/contracts/cw-neuron-booster/schema/is_approved_for_all_response.json new file mode 100644 index 0000000..e3af7a9 --- /dev/null +++ b/contracts/cw-neuron-booster/schema/is_approved_for_all_response.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "IsApprovedForAllResponse", + "type": "object", + "required": [ + "approved" + ], + "properties": { + "approved": { + "type": "boolean" + } + } +} diff --git a/contracts/cw-neuron-booster/schema/neuron_vestings_response.json b/contracts/cw-neuron-booster/schema/neuron_vestings_response.json new file mode 100644 index 0000000..903ca48 --- /dev/null +++ b/contracts/cw-neuron-booster/schema/neuron_vestings_response.json @@ -0,0 +1,37 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "NeuronVestingsResponse", + "type": "object", + "required": [ + "vestings" + ], + "properties": { + "vestings": { + "type": "array", + "items": { + "$ref": "#/definitions/Vesting" + } + } + }, + "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" + }, + "Vesting": { + "type": "object", + "required": [ + "amount", + "token_id" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "token_id": { + "type": "string" + } + } + } + } +} diff --git a/contracts/cw-neuron-booster/schema/query_msg.json b/contracts/cw-neuron-booster/schema/query_msg.json new file mode 100644 index 0000000..4851383 --- /dev/null +++ b/contracts/cw-neuron-booster/schema/query_msg.json @@ -0,0 +1,415 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "token_state" + ], + "properties": { + "token_state": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "token_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "spot_price" + ], + "properties": { + "spot_price": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "token_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "fund_price" + ], + "properties": { + "fund_price": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "token_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "funds_by_block" + ], + "properties": { + "funds_by_block": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "funds_from_neuron" + ], + "properties": { + "funds_from_neuron": { + "type": "object", + "required": [ + "neuron" + ], + "properties": { + "neuron": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "funds_for_neuron" + ], + "properties": { + "funds_for_neuron": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "token_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "vestings" + ], + "properties": { + "vestings": { + "type": "object", + "required": [ + "neuron" + ], + "properties": { + "neuron": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "swap_out_in" + ], + "properties": { + "swap_out_in": { + "type": "object", + "required": [ + "from", + "to", + "value" + ], + "properties": { + "from": { + "type": "string" + }, + "to": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Uint128" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "swap_in_out" + ], + "properties": { + "swap_in_out": { + "type": "object", + "required": [ + "from", + "to", + "value" + ], + "properties": { + "from": { + "type": "string" + }, + "to": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Uint128" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "balance" + ], + "properties": { + "balance": { + "type": "object", + "required": [ + "owner", + "token_id" + ], + "properties": { + "owner": { + "type": "string" + }, + "token_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "batch_balance" + ], + "properties": { + "batch_balance": { + "type": "object", + "required": [ + "owner", + "token_ids" + ], + "properties": { + "owner": { + "type": "string" + }, + "token_ids": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "approved_for_all" + ], + "properties": { + "approved_for_all": { + "type": "object", + "required": [ + "owner" + ], + "properties": { + "include_expired": { + "type": [ + "boolean", + "null" + ] + }, + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "type": "string" + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "is_approved_for_all" + ], + "properties": { + "is_approved_for_all": { + "type": "object", + "required": [ + "operator", + "owner" + ], + "properties": { + "operator": { + "type": "string" + }, + "owner": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "token_info" + ], + "properties": { + "token_info": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "token_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "tokens" + ], + "properties": { + "tokens": { + "type": "object", + "required": [ + "owner" + ], + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "type": "string" + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "all_tokens" + ], + "properties": { + "all_tokens": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + } + ], + "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/cw-neuron-booster/schema/spot_price_response.json b/contracts/cw-neuron-booster/schema/spot_price_response.json new file mode 100644 index 0000000..f792465 --- /dev/null +++ b/contracts/cw-neuron-booster/schema/spot_price_response.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SpotPriceResponse", + "type": "object", + "required": [ + "spot_price" + ], + "properties": { + "spot_price": { + "$ref": "#/definitions/Decimal" + } + }, + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + } + } +} diff --git a/contracts/cw-neuron-booster/schema/swap_response.json b/contracts/cw-neuron-booster/schema/swap_response.json new file mode 100644 index 0000000..9fdac7b --- /dev/null +++ b/contracts/cw-neuron-booster/schema/swap_response.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SwapResponse", + "type": "object", + "required": [ + "buy", + "from", + "sell", + "to" + ], + "properties": { + "buy": { + "$ref": "#/definitions/Uint128" + }, + "from": { + "type": "string" + }, + "sell": { + "$ref": "#/definitions/Uint128" + }, + "to": { + "type": "string" + } + }, + "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/cw-neuron-booster/schema/token_info_response.json b/contracts/cw-neuron-booster/schema/token_info_response.json new file mode 100644 index 0000000..a94af98 --- /dev/null +++ b/contracts/cw-neuron-booster/schema/token_info_response.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TokenInfoResponse", + "type": "object", + "required": [ + "url" + ], + "properties": { + "url": { + "description": "Should be a url point to a json file", + "type": "string" + } + } +} diff --git a/contracts/cw-neuron-booster/schema/token_state_response.json b/contracts/cw-neuron-booster/schema/token_state_response.json new file mode 100644 index 0000000..0f44365 --- /dev/null +++ b/contracts/cw-neuron-booster/schema/token_state_response.json @@ -0,0 +1,55 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TokenStateResponse", + "type": "object", + "required": [ + "created", + "funded", + "funds", + "init_price", + "locked", + "reserve", + "reward", + "supply" + ], + "properties": { + "created": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "funded": { + "type": "boolean" + }, + "funds": { + "$ref": "#/definitions/Uint128" + }, + "init_price": { + "$ref": "#/definitions/Decimal" + }, + "locked": { + "type": "boolean" + }, + "reserve": { + "$ref": "#/definitions/Uint128" + }, + "reward": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "supply": { + "$ref": "#/definitions/Uint128" + } + }, + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "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/cw-neuron-booster/schema/tokens_response.json b/contracts/cw-neuron-booster/schema/tokens_response.json new file mode 100644 index 0000000..b8e3d75 --- /dev/null +++ b/contracts/cw-neuron-booster/schema/tokens_response.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TokensResponse", + "type": "object", + "required": [ + "tokens" + ], + "properties": { + "tokens": { + "description": "Contains all token_ids in lexicographical ordering If there are more than `limit`, use `start_from` in future queries to achieve pagination.", + "type": "array", + "items": { + "type": "string" + } + } + } +} diff --git a/contracts/cw-neuron-booster/src/contract.rs b/contracts/cw-neuron-booster/src/contract.rs new file mode 100644 index 0000000..e295a9a --- /dev/null +++ b/contracts/cw-neuron-booster/src/contract.rs @@ -0,0 +1,216 @@ +use cosmwasm_std::{Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, to_binary}; +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cw1155::{IsApprovedForAllResponse, TokenInfoResponse}; +use cw2::{get_contract_version, set_contract_version}; +use cw_utils::maybe_addr; +use semver::Version; + +use crate::error::ContractError; +use crate::execute::{ + check_can_approve, execute_approve_all, execute_batch_send_from, + execute_buy, execute_claim, execute_fund, execute_lock, execute_mint, + execute_revoke_all, execute_sell, execute_send_from, execute_swap_in_out, + execute_swap_out_in, execute_update_reward +}; +use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use crate::query::{query_all_approvals, query_all_funds, query_all_funds_for_neuron, + query_all_funds_from_neuron, query_all_neuron_vestings, query_all_tokens, + query_balance, query_batch_balance, query_fund_price, query_spot_price, + query_swap_in_out, query_swap_out_in, query_token_state, query_tokens +}; +use crate::state::{MINTER, TOKENS}; + +const CONTRACT_NAME: &str = "neuron-booster"; +const CONTRACT_VERSION: &str = "1.0.0"; + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + env: Env, + _info: MessageInfo, + _msg: InstantiateMsg, +) -> StdResult { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + MINTER.save(deps.storage, &env.contract.address)?; + Ok(Response::default()) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + ExecuteMsg::Mint { + reward, + locked, + msg + } => execute_mint(deps, env, info, reward, locked,msg), + ExecuteMsg::Fund { token_id } => execute_fund(deps, env, info, token_id), + ExecuteMsg::Claim { token_id } => execute_claim(deps, env, info, token_id), + ExecuteMsg::Buy { + token_id, + msg + } => execute_buy(deps, env, info, token_id, msg), + ExecuteMsg::Sell { + from, + token_id, + value, + } => execute_sell(deps, env, info, from, token_id, value), + ExecuteMsg::LockToken { token_id} => execute_lock(deps, env, info, token_id), + ExecuteMsg::UpdateReward { + token_id, + reward + } => execute_update_reward(deps, env, info, token_id, reward), + ExecuteMsg::SwapOutIn { + from, + to, + value + } => execute_swap_out_in(deps, env, info, from, to, value), + ExecuteMsg::SwapInOut { + to, + from, + value + } => execute_swap_in_out(deps, env, info, to, from, value), + ExecuteMsg::SendFrom { + from, + to, + token_id, + value, + msg, + } => execute_send_from(deps, env, info, from, to, token_id, value, msg), + ExecuteMsg::BatchSendFrom { + from, + to, + batch, + msg, + } => execute_batch_send_from(deps, env, info, from, to, batch, msg), + ExecuteMsg::ApproveAll { + operator, + expires + } => execute_approve_all(deps, env, info, operator, expires), + ExecuteMsg::RevokeAll { operator } => execute_revoke_all(deps, env, info, operator), + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::TokenState { token_id } => { + to_binary(&query_token_state(deps, env, token_id)?) + } + QueryMsg::SpotPrice { token_id } => { + let token = deps.api.addr_validate(&token_id)?; + to_binary(&query_spot_price(deps, env, token.to_string())?) + } + QueryMsg::FundPrice { token_id } => { + let token = deps.api.addr_validate(&token_id)?; + to_binary(&query_fund_price(deps, env, token.to_string())?) + } + QueryMsg::FundsByBlock { start_after, limit } => { + to_binary(&query_all_funds(deps, env, start_after, limit)?) + }, + QueryMsg::FundsFromNeuron { neuron } => { + let addr = deps.api.addr_validate(&neuron)?; + to_binary(&query_all_funds_from_neuron(deps, env, addr)?) + }, + QueryMsg::FundsForNeuron { token_id } => { + let token = deps.api.addr_validate(&token_id)?; + to_binary(&query_all_funds_for_neuron(deps, env, token.to_string())?) + }, + QueryMsg::Vestings { neuron } => { + let addr = deps.api.addr_validate(&neuron)?; + to_binary(&query_all_neuron_vestings(deps, env, addr)?) + }, + QueryMsg::SwapOutIn { from, to, value } => { + let token_out = deps.api.addr_validate(&from)?; + let token_in = deps.api.addr_validate(&to)?; + to_binary(&query_swap_out_in(deps, env, token_out.to_string(), token_in.to_string(), value)?) + } + QueryMsg::SwapInOut { to, from, value } => { + let token_in = deps.api.addr_validate(&to)?; + let token_out = deps.api.addr_validate(&from)?; + to_binary(&query_swap_in_out(deps, env, token_out.to_string(), token_in.to_string(), value)?) + } + QueryMsg::Balance { owner, token_id } => { + let owner_addr = deps.api.addr_validate(&owner)?; + let token = deps.api.addr_validate(&token_id)?; + to_binary(&query_balance(deps, env, owner_addr, token.to_string())?) + } + QueryMsg::BatchBalance { owner, token_ids } => { + let owner_addr = deps.api.addr_validate(&owner)?; + to_binary(&query_batch_balance(deps, env, owner_addr, token_ids)?) + } + QueryMsg::IsApprovedForAll { owner, operator } => { + let owner_addr = deps.api.addr_validate(&owner)?; + let operator_addr = deps.api.addr_validate(&operator)?; + let approved = check_can_approve(deps, &env, &owner_addr, &operator_addr)?; + to_binary(&IsApprovedForAllResponse { approved }) + } + QueryMsg::ApprovedForAll { + owner, + include_expired, + start_after, + limit, + } => { + let owner_addr = deps.api.addr_validate(&owner)?; + let start_addr = maybe_addr(deps.api, start_after)?; + to_binary(&query_all_approvals( + deps, + env, + owner_addr, + include_expired.unwrap_or(false), + start_addr, + limit, + )?) + } + QueryMsg::TokenInfo { token_id } => { + let url = TOKENS.load(deps.storage, &token_id)?; + to_binary(&TokenInfoResponse { url }) + } + QueryMsg::Tokens { + owner, + start_after, + limit, + } => { + let owner_addr = deps.api.addr_validate(&owner)?; + to_binary(&query_tokens(deps, owner_addr, start_after, limit)?) + } + QueryMsg::AllTokens { start_after, limit } => { + to_binary(&query_all_tokens(deps, start_after, limit)?) + } + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate( + deps: DepsMut, + _env: Env, + _msg: Empty, +) -> Result { + let stored = get_contract_version(deps.storage)?; + if stored.contract != CONTRACT_NAME { + return Err(ContractError::CannotMigrate { + previous_contract: stored.contract, + }); + } + + let version: Version = CONTRACT_VERSION.parse()?; + let storage_version: Version = get_contract_version(deps.storage)?.version.parse()?; + + if storage_version > version { + return Err(ContractError::CannotMigrateVersion { + previous_version: stored.version, + }); + } + + if storage_version < version { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + } + + Ok(Response::new()) +} \ No newline at end of file diff --git a/contracts/cw-neuron-booster/src/error.rs b/contracts/cw-neuron-booster/src/error.rs new file mode 100644 index 0000000..144347c --- /dev/null +++ b/contracts/cw-neuron-booster/src/error.rs @@ -0,0 +1,48 @@ +use cosmwasm_std::StdError; +use cw_utils::PaymentError; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("Unauthorized")] + Unauthorized {}, + + #[error("{0}")] + Payment(#[from] PaymentError), + + #[error("Expired")] + Expired {}, + + #[error("Funding ended")] + FundingEnded {}, + + #[error("Funding period")] + FundingPeriod {}, + + #[error("Token locked")] + TokenLocked {}, + + #[error("Funds claimed")] + FundsClaimed {}, + + #[error("Vesting Period")] + VestingPeriod {}, + + #[error("Cannot migrate from different contract type: {previous_contract}")] + CannotMigrate { previous_contract: String }, + + #[error("Cannot migrate from unsupported version: {previous_version}")] + CannotMigrateVersion { previous_version: String }, + + #[error("Semver parsing error: {0}")] + SemVer(String), +} + +impl From for ContractError { + fn from(err: semver::Error) -> Self { + Self::SemVer(err.to_string()) + } +} diff --git a/contracts/cw-neuron-booster/src/execute.rs b/contracts/cw-neuron-booster/src/execute.rs new file mode 100644 index 0000000..f5411ef --- /dev/null +++ b/contracts/cw-neuron-booster/src/execute.rs @@ -0,0 +1,830 @@ +use std::ops::{Add, Div, Mul, Sub}; + +use cosmwasm_std::{BankMsg, coins, Decimal, StdError, Storage}; +use cosmwasm_std::{ + Addr, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, SubMsg, + Uint128, +}; +use cw1155::{ + ApproveAllEvent, + Cw1155BatchReceiveMsg, Cw1155ReceiveMsg, Expiration, + TokenId, TransferEvent, +}; +use cw20_bonding::curves::{Curve, decimal, DecimalPlaces, SquareRoot}; +use cw_utils::{Event, must_pay}; + +use crate::error::ContractError; +use crate::state::{ + APPROVES, BALANCES, FUNDS_BY_BLOCKS, FUNDS_FOR_NEURONS, + FUNDS_FROM_NEURONS, MINTER, TOKENS, TOKENS_STATES, TokenState, VESTINGS +}; + +const RESERVE_DENOM: &str = "milliampere"; +const FUND_PERIOD: u64 = 250; +const VESTING_PERIOD: u64 = 500; + +// TODO apply fee to buy and sell to self? +// TODO if reward rounded to 0? + +pub fn execute_mint( + deps: DepsMut, + env: Env, + info: MessageInfo, + reward: u64, + locked: bool, + msg: Option, +) -> Result { + let token_id = info.clone().sender.into_string(); + + if reward > 100 { return Err(ContractError::Unauthorized {}) }; + + let payment = must_pay(&info.clone(), RESERVE_DENOM)?; + + let token_state = TokenState{ + reserve:Uint128::new(0), + supply: Uint128::new(0), + funds: payment, + funded: false, + reward, + locked, + created: env.block.height, + init_price: Decimal::zero() + }; + + TOKENS_STATES.save(deps.storage, &token_id.clone(), &token_state)?; + FUNDS_BY_BLOCKS.save(deps.storage, env.block.height, &token_id)?; + FUNDS_FROM_NEURONS.save(deps.storage, (&info.sender, &token_id), &payment)?; + FUNDS_FOR_NEURONS.save(deps.storage,(&token_id, &info.sender), &payment)?; + + let sub_info = MessageInfo { + sender: env.clone().contract.address, + funds: vec![], + }; + + _execute_mint(deps, env, sub_info, info.clone().sender.to_string(), token_id.clone(), Uint128::new(0), msg)?; + + let res = Response::new() + .add_attribute("action", "mint") + .add_attribute("token_id", token_id) + .add_attribute("from", info.sender) + .add_attribute("reward", Decimal::percent(reward).to_string()) + .add_attribute("locked", locked.to_string()) + .add_attribute("funds", payment.to_string()); + Ok(res) +} + +pub fn execute_fund( + deps: DepsMut, + env: Env, + info: MessageInfo, + token_id: TokenId +) -> Result { + let mut token_state = TOKENS_STATES.load(deps.storage, &token_id)?; + + if token_state.created.add(FUND_PERIOD) < env.block.height { + return Err(ContractError::FundingEnded {}) + } + + let payment = must_pay(&info.clone(), RESERVE_DENOM)?; + + token_state.funds = token_state.funds.add(payment); + TOKENS_STATES.save(deps.storage, &token_id, &token_state)?; + + FUNDS_FROM_NEURONS.update( + deps.storage, + (&info.sender, &token_id), + |funds: Option| -> StdResult<_> { + match funds { + Some(amount) => Ok(amount.add(payment)), + None => Ok(payment), + } + }, + )?; + + FUNDS_FOR_NEURONS.update( + deps.storage, + (&token_id, &info.sender), + |funds: Option| -> StdResult<_> { + match funds { + Some(amount) => Ok(amount.add(payment)), + None => Ok(payment), + } + }, + )?; + + let sub_info = MessageInfo { + sender: env.clone().contract.address, + funds: vec![], + }; + + _execute_mint(deps, env, sub_info, info.sender.to_string(), token_id.clone(), Uint128::new(0), None)?; + + let res = Response::new() + .add_attribute("action", "fund") + .add_attribute("token_id", token_id) + .add_attribute("from", info.sender) + .add_attribute("funds", payment.to_string()); + Ok(res) +} + +pub fn execute_claim( + deps: DepsMut, + env: Env, + info: MessageInfo, + token_id: TokenId, +) -> Result { + let mut token_state = TOKENS_STATES.load(deps.storage, &token_id)?; + + if token_state.created.add(FUND_PERIOD) > env.block.height { + return Err(ContractError::FundingPeriod {}) + } + + if VESTINGS.has(deps.storage, (&info.sender, &token_id)) { + return Err(ContractError::FundsClaimed {}) + } + + if !token_state.funded { + TOKENS_STATES.update( + deps.storage, + &token_id, + |ts: Option| -> StdResult<_> { + let mut state = ts.unwrap(); + let curve = SquareRoot::new(decimal(20u128, 2), DecimalPlaces::new(3, 3)); + state.reserve = state.funds; + state.supply = curve.supply(state.reserve); + state.funded = true; + state.init_price = Decimal::from_ratio(state.reserve, state.supply); + token_state = state.clone(); + Ok(state) + }, + )?; + } + + let neuron_funds = FUNDS_FROM_NEURONS.load(deps.storage, (&info.sender, &token_id))?; + let amount = Decimal::new(neuron_funds) + .div(token_state.init_price) + .atomics(); + + let sub_info = MessageInfo { + sender: env.clone().contract.address, + funds: vec![], + }; + + VESTINGS.save(deps.storage, (&info.sender, &token_id), &amount)?; + + _execute_mint(deps, env, sub_info, info.sender.to_string(), token_id.clone(), amount, None)?; + + let res = Response::new() + .add_attribute("action", "claim") + .add_attribute("token_id", token_id.clone()) + .add_attribute("amount", amount.to_string()); + + Ok(res) +} + +pub fn execute_buy( + deps: DepsMut, + env: Env, + info: MessageInfo, + token_id: TokenId, + msg: Option, +) -> Result { + let mut token_state = TOKENS_STATES.load(deps.storage, &token_id)?; + + if token_state.created.add(FUND_PERIOD) > env.block.height { + return Err(ContractError::FundingPeriod {}) + } + + if !token_state.funded { return Err(ContractError::FundingPeriod {}) } + + let payment = must_pay(&info.clone(), RESERVE_DENOM)?; + + let reward = Decimal::percent(token_state.reward).mul(payment); + let curve = SquareRoot::new(decimal(20u128, 2), DecimalPlaces::new(3, 3)); + + let payment_to = payment + .checked_sub(reward) + .map_err(StdError::overflow)?; + token_state.reserve += payment_to; + let new_supply = curve.supply(token_state.reserve); + let minted = new_supply + .checked_sub(token_state.supply) + .map_err(StdError::overflow)?; + token_state.supply = new_supply; + TOKENS_STATES.save(deps.storage, &token_id.clone(), &token_state)?; + + let sub_info = MessageInfo { + sender: env.clone().contract.address, + funds: vec![], + }; + + _execute_mint(deps, env, sub_info, info.clone().sender.to_string(), token_id.clone(), minted, msg)?; + + let res = Response::new() + .add_message(BankMsg::Send { + to_address: token_id.clone(), + amount: coins(reward.u128(), RESERVE_DENOM)}) + .add_attribute("action", "buy") + .add_attribute("token_id", token_id) + .add_attribute("from", info.sender) + .add_attribute("reserve", token_state.reserve.to_string()) + .add_attribute("supply", token_state.supply.to_string()) + .add_attribute("payment", payment.to_string()) + .add_attribute("reward", reward.to_string()) + .add_attribute("minted", minted.to_string()); + Ok(res) +} + +pub fn execute_sell( + mut deps: DepsMut, + env: Env, + info: MessageInfo, + from: String, + token_id: TokenId, + amount: Uint128 +) -> Result { + _execute_burn(deps.branch(), env.clone(), info.clone(), from, token_id.clone(), amount)?; + + let mut token_state = TOKENS_STATES.load(deps.storage, &token_id)?; + + if token_state.created.add(FUND_PERIOD) > env.block.height { + return Err(ContractError::FundingPeriod {}) + } + + if !token_state.funded { + return Err(ContractError::FundingPeriod {}) + } + + let curve = SquareRoot::new(decimal(20u128, 2), DecimalPlaces::new(3,3)); + token_state.supply = token_state + .supply + .checked_sub(amount) + .map_err(StdError::overflow)?; + let new_reserve = curve.reserve(token_state.supply); + let mut released = token_state + .reserve + .checked_sub(new_reserve) + .map_err(StdError::overflow)?; + token_state.reserve = new_reserve; + TOKENS_STATES.save(deps.storage, &token_id, &token_state)?; + + let reward = Decimal::percent(token_state.reward).mul(released); + released = released.sub(reward); + + let res = Response::new() + .add_message(BankMsg::Send { + to_address: info.sender.to_string(), + amount: coins(released.u128(), RESERVE_DENOM), + }) + .add_message(BankMsg::Send { + to_address: token_id.clone(), + amount: coins(reward.u128(), RESERVE_DENOM), + }) + .add_attribute("action", "burn") + .add_attribute("token_id", token_id) + .add_attribute("from", info.sender) + .add_attribute("reward", reward) + .add_attribute("payment", released) + .add_attribute("reserve", token_state.reserve) + .add_attribute("supply", token_state.supply); + + Ok(res) +} + +pub fn execute_lock( + deps: DepsMut, + env: Env, + info: MessageInfo, + token_id: TokenId, +) -> Result { + let mut token_state = TOKENS_STATES.load(deps.storage, &token_id)?; + + if token_id.ne(&info.sender.to_string()) { + return Err(ContractError::Unauthorized {}) + } + + if token_state.created.add(FUND_PERIOD) > env.block.height { + return Err(ContractError::FundingPeriod {}) + } + + if !token_state.funded { + return Err(ContractError::FundingPeriod {}) + } + + token_state.locked = true; + TOKENS_STATES.save(deps.storage, &token_id, &token_state)?; + + let res = Response::new() + .add_attribute("action", "lock") + .add_attribute("token_id", token_id); + + Ok(res) +} + +pub fn execute_update_reward( + deps: DepsMut, + env: Env, + info: MessageInfo, + token_id: TokenId, + reward: u64, +) -> Result { + let mut token_state = TOKENS_STATES.load(deps.storage, &token_id)?; + + if token_id.ne(&info.sender.to_string()) { + return Err(ContractError::Unauthorized {}) + } + + if token_state.created.add(FUND_PERIOD) > env.block.height { + return Err(ContractError::FundingPeriod {}) + } + + if !token_state.funded { + return Err(ContractError::FundingPeriod {}) + } + + if token_state.locked { + return Err(ContractError::TokenLocked {}) + } + + token_state.reward = reward; + TOKENS_STATES.save(deps.storage, &token_id, &token_state)?; + + let res = Response::new() + .add_attribute("action", "update_reward") + .add_attribute("token_id", token_id) + .add_attribute("reward", reward.to_string()); + + Ok(res) +} + +pub fn execute_swap_out_in( + mut deps: DepsMut, + env: Env, + info: MessageInfo, + from: TokenId, + to: TokenId, + value: Uint128, +) -> Result { + _execute_burn(deps.branch(), env.clone(), info.clone(), info.sender.to_string(), from.clone(), value)?; + + let mut out_token = TOKENS_STATES.load(deps.storage, &from)?; + + if out_token.created.add(FUND_PERIOD) > env.block.height { + return Err(ContractError::FundingPeriod {}) + } + if !out_token.funded { return Err(ContractError::FundingPeriod {}) } + + let mut in_token = TOKENS_STATES.load(deps.storage, &to)?; + + if in_token.created.add(FUND_PERIOD) > env.block.height { + return Err(ContractError::FundingPeriod {}) + } + if !in_token.funded { return Err(ContractError::FundingPeriod {}) } + + let curve = SquareRoot::new( + decimal(20u128, 2), + DecimalPlaces::new(3,3) + ); + + // sell out with value + out_token.supply = out_token + .supply + .checked_sub(value) + .map_err(StdError::overflow)?; + let new_reserve = curve.reserve(out_token.supply); + let mut released_out = out_token + .reserve + .checked_sub(new_reserve) + .map_err(StdError::overflow)?; + out_token.reserve = new_reserve; + TOKENS_STATES.save(deps.storage, &from, &out_token)?; + + let reward_out = Decimal::percent(out_token.reward).mul(released_out); + released_out = released_out.sub(reward_out); + + let reward_in = Decimal::percent(in_token.reward).mul(released_out); + let buy_in = released_out.sub(reward_in); + + // buy in with amount + in_token.reserve += buy_in; + let new_supply = curve.supply(in_token.reserve); + let minted = new_supply + .checked_sub(in_token.supply) + .map_err(StdError::overflow)?; + in_token.supply = new_supply; + TOKENS_STATES.save(deps.storage, &to, &in_token)?; + + let sub_info = MessageInfo { + sender: env.clone().contract.address, + funds: vec![], + }; + + _execute_mint(deps, env, sub_info, info.clone().sender.to_string(), to.clone(), minted, None)?; + + let res = Response::new() + .add_message(BankMsg::Send { + to_address: from.clone(), + amount: coins(reward_out.u128(), RESERVE_DENOM)}) + .add_message(BankMsg::Send { + to_address: to.clone(), + amount: coins(reward_in.u128(), RESERVE_DENOM)}) + .add_attribute("action", "swap_out_in") + .add_attribute("addr", info.sender) + .add_attribute("from", from.to_string()) + .add_attribute("to", to.to_string()) + .add_attribute("sell", value.to_string()) + .add_attribute("bought", minted.to_string()) + .add_attribute("reward_out", reward_out.to_string()) + .add_attribute("reward_in", reward_in.to_string()); + + Ok(res) +} + +pub fn execute_swap_in_out( + mut deps: DepsMut, + env: Env, + info: MessageInfo, + to: TokenId, + from: TokenId, + value: Uint128, +) -> Result { + let mut out_token = TOKENS_STATES.load(deps.storage, &from)?; + + if out_token.created.add(FUND_PERIOD) > env.block.height { + return Err(ContractError::FundingPeriod {}) + } + if !out_token.funded { return Err(ContractError::FundingPeriod {}) } + + let mut in_token = TOKENS_STATES.load(deps.storage, &to)?; + + if in_token.created.add(FUND_PERIOD) > env.block.height { + return Err(ContractError::FundingPeriod {}) + } + if !in_token.funded { return Err(ContractError::FundingPeriod {}) } + + let curve = SquareRoot::new( + decimal(20u128, 2), + DecimalPlaces::new(3,3) + ); + + let mut reserve_in = curve.reserve(in_token.supply.add(value)); + // THIS LINE!!! + reserve_in = reserve_in.sub(in_token.reserve); + let reward_in = Decimal::percent(in_token.reward).mul(reserve_in); + reserve_in = reserve_in.add(reward_in); + + let reward_out = Decimal::percent(out_token.reward).mul(reserve_in); + let reserve_out = reserve_in.add(reward_out); + + out_token.reserve -= reserve_out; + let new_supply = curve.supply(out_token.reserve); + // THIS LINE!!! + let burned = out_token.supply + .checked_sub(new_supply) + .map_err(StdError::overflow)?; + out_token.supply = new_supply; + TOKENS_STATES.save(deps.storage, &from, &out_token)?; + + _execute_burn(deps.branch(), env.clone(), info.clone(), info.sender.to_string(), from.clone(), burned.clone())?; + + //---- + + in_token.reserve += reserve_in; + let new_supply = curve.supply(in_token.reserve); + let minted = new_supply + .checked_sub(in_token.supply) + .map_err(StdError::overflow)?; + in_token.supply = new_supply; + TOKENS_STATES.save(deps.storage, &to, &in_token)?; + + let sub_info = MessageInfo { + sender: env.clone().contract.address, + funds: vec![], + }; + + _execute_mint(deps, env, sub_info, info.sender.to_string(), to.clone(), minted.clone(), None)?; + + let res = Response::new() + .add_message(BankMsg::Send { + to_address: from.clone(), + amount: coins(reward_out.u128(), RESERVE_DENOM)}) + .add_message(BankMsg::Send { + to_address: to.clone(), + amount: coins(reward_out.u128(), RESERVE_DENOM)}) + .add_attribute("action", "swap_in_out") + .add_attribute("addr", info.sender) + .add_attribute("from", from.to_string()) + .add_attribute("to", to.to_string()) + .add_attribute("bought", value.to_string()) + .add_attribute("sell", burned.to_string()) + .add_attribute("reward_out", reward_out.to_string()) + .add_attribute("reward_in", reward_in.to_string()); + + Ok(res) + +} + +/// returns true iff the sender can execute approve or reject on the contract +pub fn check_can_approve( + deps: Deps, + env: &Env, + owner: &Addr, + operator: &Addr +) -> StdResult { + // owner can approve + if owner == operator { + return Ok(true); + } + // operator can approve + let op = APPROVES.may_load(deps.storage, (owner, operator))?; + Ok(match op { + Some(ex) => !ex.is_expired(&env.block), + None => false, + }) +} + +pub fn execute_send_from( + mut deps: DepsMut, + env: Env, + info: MessageInfo, + from: String, + to: String, + token_id: TokenId, + amount: Uint128, + msg: Option, +) -> Result { + let from_addr = deps.api.addr_validate(&from)?; + let to_addr = deps.api.addr_validate(&to)?; + + _guard_can_approve(deps.as_ref(), &env, &from_addr, &info.sender)?; + + let mut rsp = Response::default(); + + let event = _execute_transfer_inner( + &mut deps, + env.block.height, + Some(&from_addr), + Some(&to_addr), + &token_id, + amount, + )?; + event.add_attributes(&mut rsp); + + if let Some(msg) = msg { + rsp.messages = vec![SubMsg::new( + Cw1155ReceiveMsg { + operator: info.sender.to_string(), + from: Some(from), + amount, + token_id: token_id.clone(), + msg, + } + .into_cosmos_msg(to)?, + )] + } + + Ok(rsp) +} + +pub fn execute_batch_send_from( + mut deps: DepsMut, + env: Env, + info: MessageInfo, + from: String, + to: String, + batch: Vec<(TokenId, Uint128)>, + msg: Option, +) -> Result { + let from_addr = deps.api.addr_validate(&from)?; + let to_addr = deps.api.addr_validate(&to)?; + + _guard_can_approve(deps.as_ref(), &env, &from_addr, &info.sender)?; + + let mut rsp = Response::default(); + for (token_id, amount) in batch.iter() { + let event = _execute_transfer_inner( + &mut deps, + env.block.height, + Some(&from_addr), + Some(&to_addr), + token_id, + *amount, + )?; + event.add_attributes(&mut rsp); + } + + if let Some(msg) = msg { + rsp.messages = vec![SubMsg::new( + Cw1155BatchReceiveMsg { + operator: info.sender.to_string(), + from: Some(from), + batch, + msg, + } + .into_cosmos_msg(to)?, + )] + }; + + Ok(rsp) +} + +pub fn execute_approve_all( + deps: DepsMut, + env: Env, + info: MessageInfo, + operator: String, + expires: Option, +) -> Result { + // reject expired data as invalid + let expires = expires.unwrap_or_default(); + if expires.is_expired(&env.block) { + return Err(ContractError::Expired {}); + } + + // set the operator for us + let operator_addr = deps.api.addr_validate(&operator)?; + APPROVES.save(deps.storage, (&info.sender, &operator_addr), &expires)?; + + let mut rsp = Response::default(); + ApproveAllEvent { + sender: info.sender.as_ref(), + operator: &operator, + approved: true, + } + .add_attributes(&mut rsp); + Ok(rsp) +} + +pub fn execute_revoke_all( + deps: DepsMut, + _env: Env, + info: MessageInfo, + operator: String +) -> Result { + let operator_addr = deps.api.addr_validate(&operator)?; + APPROVES.remove(deps.storage, (&info.sender, &operator_addr)); + + let mut rsp = Response::default(); + ApproveAllEvent { + sender: info.sender.as_ref(), + operator: &operator, + approved: false, + } + .add_attributes(&mut rsp); + Ok(rsp) +} + +/// When from is None: mint new coins +/// When to is None: burn coins +/// When both are None: no token balance is changed, pointless but valid +/// +/// Make sure permissions are checked before calling this. +fn _execute_transfer_inner<'a>( + deps: &'a mut DepsMut, + height: u64, + from: Option<&'a Addr>, + to: Option<&'a Addr>, + token_id: &'a str, + amount: Uint128, +) -> Result, ContractError> { + if let Some(from_addr) = from { + + _deduct_vesting(deps.storage, &from_addr, (&token_id).to_string(), amount, height)?; + + BALANCES.update( + deps.storage, + (from_addr, token_id), + |balance: Option| -> StdResult<_> { + Ok(balance.unwrap_or_default().checked_sub(amount)?) + }, + )?; + } + + if let Some(to_addr) = to { + BALANCES.update( + deps.storage, + (to_addr, token_id), + |balance: Option| -> StdResult<_> { + Ok(balance.unwrap_or_default().checked_add(amount)?) + }, + )?; + } + + Ok(TransferEvent { + from: from.map(|x| x.as_ref()), + to: to.map(|x| x.as_ref()), + token_id, + amount, + }) +} + +fn _execute_mint( + mut deps: DepsMut, + env: Env, + info: MessageInfo, + to: String, + token_id: TokenId, + amount: Uint128, + msg: Option, +) -> Result { + let to_addr = deps.api.addr_validate(&to)?; + + if info.sender != MINTER.load(deps.storage)? { + return Err(ContractError::Unauthorized {}); + } + + let mut rsp = Response::default(); + + let event = _execute_transfer_inner(&mut deps, env.block.height, None, Some(&to_addr), &token_id, amount)?; + event.add_attributes(&mut rsp); + + if let Some(msg) = msg { + rsp.messages = vec![SubMsg::new( + Cw1155ReceiveMsg { + operator: info.sender.to_string(), + from: None, + amount, + token_id: token_id.clone(), + msg, + } + .into_cosmos_msg(to)?, + )] + } + + // insert if not exist + if !TOKENS.has(deps.storage, &token_id) { + // we must save some valid data here + TOKENS.save(deps.storage, &token_id, &String::new())?; + } + + Ok(rsp) +} + +fn _execute_burn( + mut deps: DepsMut, + env: Env, + info: MessageInfo, + from: String, + token_id: TokenId, + amount: Uint128, +) -> Result { + let from_addr = deps.api.addr_validate(&from)?; + + // whoever can transfer these tokens can burn + _guard_can_approve(deps.as_ref(), &env, &from_addr, &info.sender)?; + + let mut rsp = Response::default(); + let event = _execute_transfer_inner(&mut deps, env.block.height,Some(&from_addr), None, &token_id, amount)?; + event.add_attributes(&mut rsp); + Ok(rsp) +} + +fn _guard_can_approve( + deps: Deps, + env: &Env, + owner: &Addr, + operator: &Addr, +) -> Result<(), ContractError> { + if !check_can_approve(deps, env, owner, operator)? { + Err(ContractError::Unauthorized {}) + } else { + Ok(()) + } +} + +fn _deduct_vesting( + storage: &mut dyn Storage, + address: &Addr, + token_id: TokenId, + amount: Uint128, + current_block: u64, +) -> Result<(), ContractError> { + if !VESTINGS.has(storage, (address, &token_id)) { + return Ok(()) + }; + + let vesting = VESTINGS.load(storage, (address, &token_id))?; + if vesting.is_zero() { return Ok(()) }; + + let balance = BALANCES.load(storage, (address, &token_id))?; + let token_state = TOKENS_STATES.load(storage, &token_id)?; + + if token_state.created.add(VESTING_PERIOD) > current_block { + return if balance.sub(amount) < vesting { + Err(ContractError::VestingPeriod {}) + } else { Ok(()) } + } else { + VESTINGS.update(storage, (address, &token_id), |vesting| { + match vesting { + Some(vesting_amount) => { + if vesting_amount > amount { + Ok(vesting_amount.sub(amount)) + } else { Ok(Uint128::zero()) } + } + None => Err(ContractError::Unauthorized {}), + } + })?; + } + + Ok(()) +} \ No newline at end of file diff --git a/contracts/cw-neuron-booster/src/lib.rs b/contracts/cw-neuron-booster/src/lib.rs new file mode 100644 index 0000000..52c6a49 --- /dev/null +++ b/contracts/cw-neuron-booster/src/lib.rs @@ -0,0 +1,9 @@ +pub mod contract; +pub mod error; +pub mod msg; +pub mod state; +pub mod query; +pub mod execute; +mod tests; + +pub use crate::error::ContractError; diff --git a/contracts/cw-neuron-booster/src/msg.rs b/contracts/cw-neuron-booster/src/msg.rs new file mode 100644 index 0000000..789ff97 --- /dev/null +++ b/contracts/cw-neuron-booster/src/msg.rs @@ -0,0 +1,187 @@ +use cosmwasm_std::{Binary, Decimal, Uint128}; +use cw_utils::Expiration; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct InstantiateMsg {} + +pub type TokenId = String; + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +#[serde(rename_all = "snake_case")] +pub enum ExecuteMsg { + Mint { + reward: u64, + locked: bool, + msg: Option + }, + Fund { token_id: TokenId }, + Claim { token_id: TokenId }, + Buy { token_id: TokenId, msg: Option }, + Sell { + from: String, + token_id: TokenId, + value: Uint128, + }, + LockToken { token_id: TokenId }, + UpdateReward { token_id: TokenId, reward: u64 }, + SwapOutIn { + from: TokenId, + to: TokenId, + value: Uint128 + }, + SwapInOut { + to: TokenId, + from: TokenId, + value: Uint128 + }, + SendFrom { + from: String, + to: String, + token_id: TokenId, + value: Uint128, + msg: Option, + }, + BatchSendFrom { + from: String, + to: String, + batch: Vec<(TokenId, Uint128)>, + msg: Option, + }, + ApproveAll { + operator: String, + expires: Option, + }, + RevokeAll { operator: String }, +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +#[serde(rename_all = "snake_case")] +pub enum QueryMsg { + TokenState { token_id: TokenId }, + SpotPrice { token_id: TokenId }, + FundPrice { token_id: TokenId }, + FundsByBlock { + start_after: Option, + limit: Option + }, + FundsFromNeuron { + // TODO add pagination + neuron: String, + }, + FundsForNeuron { + // TODO add pagination + token_id: TokenId, + }, + Vestings { + // TODO add pagination + neuron: String, + }, + SwapOutIn { + from: TokenId, + to: TokenId, + value: Uint128 + }, + SwapInOut { + to: TokenId, + from: TokenId, + value: Uint128 + }, + Balance { owner: String, token_id: TokenId }, + BatchBalance { + owner: String, + token_ids: Vec, + }, + ApprovedForAll { + owner: String, + include_expired: Option, + start_after: Option, + limit: Option, + }, + IsApprovedForAll { owner: String, operator: String }, + TokenInfo { token_id: TokenId }, + Tokens { + owner: String, + start_after: Option, + limit: Option, + }, + AllTokens { + start_after: Option, + limit: Option, + }, +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct SpotPriceResponse { + pub spot_price: Decimal, +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct FundPriceResponse { + pub fund_price: Decimal, +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct TokenStateResponse { + pub reserve: Uint128, + pub supply: Uint128, + pub funds: Uint128, + pub funded: bool, + pub reward: u64, + pub locked: bool, + pub created: u64, + pub init_price: Decimal +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct FundsFromNeuronResponse { + pub funds: Vec +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct FundFromNeuron { + pub token_id: TokenId, + pub amount: Uint128, +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct FundsForNeuronResponse { + pub funds: Vec +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct FundForNeuron { + pub address: String, + pub amount: Uint128, +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct NeuronVestingsResponse { + pub vestings: Vec +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct Vesting { + pub token_id: TokenId, + pub amount: Uint128, +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct FundsResponse { + pub funds: Vec +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct Fund { + pub token_id: TokenId, + pub height: u64, +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct SwapResponse { + pub from: TokenId, + pub to: TokenId, + pub sell: Uint128, + pub buy: Uint128 +} diff --git a/contracts/cw-neuron-booster/src/query.rs b/contracts/cw-neuron-booster/src/query.rs new file mode 100644 index 0000000..fae728a --- /dev/null +++ b/contracts/cw-neuron-booster/src/query.rs @@ -0,0 +1,336 @@ +use std::ops::{Add, Mul}; + +use cosmwasm_std::{Decimal, StdError, Uint128}; +use cosmwasm_std::{ + Addr, Deps, Env, Order, StdResult, +}; +use cw1155::{ApprovedForAllResponse, BalanceResponse, BatchBalanceResponse, Expiration, TokensResponse}; +use cw20_bonding::curves::{Curve, decimal, DecimalPlaces, SquareRoot}; +use cw_storage_plus::Bound; + +use crate::msg::{Fund, FundForNeuron, FundFromNeuron, FundPriceResponse, FundsForNeuronResponse, FundsFromNeuronResponse, FundsResponse, NeuronVestingsResponse, SpotPriceResponse, SwapResponse, TokenId, TokenStateResponse, Vesting}; +use crate::state::{APPROVES, BALANCES, FUNDS_BY_BLOCKS, FUNDS_FOR_NEURONS, FUNDS_FROM_NEURONS, TOKENS, TOKENS_STATES, VESTINGS}; + +const DEFAULT_LIMIT: u32 = 10; +const MAX_LIMIT: u32 = 30; + +pub fn query_token_state( + deps: Deps, + _env: Env, + token_id: TokenId, +) -> StdResult { + let token_state = TOKENS_STATES.load(deps.storage, &token_id.clone())?; + // TODO refactor response + Ok(TokenStateResponse { + reserve: token_state.reserve, + supply: token_state.supply, + funds: token_state.funds, + funded: token_state.funded, + reward: token_state.reward, + locked: token_state.locked, + created: token_state.created, + init_price: token_state.init_price + }) +} + +pub fn query_spot_price( + deps: Deps, + _env: Env, + token_id: TokenId, +) -> StdResult { + let token_state = TOKENS_STATES.load(deps.storage, &token_id)?; + let curve = SquareRoot::new( + decimal(20u128, 2), + DecimalPlaces::new(3, 3) + ); + if token_state.funded { + let spot_price = curve.spot_price(token_state.supply); + Ok(SpotPriceResponse { spot_price }) + } else { + let spot_price = curve.spot_price(curve.supply(token_state.funds)); + Ok(SpotPriceResponse { spot_price }) + } +} + +pub fn query_fund_price( + deps: Deps, + _env: Env, + token_id: TokenId, +) -> StdResult { + let token_state = TOKENS_STATES.load(deps.storage, &token_id)?; + if token_state.funded { + let fund_price = token_state.init_price; + Ok(FundPriceResponse { fund_price }) + } else { + let curve = SquareRoot::new( + decimal(20u128, 2), + DecimalPlaces::new(3, 3) + ); + let fund_price = Decimal::from_ratio(token_state.funds, curve.supply(token_state.funds)); + Ok(FundPriceResponse { fund_price }) + } +} + +pub fn query_balance( + deps: Deps, + _env: Env, + owner_addr: Addr, + token_id: TokenId, +) -> StdResult { + let balance = BALANCES + .may_load(deps.storage, (&owner_addr, &token_id))? + .unwrap_or_default(); + Ok(BalanceResponse { balance }) +} + +pub fn query_batch_balance( + deps: Deps, + _env: Env, + owner_addr: Addr, + token_ids: Vec, +) -> StdResult { + let balances = token_ids + .into_iter() + .map(|token_id| -> StdResult<_> { + Ok(BALANCES + .may_load(deps.storage, (&owner_addr, &token_id))? + .unwrap_or_default()) + }) + .collect::>()?; + Ok(BatchBalanceResponse { balances }) +} + +pub fn query_all_approvals( + deps: Deps, + env: Env, + owner: Addr, + include_expired: bool, + start_after: Option, + limit: Option, +) -> StdResult { + let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; + let start = start_after.as_ref().map(Bound::exclusive); + + let operators = APPROVES + .prefix(&owner) + .range(deps.storage, start, None, Order::Ascending) + .filter(|r| include_expired || r.is_err() || !r.as_ref().unwrap().1.is_expired(&env.block)) + .take(limit) + .map(build_approval) + .collect::>()?; + Ok(ApprovedForAllResponse { operators }) +} + +pub fn query_tokens( + deps: Deps, + owner: Addr, + start_after: Option, + limit: Option, +) -> StdResult { + let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; + let start = start_after.as_ref().map(|s| Bound::exclusive(s.as_str())); + + let tokens = BALANCES + .prefix(&owner) + .keys(deps.storage, start, None, Order::Ascending) + .take(limit) + .collect::>()?; + Ok(TokensResponse { tokens }) +} + +pub fn query_all_tokens( + deps: Deps, + start_after: Option, + limit: Option, +) -> StdResult { + let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; + let start = start_after.as_ref().map(|s| Bound::exclusive(s.as_str())); + let tokens = TOKENS + .keys(deps.storage, start, None, Order::Ascending) + .take(limit) + .collect::>()?; + Ok(TokensResponse { tokens }) +} + +pub fn build_approval(item: StdResult<(Addr, Expiration)>) -> StdResult { + item.map(|(addr, expires)| cw1155::Approval { + spender: addr.into(), + expires, + }) +} + +pub fn query_all_funds( + deps: Deps, + _env: Env, + start_after: Option, + limit: Option, +) -> StdResult { + let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; + let start = start_after.map(Bound::exclusive); + + let funds = FUNDS_BY_BLOCKS + .range(deps.storage, start, None, Order::Ascending) + .take(limit) + .map(build_fund) + .collect::>()?; + Ok(FundsResponse { funds }) +} + +pub fn build_fund(item: StdResult<(u64, String)>) -> StdResult { + item.map(|(height, token_id)| Fund { + token_id, + height + }) +} + +pub fn query_all_funds_from_neuron( + deps: Deps, + _env: Env, + neuron: Addr, +) -> StdResult { + let funds = FUNDS_FROM_NEURONS + .prefix(&neuron) + .range(deps.storage, None, None, Order::Ascending) + .map(build_funds_from) + .collect::>()?; + Ok(FundsFromNeuronResponse { funds }) +} + +pub fn build_funds_from(item: StdResult<(String, Uint128)>) -> StdResult { + item.map(|(token_id, amount)| FundFromNeuron { + token_id, + amount, + }) +} + +pub fn query_all_funds_for_neuron( + deps: Deps, + _env: Env, + token_id: TokenId, +) -> StdResult { + let funds = FUNDS_FOR_NEURONS + .prefix(&token_id) + .range(deps.storage, None, None, Order::Ascending) + .map(build_funds_for) + .collect::>()?; + Ok(FundsForNeuronResponse { funds }) +} + +pub fn build_funds_for(item: StdResult<(Addr, Uint128)>) -> StdResult { + item.map(|(addr, amount)| FundForNeuron { + address: addr.into(), + amount, + }) +} + +pub fn query_all_neuron_vestings( + deps: Deps, + _env: Env, + neuron: Addr, +) -> StdResult { + let vestings = VESTINGS + .prefix(&neuron) + .range(deps.storage, None, None, Order::Ascending) + .map(build_vesting) + .collect::>()?; + Ok(NeuronVestingsResponse { vestings }) +} + +pub fn build_vesting(item: StdResult<(String, Uint128)>) -> StdResult { + item.map(|(token_id, amount)| Vesting { + token_id, + amount, + }) +} + +pub fn query_swap_out_in( + deps: Deps, + _env: Env, + from: TokenId, + to: TokenId, + value: Uint128, +) -> StdResult { + let mut out_token = TOKENS_STATES.load(deps.storage, &from.clone())?; + let mut in_token = TOKENS_STATES.load(deps.storage, &to.clone())?; + + let curve = SquareRoot::new( + decimal(20u128, 2), + DecimalPlaces::new(3,3) + ); + + out_token.supply = out_token + .supply + .checked_sub(value) + .map_err(StdError::overflow)?; + let new_reserve = curve.reserve(out_token.supply); + let mut released_out = out_token + .reserve + .checked_sub(new_reserve) + .map_err(StdError::overflow)?; + + let reward_out = Decimal::percent(out_token.reward).mul(released_out.clone()); + released_out = released_out + .checked_sub(reward_out.clone()) + .map_err(StdError::overflow)?; + + let reward_in = Decimal::percent(in_token.reward).mul(released_out.clone()); + let buy_in = released_out + .checked_sub(reward_in.clone()) + .map_err(StdError::overflow)?; + + in_token.reserve += buy_in; + let new_supply = curve.supply(in_token.reserve); + let minted = new_supply + .checked_sub(in_token.supply) + .map_err(StdError::overflow)?; + + Ok(SwapResponse { + from, + to, + sell: value, + buy: minted + }) +} + +pub fn query_swap_in_out( + deps: Deps, + _env: Env, + to: TokenId, + from: TokenId, + value: Uint128, +) -> StdResult { + let mut out_token = TOKENS_STATES.load(deps.storage, &from.clone())?; + let in_token = TOKENS_STATES.load(deps.storage, &to.clone())?; + + let curve = SquareRoot::new( + decimal(20u128, 2), + DecimalPlaces::new(3,3) + ); + + let mut reserve_in = curve.reserve(in_token.supply.add(value)); + reserve_in = reserve_in + .checked_sub(in_token.reserve) + .map_err(StdError::overflow)?; + let reward_in = Decimal::percent(in_token.reward).mul(reserve_in.clone()); + reserve_in = reserve_in + .checked_add(reward_in) + .map_err(StdError::overflow)?; + + let reward_out = Decimal::percent(out_token.reward).mul(reserve_in.clone()); + let reserve_out = reserve_in + .checked_sub(reward_out) + .map_err(StdError::overflow)?; + + out_token.reserve -= reserve_out; + let new_supply = curve.supply(out_token.reserve); + let burned = out_token.supply + .checked_sub(new_supply) + .map_err(StdError::overflow)?; + + Ok(SwapResponse { + from, + to, + sell: burned, + buy: value + }) +} \ No newline at end of file diff --git a/contracts/cw-neuron-booster/src/state.rs b/contracts/cw-neuron-booster/src/state.rs new file mode 100644 index 0000000..d4db954 --- /dev/null +++ b/contracts/cw-neuron-booster/src/state.rs @@ -0,0 +1,29 @@ +use cosmwasm_std::{Addr, Decimal, Uint128}; +use cw1155::Expiration; +use cw_storage_plus::{Item, Map}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct TokenState { + pub reserve: Uint128, + pub supply: Uint128, + pub funds: Uint128, + pub funded: bool, + pub reward: u64, + pub locked: bool, + pub created: u64, + pub init_price: Decimal +} + +pub const MINTER: Item = Item::new("minter"); +pub const BALANCES: Map<(&Addr, &str), Uint128> = Map::new("balances"); +pub const APPROVES: Map<(&Addr, &Addr), Expiration> = Map::new("approves"); +// TODO reuse TOKENS +pub const TOKENS: Map<&str, String> = Map::new("tokens"); +pub const TOKENS_STATES: Map<&str, TokenState> = Map::new("tokens_states"); + +pub const FUNDS_BY_BLOCKS: Map = Map::new("funds_by_blocks"); +pub const FUNDS_FROM_NEURONS: Map<(&Addr, &str), Uint128> = Map::new("funds_from"); +pub const FUNDS_FOR_NEURONS: Map<(&str, &Addr), Uint128> = Map::new("funds_for"); +pub const VESTINGS: Map<(&Addr, &str), Uint128> = Map::new("vestings"); diff --git a/contracts/cw-neuron-booster/src/tests.rs b/contracts/cw-neuron-booster/src/tests.rs new file mode 100644 index 0000000..5716c5b --- /dev/null +++ b/contracts/cw-neuron-booster/src/tests.rs @@ -0,0 +1,799 @@ +#[cfg(test)] +mod tests { + use std::ops::Add; + + use cosmwasm_std::{Addr, BankMsg, coin, coins, Response, Uint128}; + use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; + + use crate::contract::{execute, instantiate}; + use crate::ContractError; + use crate::msg::{ExecuteMsg, InstantiateMsg}; + use crate::query::{query_all_funds, query_all_funds_for_neuron, query_all_funds_from_neuron, query_all_neuron_vestings, query_balance, query_batch_balance, query_spot_price, query_swap_in_out, query_swap_out_in, query_token_state}; + + const RESERVE_DENOM: &str = "milliampere"; + const FUND_PERIOD: u64 = 250; + const VESTING_PERIOD: u64 = 500; + + #[test] + fn check_flow() { + // TODO A long test case that try to cover as many cases as possible. + // Summary of what it does: + // mint token1 by neuron1 + // fund token1 by neuron2 + // error to claim token1 by neuron1 + // error to buy token1 by neuron3 + // error to sell token1 by neuron2 + // -> pass blocks FUND_PERIOD + // claim token1 by neuron1 + // claim token1 by neuron2 + // buy token1 by neuron3 + // sell token1 by neuron3 + // error to transfer token1 by neuron2 to neuron3 + // buy token1 by neuron2 + // transfer bought token1 by neuron2 to neuron3 + // -> pass blocks VESTING_PERIOD + // transfer all token1 by neuron2 to neuron3 + // mint token2 by neuron2 + // mint token3 by neuron3 + // -> pass blocks FUND_PERIOD + // claim token2 by neuron3 + // claim token3 by neuron3 + // error swap out token3 in token2 by neuron3 + // error swap in token3 out token2 by neuron2 + // -> pass blocks VESTING_PERIOD + // swap out token3 in token2 by neuron3 + // swap in token3 out token2 by neuron2 + // buy token3 by neuron1 + let token1 = "neuron1".to_owned(); + let token2 = "neuron2".to_owned(); + let token3 = "neuron3".to_owned(); + let token4 = "neuron4".to_owned(); + let neuron1 = String::from("neuron1"); + let neuron2 = String::from("neuron2"); + let neuron3 = String::from("neuron3"); + let neuron4 = String::from("neuron4"); + let payment1 = 2000u128; + let payment2 = 2000u128; + let payment3 = 3000u128; + let payment4 = 10000u128; + let mut mock_env = mock_env(); + + let mut deps = mock_dependencies(); + let msg = InstantiateMsg {}; + let res = instantiate(deps.as_mut(), mock_env.clone(), mock_info("operator", &[]), msg).unwrap(); + assert_eq!(0, res.messages.len()); + + let mint_msg = ExecuteMsg::Mint { + reward: 10u64, + locked: false, + msg: None, + }; + assert_eq!( + execute( + deps.as_mut(), + mock_env.clone(), + mock_info(neuron1.as_ref(), &[coin(payment1, RESERVE_DENOM)]), + mint_msg.clone(), + ).unwrap(), + Response::new() + .add_attribute("action", "mint") + .add_attribute("token_id", &token1) + .add_attribute("from", &neuron1) + .add_attribute("reward", "0.1") + .add_attribute("locked", false.to_string()) + .add_attribute("funds", payment1.to_string()) + ); + + let fund_msg = ExecuteMsg::Fund { + token_id: neuron1.clone(), + }; + assert_eq!( + execute( + deps.as_mut(), + mock_env.clone(), + mock_info(neuron2.as_ref(), &[coin(payment2, RESERVE_DENOM)]), + fund_msg.clone(), + ).unwrap(), + Response::new() + .add_attribute("action", "fund") + .add_attribute("token_id", &token1) + .add_attribute("from", &neuron2) + .add_attribute("funds", payment2.to_string()) + ); + + let fail_claim_msg = ExecuteMsg::Claim { + token_id: neuron1.clone(), + }; + assert_eq!( + execute( + deps.as_mut(), + mock_env.clone(), + mock_info(neuron1.as_ref(), &[coin(0u128, RESERVE_DENOM)]), + fail_claim_msg.clone(), + ).unwrap_err(), + ContractError::FundingPeriod {} + ); + + let fail_buy_msg = ExecuteMsg::Buy { + token_id: neuron1.clone(), + msg: None, + }; + assert_eq!( + execute( + deps.as_mut(), + mock_env.clone(), + mock_info(neuron3.as_ref(), &[coin(payment3, RESERVE_DENOM)]), + fail_buy_msg.clone(), + ).unwrap_err(), + ContractError::FundingPeriod {} + ); + + let fail_sell_msg = ExecuteMsg::Sell { + from: neuron2.clone(), + token_id: neuron1.clone(), + value: 300u64.into(), + }; + + assert_eq!( + execute( + deps.as_mut(), + mock_env.clone(), + mock_info(neuron3.as_ref(), &[]), + fail_sell_msg + ).unwrap_err(), + ContractError::Unauthorized {} + ); + + mock_env.block.height = mock_env.block.height.add(FUND_PERIOD); + + let claim_msg = ExecuteMsg::Claim { + token_id: neuron1.clone(), + }; + assert_eq!( + execute( + deps.as_mut(), + mock_env.clone(), + mock_info(neuron1.as_ref(), &[coin(0u128, RESERVE_DENOM)]), + claim_msg.clone(), + ).unwrap(), + Response::new() + .add_attribute("action", "claim") + .add_attribute("token_id", &token1) + .add_attribute("amount", &4827u128.to_string()) + ); + + let claim_msg = ExecuteMsg::Claim { + token_id: neuron1.clone(), + }; + assert_eq!( + execute( + deps.as_mut(), + mock_env.clone(), + mock_info(neuron2.as_ref(), &[coin(0u128, RESERVE_DENOM)]), + claim_msg.clone(), + ).unwrap(), + Response::new() + .add_attribute("action", "claim") + .add_attribute("token_id", &token1) + .add_attribute("amount", &4827u128.to_string()) + ); + + println!("TOKEN 1 - {:?}", query_token_state( + deps.as_ref(), + mock_env.clone(), + token1.clone() + ).unwrap()); + + let buy_msg = ExecuteMsg::Buy { + token_id: neuron1.clone(), + msg: None, + }; + assert_eq!( + execute( + deps.as_mut(), + mock_env.clone(), + mock_info(neuron3.as_ref(), &[coin(payment3, RESERVE_DENOM)]), + buy_msg.clone(), + ) + .unwrap(), + Response::new() + .add_message(BankMsg::Send { + to_address: neuron1.clone(), + amount: coins(300u128, RESERVE_DENOM) + }) + .add_attribute("action", "buy") + .add_attribute("token_id", &token1) + .add_attribute("from", &neuron3) + .add_attribute("reserve", 6700.to_string()) + .add_attribute("supply", 13617.to_string()) + .add_attribute("payment", 3000.to_string()) + .add_attribute("reward", 300.to_string()) + .add_attribute("minted", 3963.to_string()) + ); + + let sell_msg = ExecuteMsg::Sell { + from: neuron3.clone(), + token_id: neuron1.clone(), + value: 300u64.into(), + }; + + assert_eq!( + execute( + deps.as_mut(), + mock_env.clone(), + mock_info(neuron3.as_ref(), &[]), + sell_msg + ) + .unwrap(), + Response::new() + .add_message(BankMsg::Send { + to_address: neuron3.clone(), + amount: coins(199u128, RESERVE_DENOM) + }) + .add_message(BankMsg::Send { + to_address: neuron1.clone(), + amount: coins(22u128, RESERVE_DENOM) + }) + .add_attribute("action", "burn") + .add_attribute("token_id", &token1) + .add_attribute("from", &neuron3) + .add_attribute("reward", 22.to_string()) + .add_attribute("payment", 199.to_string()) + .add_attribute("reserve", 6479.to_string()) + .add_attribute("supply", 13317.to_string()) + ); + + println!("FUNDS BY BLOCK - {:?}", query_all_funds( + deps.as_ref(), + mock_env.clone(), + None, + None + ).unwrap()); + + println!("FUNDS FROM NEURON 1 - {:?}", query_all_funds_from_neuron( + deps.as_ref(), + mock_env.clone(), + Addr::unchecked(neuron1.clone()) + ).unwrap()); + + println!("FUNDS FROM NEURON 2 - {:?}", query_all_funds_from_neuron( + deps.as_ref(), + mock_env.clone(), + Addr::unchecked(neuron2.clone()) + ).unwrap()); + + println!("FUNDS FROM NEURON 3 - {:?}", query_all_funds_from_neuron( + deps.as_ref(), + mock_env.clone(), + Addr::unchecked(neuron3.clone()) + ).unwrap()); + + println!("FUNDS FOR NEURON 1 - {:?}", query_all_funds_for_neuron( + deps.as_ref(), + mock_env.clone(), + neuron1.clone() + ).unwrap()); + + println!("FUNDS FOR NEURON 2 - {:?}", query_all_funds_for_neuron( + deps.as_ref(), + mock_env.clone(), + neuron2.clone() + ).unwrap()); + + println!("VESTINGS 1 - {:?}", query_all_neuron_vestings( + deps.as_ref(), + mock_env.clone(), + Addr::unchecked(neuron1.clone()) + ).unwrap()); + + println!("VESTINGS 2 - {:?}", query_all_neuron_vestings( + deps.as_ref(), + mock_env.clone(), + Addr::unchecked(neuron2.clone()) + ).unwrap()); + + println!("VESTINGS 3 - {:?}", query_all_neuron_vestings( + deps.as_ref(), + mock_env.clone(), + Addr::unchecked(neuron3.clone()) + ).unwrap()); + + println!("BALANCE 1 - {:?}", query_balance( + deps.as_ref(), + mock_env.clone(), + Addr::unchecked(neuron1.clone()), + neuron1.clone() + ).unwrap()); + + println!("BALANCE 2 - {:?}", query_balance( + deps.as_ref(), + mock_env.clone(), + Addr::unchecked(neuron2.clone()), + neuron1.clone() + ).unwrap()); + + println!("BALANCE 3 - {:?}", query_balance( + deps.as_ref(), + mock_env.clone(), + Addr::unchecked(neuron3.clone()), + neuron1.clone() + ).unwrap()); + + let transfer_msg = ExecuteMsg::SendFrom { + from: neuron2.clone(), + to: neuron3.clone(), + token_id: token1.clone(), + value: 100u64.into(), + msg: None, + }; + + assert_eq!( + execute( + deps.as_mut(), + mock_env.clone(), + mock_info(neuron2.as_ref(), &[]), + transfer_msg.clone(), + ), + Err(ContractError::VestingPeriod {}) + ); + + assert_eq!( + execute( + deps.as_mut(), + mock_env.clone(), + mock_info(neuron2.as_ref(), &[coin(payment3, RESERVE_DENOM)]), + buy_msg.clone(), + ).unwrap(), + Response::new() + .add_message(BankMsg::Send { + to_address: neuron1.clone(), + amount: coins(300u128, RESERVE_DENOM) + }) + .add_attribute("action", "buy") + .add_attribute("token_id", &token1) + .add_attribute("from", &neuron2) + .add_attribute("reserve", 9179.to_string()) + .add_attribute("supply", 16797.to_string()) + .add_attribute("payment", 3000.to_string()) + .add_attribute("reward", 300.to_string()) + .add_attribute("minted", 3480.to_string()) + ); + + let transfer_msg = ExecuteMsg::SendFrom { + from: neuron2.clone(), + to: neuron3.clone(), + token_id: token1.clone(), + value: 3480u64.into(), + msg: None, + }; + + assert_eq!( + execute( + deps.as_mut(), + mock_env.clone(), + mock_info(neuron2.as_ref(), &[]), + transfer_msg.clone(), + ).unwrap(), + Response::new() + .add_attribute("action", "transfer") + .add_attribute("token_id", &token1) + .add_attribute("amount", 3480u64.to_string()) + .add_attribute("from", &neuron2) + .add_attribute("to", &neuron3) + ); + + mock_env.block.height = mock_env.block.height.add(VESTING_PERIOD); + + let transfer_msg = ExecuteMsg::SendFrom { + from: neuron2.clone(), + to: neuron3.clone(), + token_id: token1.clone(), + value: 1242u64.into(), + msg: None, + }; + + assert_eq!( + execute( + deps.as_mut(), + mock_env.clone(), + mock_info(neuron2.as_ref(), &[]), + transfer_msg.clone(), + ).unwrap(), + Response::new() + .add_attribute("action", "transfer") + .add_attribute("token_id", &token1) + .add_attribute("amount", 1242u64.to_string()) + .add_attribute("from", &neuron2) + .add_attribute("to", &neuron3) + ); + + println!("VESTINGS 2 - {:?}", query_all_neuron_vestings( + deps.as_ref(), + mock_env.clone(), + Addr::unchecked(neuron2.clone()) + ).unwrap()); + + println!("BALANCE 2 - {:?}", query_balance( + deps.as_ref(), + mock_env.clone(), + Addr::unchecked(neuron2.clone()), + neuron1.clone() + ).unwrap()); + + println!("BALANCE 3 - {:?}", query_balance( + deps.as_ref(), + mock_env.clone(), + Addr::unchecked(neuron3.clone()), + neuron1.clone() + ).unwrap()); + + let mint_msg_neuron2 = ExecuteMsg::Mint { + reward: 10u64, + locked: false, + msg: None, + }; + assert_eq!( + execute( + deps.as_mut(), + mock_env.clone(), + mock_info(neuron2.as_ref(), &[coin(payment2, RESERVE_DENOM)]), + mint_msg_neuron2.clone(), + ).unwrap(), + Response::new() + .add_attribute("action", "mint") + .add_attribute("token_id", &token2) + .add_attribute("from", &neuron2) + .add_attribute("reward", "0.1") + .add_attribute("locked", false.to_string()) + .add_attribute("funds", payment2.to_string()) + ); + + let mint_msg_neuron3 = ExecuteMsg::Mint { + reward: 10u64, + locked: false, + msg: None, + }; + assert_eq!( + execute( + deps.as_mut(), + mock_env.clone(), + mock_info(neuron3.as_ref(), &[coin(payment2, RESERVE_DENOM)]), + mint_msg_neuron3.clone(), + ).unwrap(), + Response::new() + .add_attribute("action", "mint") + .add_attribute("token_id", &token3) + .add_attribute("from", &neuron3) + .add_attribute("reward", "0.1") + .add_attribute("locked", false.to_string()) + .add_attribute("funds", payment2.to_string()) + ); + + mock_env.block.height = mock_env.block.height.add(FUND_PERIOD); + + let claim_msg_neuron2 = ExecuteMsg::Claim { + token_id: neuron2.clone(), + }; + assert_eq!( + execute( + deps.as_mut(), + mock_env.clone(), + mock_info(neuron2.as_ref(), &[]), + claim_msg_neuron2.clone(), + ).unwrap(), + Response::new() + .add_attribute("action", "claim") + .add_attribute("token_id", &token2) + .add_attribute("amount", &6082u128.to_string()) + ); + + let claim_msg_neuron3 = ExecuteMsg::Claim { + token_id: neuron3.clone(), + }; + assert_eq!( + execute( + deps.as_mut(), + mock_env.clone(), + mock_info(neuron3.as_ref(), &[]), + claim_msg_neuron3.clone(), + ).unwrap(), + Response::new() + .add_attribute("action", "claim") + .add_attribute("token_id", &token3) + .add_attribute("amount", &6082u128.to_string()) + ); + + let swap_msg_neuron3 = ExecuteMsg::SwapOutIn { + from: token3.clone(), + to: token2.clone(), + value: 300u64.into(), + }; + + let swap_msg_neuron2 = ExecuteMsg::SwapInOut { + to: token3.clone(), + from: token2.clone(), + value: 300u64.into(), + }; + + assert_eq!( + execute( + deps.as_mut(), + mock_env.clone(), + mock_info(neuron3.as_ref(), &[]), + swap_msg_neuron3.clone(), + ), + Err(ContractError::VestingPeriod {}) + ); + + assert_eq!( + execute( + deps.as_mut(), + mock_env.clone(), + mock_info(neuron2.as_ref(), &[]), + swap_msg_neuron2.clone(), + ), + Err(ContractError::VestingPeriod {}) + ); + + mock_env.block.height = mock_env.block.height.add(VESTING_PERIOD); + + println!("BALANCE 2 - {:?}", query_batch_balance( + deps.as_ref(), + mock_env.clone(), + Addr::unchecked(neuron2.clone()), + vec![token2.clone(), token3.clone()] + ).unwrap()); + + println!("BALANCE 3 - {:?}", query_batch_balance( + deps.as_ref(), + mock_env.clone(), + Addr::unchecked(neuron3.clone()), + vec![token2.clone(), token3.clone()] + ).unwrap()); + + println!("SWAP_OUT_IN 32 - {:?}", query_swap_out_in( + deps.as_ref(), + mock_env.clone(), + token3.clone(), + token2.clone(), + Uint128::new(300), + ).unwrap()); + + assert_eq!( + execute( + deps.as_mut(), + mock_env.clone(), + mock_info(neuron3.as_ref(), &[]), + swap_msg_neuron3.clone(), + ).unwrap(), + Response::new() + .add_message(BankMsg::Send { + to_address: token3.clone(), + amount: coins(14u128, RESERVE_DENOM) + }) + .add_message(BankMsg::Send { + to_address: token2.clone(), + amount: coins(13u128, RESERVE_DENOM) + }) + .add_attribute("action", "swap_out_in") + .add_attribute("addr", &neuron3) + .add_attribute("from", &token3) + .add_attribute("to", &token2) + .add_attribute("sell", 300.to_string()) + .add_attribute("bought", 248.to_string()) + .add_attribute("reward_out", 14.to_string()) + .add_attribute("reward_in", 13.to_string()) + ); + + println!("SWAP_IN_OUT 32 - {:?}", query_swap_in_out( + deps.as_ref(), + mock_env.clone(), + token3.clone(), + token2.clone(), + Uint128::new(300), + ).unwrap()); + + assert_eq!( + execute( + deps.as_mut(), + mock_env.clone(), + mock_info(neuron2.as_ref(), &[]), + swap_msg_neuron2.clone(), + ).unwrap(), + Response::new() + .add_message(BankMsg::Send { + to_address: token2.clone(), + amount: coins(16u128, RESERVE_DENOM) + }) + .add_message(BankMsg::Send { + to_address: token3.clone(), + amount: coins(16u128, RESERVE_DENOM) + }) + .add_attribute("action", "swap_in_out") + .add_attribute("addr", &neuron2) + .add_attribute("from", &token2) + .add_attribute("to", &token3) + .add_attribute("bought", 300.to_string()) + .add_attribute("sell", 366.to_string()) + .add_attribute("reward_out", 16.to_string()) + .add_attribute("reward_in", 14.to_string()) + ); + + println!("BALANCE 2 - {:?}", query_batch_balance( + deps.as_ref(), + mock_env.clone(), + Addr::unchecked(neuron2.clone()), + vec![token2.clone(), token3.clone()] + ).unwrap()); + + println!("BALANCE 3 - {:?}", query_batch_balance( + deps.as_ref(), + mock_env.clone(), + Addr::unchecked(neuron3.clone()), + vec![token2.clone(), token3.clone()] + ).unwrap()); + + println!("VESTINGS 2 - {:?}", query_all_neuron_vestings( + deps.as_ref(), + mock_env.clone(), + Addr::unchecked(neuron2.clone()) + ).unwrap()); + + println!("VESTINGS 3 - {:?}", query_all_neuron_vestings( + deps.as_ref(), + mock_env.clone(), + Addr::unchecked(neuron3.clone()) + ).unwrap()); + + println!("SWAP_OUT_IN 32 - {:?}", query_swap_out_in( + deps.as_ref(), + mock_env.clone(), + token3.clone(), + token2.clone(), + Uint128::new(200), + ).unwrap()); + + println!("SWAP_IN_OUT 32 - {:?}", query_swap_in_out( + deps.as_ref(), + mock_env.clone(), + token3.clone(), + token2, + Uint128::new(200), + ).unwrap()); + + let update_reward_msg = ExecuteMsg::UpdateReward { + token_id: token3.clone(), + reward: 100u64 + }; + assert_eq!( + execute( + deps.as_mut(), + mock_env.clone(), + mock_info(neuron3.as_ref(), &[]), + update_reward_msg.clone(), + ).unwrap(), + Response::new() + .add_attribute("action", "update_reward") + .add_attribute("token_id", &token3) + .add_attribute("reward", &100u64.to_string()) + ); + + let lock_msg = ExecuteMsg::LockToken { + token_id: token3.clone(), + }; + assert_eq!( + execute( + deps.as_mut(), + mock_env.clone(), + mock_info(neuron3.as_ref(), &[]), + lock_msg.clone(), + ).unwrap(), + Response::new() + .add_attribute("action", "lock") + .add_attribute("token_id", &token3) + ); + + let update_reward_msg = ExecuteMsg::UpdateReward { + token_id: token3.clone(), + reward: 90u64 + }; + assert_eq!( + execute( + deps.as_mut(), + mock_env.clone(), + mock_info(neuron3.as_ref(), &[]), + update_reward_msg.clone(), + ).unwrap_err(), + ContractError::TokenLocked {} + ); + + let buy_msg = ExecuteMsg::Buy { + token_id: token3.clone(), + msg: None, + }; + assert_eq!( + execute( + deps.as_mut(), + mock_env.clone(), + mock_info(neuron2.as_ref(), &[coin(payment3, RESERVE_DENOM)]), + buy_msg.clone(), + ) + .unwrap(), + Response::new() + .add_message(BankMsg::Send { + to_address: neuron3.clone(), + amount: coins(3000u128, RESERVE_DENOM) + }) + .add_attribute("action", "buy") + .add_attribute("token_id", &token3) + .add_attribute("from", &neuron2) + .add_attribute("reserve", 2013u64.to_string()) + .add_attribute("supply", 6108u64.to_string()) + .add_attribute("payment", 3000u64.to_string()) + .add_attribute("reward", 3000u64.to_string()) + .add_attribute("minted", 0u64.to_string()) + ); + + println!("SPOT_PRICE 3 - {:?}", query_spot_price( + deps.as_ref(), + mock_env.clone(), + token3.clone(), + ).unwrap()); + + // DEBUG CASE + + let mint_msg = ExecuteMsg::Mint { + reward: 10u64, + locked: false, + msg: None, + }; + execute( + deps.as_mut(), + mock_env.clone(), + mock_info(neuron4.as_ref(), &[coin(payment4, RESERVE_DENOM)]), + mint_msg.clone(), + ); + + let fund_msg = ExecuteMsg::Fund { + token_id: token4.clone(), + }; + + execute( + deps.as_mut(), + mock_env.clone(), + mock_info(neuron4.as_ref(), &[coin(payment4, RESERVE_DENOM)]), + fund_msg.clone(), + ); + + mock_env.block.height = mock_env.block.height.add(FUND_PERIOD); + + let fail_claim_msg = ExecuteMsg::Claim { + token_id: token4.clone(), + }; + execute( + deps.as_mut(), + mock_env.clone(), + mock_info(neuron4.as_ref(), &[coin(0u128, RESERVE_DENOM)]), + fail_claim_msg.clone(), + ); + + println!("TOKEN 4 - {:?}", query_token_state( + deps.as_ref(), + mock_env.clone(), + token4.clone() + ).unwrap()); + + println!("BALANCE 4 - {:?}", query_balance( + deps.as_ref(), + mock_env.clone(), + Addr::unchecked(neuron4.clone()), + neuron4.clone() + ).unwrap()); + + println!("VESTINGS 4 - {:?}", query_all_neuron_vestings( + deps.as_ref(), + mock_env.clone(), + Addr::unchecked(neuron4.clone()) + ).unwrap()); + } +}