diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 384294de..082eab8b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -88,4 +88,7 @@ jobs: yarn build-images - run: make compile - name: Run tests - run: cd integration_tests && yarn test \ No newline at end of file + run: | + cd integration_tests + yarn vitest ./src --run --shard 1/2 --bail 1 + yarn vitest ./src --run --shard 2/2 --bail 1 \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 777e3011..187e2d9b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -619,6 +619,31 @@ version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +[[package]] +name = "lido-core" +version = "1.0.0" +dependencies = [ + "cosmos-sdk-proto", + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-storage", + "cw-multi-test 0.20.0", + "cw-ownable", + "cw-storage-plus", + "cw2", + "cw20", + "lido-staking-base", + "neutron-sdk", + "prost", + "prost-types", + "protobuf", + "schemars", + "serde", + "serde-json-wasm 1.0.0", + "tendermint-proto", + "thiserror", +] + [[package]] name = "lido-distribution" version = "1.0.0" @@ -642,6 +667,31 @@ dependencies = [ "tendermint-proto", ] +[[package]] +name = "lido-factory" +version = "1.0.0" +dependencies = [ + "cosmos-sdk-proto", + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-storage", + "cw-multi-test 0.20.0", + "cw-ownable", + "cw-storage-plus", + "cw2", + "cw20", + "lido-staking-base", + "neutron-sdk", + "prost", + "prost-types", + "protobuf", + "schemars", + "serde", + "serde-json-wasm 1.0.0", + "tendermint-proto", + "thiserror", +] + [[package]] name = "lido-interchain-interceptor" version = "1.0.0" @@ -675,7 +725,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", - "cw-multi-test 0.17.0", + "cw-multi-test 0.20.0", "cw-storage-plus", "cw2", "cw20", @@ -715,6 +765,18 @@ dependencies = [ "thiserror", ] +[[package]] +name = "lido-staking-base" +version = "1.0.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-storage", + "cw-multi-test 0.20.0", + "cw-storage-plus", + "neutron-sdk", +] + [[package]] name = "lido-stargate-poc" version = "1.0.0" @@ -771,6 +833,7 @@ dependencies = [ "cw-storage-plus", "cw-utils", "cw2", + "lido-staking-base", "neutron-sdk", "thiserror", ] diff --git a/Cargo.toml b/Cargo.toml index 46bb87bf..8d8a1c1c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,13 +3,16 @@ members = [ "contracts/stargate-poc", "contracts/distribution", + "contracts/factory", "contracts/interchain-interceptor", "contracts/interchain-interceptor-authz", "contracts/validators-stats", "contracts/token", "contracts/validators-set", "contracts/strategy", + "contracts/core", "packages/interchain-interceptor-base", + "packages/base", ] resolver = "2" @@ -25,6 +28,7 @@ protobuf = "3.2.0" tendermint-proto = "0.34.0" cosmwasm-std = { version = "1.5.0", default-features = false, features = [ "stargate", + "cosmwasm_1_2", ] } cosmwasm-schema = { version = "1.5.0", default-features = false } cw-storage-plus = { version = "1.2.0", default-features = false } @@ -40,6 +44,7 @@ sha2 = { version = "0.10.8" } bech32 = { version = "0.9.1" } lido-interchain-interceptor-base = { path = "./packages/interchain-interceptor-base", default-features = false } +lido-staking-base = { path = "./packages/base", default-features = false } thiserror = "1.0.50" [profile.release] diff --git a/Makefile b/Makefile index a52e079f..79ef51f1 100644 --- a/Makefile +++ b/Makefile @@ -23,6 +23,6 @@ compile: check_contracts: @cargo install cosmwasm-check - @cosmwasm-check --available-capabilities iterator,staking,stargate,neutron,cosmwasm_1_1 artifacts/*.wasm + @cosmwasm-check --available-capabilities iterator,staking,stargate,neutron,cosmwasm_1_1,cosmwasm_1_2 artifacts/*.wasm build: schema clippy test fmt doc compile check_contracts diff --git a/contracts/core/.cargo/config b/contracts/core/.cargo/config new file mode 100644 index 00000000..b2858fab --- /dev/null +++ b/contracts/core/.cargo/config @@ -0,0 +1,2 @@ +[alias] +schema = "run --bin lido-core-schema" diff --git a/contracts/core/Cargo.toml b/contracts/core/Cargo.toml new file mode 100644 index 00000000..803c4350 --- /dev/null +++ b/contracts/core/Cargo.toml @@ -0,0 +1,47 @@ +[package] +authors = ["Sergey Ratiashvili "] +description = "Contract to support staking core" +edition = "2021" +name = "lido-core" +version = "1.0.0" + +exclude = [ + # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. + "contract.wasm", + "hash.txt", +] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[dependencies] +cosmos-sdk-proto = { workspace = true } +prost = { workspace = true } +prost-types = { workspace = true } +protobuf = { workspace = true } +tendermint-proto = { workspace = true } + +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +cw-ownable = { workspace = true } +cw2 = { workspace = true } +cw20 = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true } +serde-json-wasm = { workspace = true } +thiserror = { workspace = true } +lido-staking-base = { workspace = true } +neutron-sdk = { workspace = true } + +[dev-dependencies] +cosmwasm-storage = { workspace = true } +cw-multi-test = { workspace = true } diff --git a/contracts/core/README.md b/contracts/core/README.md new file mode 100644 index 00000000..ad1967fe --- /dev/null +++ b/contracts/core/README.md @@ -0,0 +1 @@ +# LIDO Staking Core \ No newline at end of file diff --git a/contracts/core/src/bin/lido-core-schema.rs b/contracts/core/src/bin/lido-core-schema.rs new file mode 100644 index 00000000..cac8e638 --- /dev/null +++ b/contracts/core/src/bin/lido-core-schema.rs @@ -0,0 +1,11 @@ +use cosmwasm_schema::write_api; +use lido_staking_base::msg::core::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + query: QueryMsg, + execute: ExecuteMsg, + migrate: MigrateMsg + } +} diff --git a/contracts/core/src/contract.rs b/contracts/core/src/contract.rs new file mode 100644 index 00000000..9d1a3a01 --- /dev/null +++ b/contracts/core/src/contract.rs @@ -0,0 +1,172 @@ +use crate::error::{ContractError, ContractResult}; +use cosmwasm_std::{ + attr, ensure_eq, ensure_ne, entry_point, to_json_binary, Attribute, Binary, CosmosMsg, Decimal, + Deps, DepsMut, Env, MessageInfo, Response, StdResult, Uint128, WasmMsg, +}; +use cw2::set_contract_version; +use lido_staking_base::helpers::answer::response; +use lido_staking_base::msg::core::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use lido_staking_base::msg::token::ExecuteMsg as TokenExecuteMsg; +use lido_staking_base::state::core::CONFIG; +use neutron_sdk::bindings::{msg::NeutronMsg, query::NeutronQuery}; +use std::str::FromStr; +use std::vec; +const CONTRACT_NAME: &str = concat!("crates.io:lido-neutron-contracts__", env!("CARGO_PKG_NAME")); +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> ContractResult> { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + CONFIG.save(deps.storage, &msg.clone().into())?; + let attrs: Vec = vec![ + attr("token_contract", msg.token_contract), + attr("puppeteer_contract", msg.puppeteer_contract), + attr("strategy_contract", msg.strategy_contract), + attr("owner", msg.owner), + ]; + Ok(response("instantiate", CONTRACT_NAME, attrs)) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::Config {} => to_json_binary(&CONFIG.load(deps.storage)?), + QueryMsg::ExchangeRate {} => to_json_binary(&query_exchange_rate(deps, env)?), + } +} + +fn query_exchange_rate(_deps: Deps, _env: Env) -> StdResult { + Decimal::from_str("1.01") +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> ContractResult> { + match msg { + ExecuteMsg::Bond { receiver } => execute_bond(deps, env, info, receiver), + ExecuteMsg::Unbond { amount } => execute_unbond(deps, env, info, amount), + ExecuteMsg::UpdateConfig { + token_contract, + puppeteer_contract, + strategy_contract, + owner, + } => execute_update_config( + deps, + env, + info, + token_contract, + puppeteer_contract, + strategy_contract, + owner, + ), + } +} + +fn execute_bond( + deps: DepsMut, + env: Env, + info: MessageInfo, + receiver: Option, +) -> ContractResult> { + let config = CONFIG.load(deps.storage)?; + + let funds = info.funds; + ensure_ne!( + funds.len(), + 0, + ContractError::InvalidFunds { + reason: "no funds".to_string() + } + ); + ensure_eq!( + funds.len(), + 1, + ContractError::InvalidFunds { + reason: "expected 1 denom".to_string() + } + ); + let mut attrs = vec![attr("action", "bond")]; + + let amount = funds[0].amount; + let denom = funds[0].denom.to_string(); + check_denom(denom)?; + + let exchange_rate = query_exchange_rate(deps.as_ref(), env)?; + attrs.push(attr("exchange_rate", exchange_rate.to_string())); + + let issue_amount = amount * exchange_rate; + attrs.push(attr("issue_amount", issue_amount.to_string())); + + let receiver = receiver.map_or(Ok::(info.sender.to_string()), |a| { + deps.api.addr_validate(&a)?; + Ok(a) + })?; + attrs.push(attr("receiver", receiver.clone())); + + let msgs = vec![CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: config.token_contract, + msg: to_json_binary(&TokenExecuteMsg::Mint { + amount: issue_amount, + receiver, + })?, + funds: vec![], + })]; + Ok(response("execute-bond", CONTRACT_NAME, attrs).add_messages(msgs)) +} + +fn check_denom(_denom: String) -> ContractResult<()> { + //todo: check denom + Ok(()) +} + +fn execute_update_config( + deps: DepsMut, + _env: Env, + info: MessageInfo, + token_contract: Option, + puppeteer_contract: Option, + strategy_contract: Option, + owner: Option, +) -> ContractResult> { + let mut config = CONFIG.load(deps.storage)?; + ensure_eq!(config.owner, info.sender, ContractError::Unauthorized {}); + + let mut attrs = vec![attr("action", "update_config")]; + if let Some(token_contract) = token_contract { + config.token_contract = token_contract.clone(); + attrs.push(attr("token_contract", token_contract)); + } + if let Some(puppeteer_contract) = puppeteer_contract { + config.puppeteer_contract = puppeteer_contract.clone(); + attrs.push(attr("puppeteer_contract", puppeteer_contract)); + } + if let Some(strategy_contract) = strategy_contract { + config.strategy_contract = strategy_contract.clone(); + attrs.push(attr("strategy_contract", strategy_contract)); + } + if let Some(owner) = owner { + config.owner = owner.clone(); + attrs.push(attr("owner", owner)); + } + CONFIG.save(deps.storage, &config)?; + Ok(response("execute-update_config", CONTRACT_NAME, attrs)) +} + +fn execute_unbond( + _deps: DepsMut, + _env: Env, + _info: MessageInfo, + _amount: Uint128, +) -> ContractResult> { + unimplemented!("todo"); +} diff --git a/contracts/core/src/error.rs b/contracts/core/src/error.rs new file mode 100644 index 00000000..24c4bd60 --- /dev/null +++ b/contracts/core/src/error.rs @@ -0,0 +1,23 @@ +use cosmwasm_std::{OverflowError, StdError}; +use neutron_sdk::NeutronError; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("{0}")] + NeutronError(#[from] NeutronError), + + #[error("Invalid Funds: {reason}")] + InvalidFunds { reason: String }, + + #[error("{0}")] + OverflowError(#[from] OverflowError), + + #[error("Unauthorized")] + Unauthorized {}, +} + +pub type ContractResult = Result; diff --git a/contracts/core/src/lib.rs b/contracts/core/src/lib.rs new file mode 100644 index 00000000..ed729875 --- /dev/null +++ b/contracts/core/src/lib.rs @@ -0,0 +1,2 @@ +pub mod contract; +pub mod error; diff --git a/contracts/factory/.cargo/config b/contracts/factory/.cargo/config new file mode 100644 index 00000000..cbec3160 --- /dev/null +++ b/contracts/factory/.cargo/config @@ -0,0 +1,2 @@ +[alias] +schema = "run --bin lido-factory-schema" diff --git a/contracts/factory/Cargo.toml b/contracts/factory/Cargo.toml new file mode 100644 index 00000000..46b2d641 --- /dev/null +++ b/contracts/factory/Cargo.toml @@ -0,0 +1,47 @@ +[package] +authors = ["Sergey Ratiashvili "] +description = "Contract to deploy all Lido contracts" +edition = "2021" +name = "lido-factory" +version = "1.0.0" + +exclude = [ + # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. + "contract.wasm", + "hash.txt", +] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[dependencies] +cosmos-sdk-proto = { workspace = true } +prost = { workspace = true } +prost-types = { workspace = true } +protobuf = { workspace = true } +tendermint-proto = { workspace = true } + +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +cw-ownable = { workspace = true } +cw2 = { workspace = true } +cw20 = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true } +serde-json-wasm = { workspace = true } +thiserror = { workspace = true } +lido-staking-base = { workspace = true } +neutron-sdk = { workspace = true } + +[dev-dependencies] +cosmwasm-storage = { workspace = true } +cw-multi-test = { workspace = true } diff --git a/contracts/factory/README.md b/contracts/factory/README.md new file mode 100644 index 00000000..15eeff36 --- /dev/null +++ b/contracts/factory/README.md @@ -0,0 +1 @@ +# LIDO Staking Factory \ No newline at end of file diff --git a/contracts/factory/src/bin/lido-factory-schema.rs b/contracts/factory/src/bin/lido-factory-schema.rs new file mode 100644 index 00000000..99ff1616 --- /dev/null +++ b/contracts/factory/src/bin/lido-factory-schema.rs @@ -0,0 +1,11 @@ +use cosmwasm_schema::write_api; +use lido_factory::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + query: QueryMsg, + execute: ExecuteMsg, + migrate: MigrateMsg + } +} diff --git a/contracts/factory/src/contract.rs b/contracts/factory/src/contract.rs new file mode 100644 index 00000000..64ae1d99 --- /dev/null +++ b/contracts/factory/src/contract.rs @@ -0,0 +1,133 @@ +use crate::{ + error::ContractResult, + msg::{ExecuteMsg, InstantiateMsg, QueryMsg}, + state::{Config, State, CONFIG, STATE}, +}; +use cosmwasm_std::{ + attr, entry_point, instantiate2_address, to_json_binary, Binary, CodeInfoResponse, CosmosMsg, + Deps, DepsMut, Env, HexBinary, MessageInfo, Response, StdResult, WasmMsg, +}; +use cw2::set_contract_version; +use lido_staking_base::msg::token::InstantiateMsg as TokenInstantiateMsg; +use lido_staking_base::{ + helpers::answer::response, msg::core::InstantiateMsg as CoreInstantiateMsg, +}; +use neutron_sdk::{ + bindings::{msg::NeutronMsg, query::NeutronQuery}, + NeutronResult, +}; + +const CONTRACT_NAME: &str = concat!("crates.io:lido-neutron-contracts__", env!("CARGO_PKG_NAME")); +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> ContractResult> { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + CONFIG.save( + deps.storage, + &Config { + salt: msg.salt.to_string(), + token_code_id: msg.token_code_id, + core_code_id: msg.core_code_id, + owner: info.sender.to_string(), + subdenom: msg.subdenom.to_string(), + }, + )?; + + let attrs = vec![ + attr("salt", msg.salt), + attr("token_code_id", msg.token_code_id.to_string()), + attr("core_code_id", msg.core_code_id.to_string()), + attr("owner", info.sender), + attr("subdenom", msg.subdenom), + ]; + Ok(response("instantiate", CONTRACT_NAME, attrs)) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::State {} => to_json_binary(&STATE.load(deps.storage)?), + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> ContractResult> { + match msg { + ExecuteMsg::Init {} => execute_init(deps, env, info), + } +} + +fn execute_init( + deps: DepsMut, + env: Env, + _info: MessageInfo, +) -> ContractResult> { + let config = CONFIG.load(deps.storage)?; + let canonical_self_address = deps.api.addr_canonicalize(env.contract.address.as_str())?; + let mut attrs = vec![attr("action", "init")]; + + let token_contract_checksum = get_code_checksum(deps.as_ref(), config.token_code_id)?; + let core_contract_checksum = get_code_checksum(deps.as_ref(), config.core_code_id)?; + let salt = config.salt.as_bytes(); + + let token_address = + instantiate2_address(&token_contract_checksum, &canonical_self_address, salt)?; + attrs.push(attr("token_address", token_address.to_string())); + let core_address = + instantiate2_address(&core_contract_checksum, &canonical_self_address, salt)?; + attrs.push(attr("core_address", core_address.to_string())); + + let core_contract = deps.api.addr_humanize(&core_address)?.to_string(); + let token_contract = deps.api.addr_humanize(&token_address)?.to_string(); + let state = State { + token_contract: token_contract.to_string(), + core_contract: core_contract.to_string(), + }; + + STATE.save(deps.storage, &state)?; + let msgs = vec![ + CosmosMsg::Wasm(WasmMsg::Instantiate2 { + admin: Some(env.contract.address.to_string()), + code_id: config.token_code_id, + label: "token".to_string(), + msg: to_json_binary(&TokenInstantiateMsg { + core_address: core_contract, + subdenom: config.subdenom, + })?, + funds: vec![], + salt: Binary::from(salt), + }), + CosmosMsg::Wasm(WasmMsg::Instantiate2 { + admin: Some(env.contract.address.to_string()), + code_id: config.core_code_id, + label: "core".to_string(), + msg: to_json_binary(&CoreInstantiateMsg { + token_contract: token_contract.to_string(), + puppeteer_contract: "".to_string(), + strategy_contract: "".to_string(), + owner: env.contract.address.to_string(), + })?, + funds: vec![], + salt: Binary::from(salt), + }), + ]; + + Ok(response("execute-init", CONTRACT_NAME, attrs).add_messages(msgs)) +} + +fn get_code_checksum(deps: Deps, code_id: u64) -> NeutronResult { + let CodeInfoResponse { checksum, .. } = deps.querier.query_wasm_code_info(code_id)?; + Ok(checksum) +} diff --git a/contracts/factory/src/error.rs b/contracts/factory/src/error.rs new file mode 100644 index 00000000..ac50d00c --- /dev/null +++ b/contracts/factory/src/error.rs @@ -0,0 +1,23 @@ +use cosmwasm_std::{Instantiate2AddressError, StdError}; +use neutron_sdk::NeutronError; + +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("{0}")] + NeutronError(#[from] NeutronError), + #[error("Could not calculcate instantiate2 address: {0}")] + Instantiate2AddressError(#[from] Instantiate2AddressError), + #[error("Unauthorized")] + Unauthorized {}, + #[error("Unimplemented")] + Unimplemented {}, + #[error("Unknown")] + Unknown {}, +} + +pub type ContractResult = Result; diff --git a/contracts/factory/src/lib.rs b/contracts/factory/src/lib.rs new file mode 100644 index 00000000..a5abdbb0 --- /dev/null +++ b/contracts/factory/src/lib.rs @@ -0,0 +1,4 @@ +pub mod contract; +pub mod error; +pub mod msg; +pub mod state; diff --git a/contracts/factory/src/msg.rs b/contracts/factory/src/msg.rs new file mode 100644 index 00000000..5ecd6303 --- /dev/null +++ b/contracts/factory/src/msg.rs @@ -0,0 +1,25 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; + +use crate::state::State; + +#[cw_serde] +pub struct InstantiateMsg { + pub token_code_id: u64, + pub core_code_id: u64, + pub salt: String, + pub subdenom: String, +} + +#[cw_serde] +pub enum ExecuteMsg { + Init {}, +} +#[cw_serde] +pub enum MigrateMsg {} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(State)] + State {}, +} diff --git a/contracts/factory/src/state.rs b/contracts/factory/src/state.rs new file mode 100644 index 00000000..a3498c97 --- /dev/null +++ b/contracts/factory/src/state.rs @@ -0,0 +1,20 @@ +use cosmwasm_schema::cw_serde; +use cw_storage_plus::Item; + +#[cw_serde] +pub struct Config { + pub token_code_id: u64, + pub core_code_id: u64, + pub owner: String, + pub salt: String, + pub subdenom: String, +} + +#[cw_serde] +pub struct State { + pub token_contract: String, + pub core_contract: String, +} + +pub const CONFIG: Item = Item::new("config"); +pub const STATE: Item = Item::new("state"); diff --git a/contracts/interchain-interceptor-authz/Cargo.toml b/contracts/interchain-interceptor-authz/Cargo.toml index ed384dde..210b6c04 100644 --- a/contracts/interchain-interceptor-authz/Cargo.toml +++ b/contracts/interchain-interceptor-authz/Cargo.toml @@ -26,25 +26,25 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -base64 = { workspace = true } -cosmos-sdk-proto = { workspace = true } -neutron-sdk = { workspace = true } -prost = { workspace = true } -prost-types = { workspace = true } -protobuf = { workspace = true } -tendermint-proto = { workspace = true } - -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } -cw-storage-plus = { workspace = true } -cw2 = { workspace = true } -cw20 = { workspace = true } -schemars = { workspace = true } -serde = { workspace = true } -serde-json-wasm = { workspace = true } +base64 = { workspace = true } +cosmos-sdk-proto = { workspace = true } +neutron-sdk = { workspace = true } +prost = { workspace = true } +prost-types = { workspace = true } +protobuf = { workspace = true } +tendermint-proto = { workspace = true } + +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +cw2 = { workspace = true } +cw20 = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true } +serde-json-wasm = { workspace = true } lido-interchain-interceptor-base = { workspace = true } [dev-dependencies] cosmwasm-storage = { version = "1.0" } -cw-multi-test = "0.17.0" +cw-multi-test = "0.20.0" diff --git a/contracts/token/Cargo.toml b/contracts/token/Cargo.toml index eff32356..27bb7857 100644 --- a/contracts/token/Cargo.toml +++ b/contracts/token/Cargo.toml @@ -1,22 +1,23 @@ [package] -authors = [ "Murad Karammaev " ] +authors = ["Murad Karammaev "] description = "Contract module which mints and burns tokens" edition = "2021" name = "lido-token" version = "1.0.0" [lib] -crate-type = [ "cdylib", "rlib" ] +crate-type = ["cdylib", "rlib"] [features] -backtraces = [ "cosmwasm-std/backtraces" ] +backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -cosmwasm-std = { workspace = true } +cosmwasm-std = { workspace = true } +lido-staking-base = { workspace = true } cosmwasm-schema = { workspace = true } cw-storage-plus = { workspace = true } -cw2 = { workspace = true } -cw-utils = { workspace = true } -neutron-sdk = { workspace = true } -thiserror = { workspace = true } +cw2 = { workspace = true } +cw-utils = { workspace = true } +neutron-sdk = { workspace = true } +thiserror = { workspace = true } diff --git a/contracts/token/src/bin/lido-token-schema.rs b/contracts/token/src/bin/lido-token-schema.rs index f5f1b88b..ecb0a4aa 100644 --- a/contracts/token/src/bin/lido-token-schema.rs +++ b/contracts/token/src/bin/lido-token-schema.rs @@ -1,5 +1,6 @@ use cosmwasm_schema::write_api; -use lido_token::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; + +use lido_staking_base::msg::token::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; fn main() { write_api! { diff --git a/contracts/token/src/contract.rs b/contracts/token/src/contract.rs new file mode 100644 index 00000000..126bc685 --- /dev/null +++ b/contracts/token/src/contract.rs @@ -0,0 +1,142 @@ +use cosmwasm_std::{ + attr, ensure_eq, ensure_ne, entry_point, to_json_binary, Binary, Deps, DepsMut, Env, + MessageInfo, Reply, Response, SubMsg, Uint128, +}; + +use lido_staking_base::{ + helpers::answer::{attr_coin, response}, + msg::token::{ConfigResponse, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}, + state::token::{CORE_ADDRESS, DENOM}, +}; +use neutron_sdk::{ + bindings::{msg::NeutronMsg, query::NeutronQuery}, + query::token_factory::query_full_denom, +}; + +use crate::error::{ContractError, ContractResult}; + +pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); +pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); +pub const CREATE_DENOM_REPLY_ID: u64 = 1; + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> ContractResult> { + cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + let core = deps.api.addr_validate(&msg.core_address)?; + CORE_ADDRESS.save(deps.storage, &core)?; + + DENOM.save(deps.storage, &msg.subdenom)?; + let create_denom_msg = SubMsg::reply_on_success( + NeutronMsg::submit_create_denom(&msg.subdenom), + CREATE_DENOM_REPLY_ID, + ); + + Ok(response( + "instantiate", + CONTRACT_NAME, + [attr("core_address", core), attr("subdenom", msg.subdenom)], + ) + .add_submessage(create_denom_msg)) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> ContractResult> { + let core = CORE_ADDRESS.load(deps.storage)?; + ensure_eq!(info.sender, core, ContractError::Unauthorized); + + match msg { + ExecuteMsg::Mint { amount, receiver } => mint(deps, amount, receiver), + ExecuteMsg::Burn {} => burn(deps, info), + } +} + +fn mint( + deps: DepsMut, + amount: Uint128, + receiver: String, +) -> ContractResult> { + ensure_ne!(amount, Uint128::zero(), ContractError::NothingToMint); + + let denom = DENOM.load(deps.storage)?; + let mint_msg = NeutronMsg::submit_mint_tokens(&denom, amount, &receiver); + + Ok(response( + "execute-mint", + CONTRACT_NAME, + [ + attr_coin("amount", amount, denom), + attr("receiver", receiver), + ], + ) + .add_message(mint_msg)) +} + +fn burn(deps: DepsMut, info: MessageInfo) -> ContractResult> { + let denom = DENOM.load(deps.storage)?; + let amount = cw_utils::must_pay(&info, &denom)?; + + let burn_msg = NeutronMsg::submit_burn_tokens(&denom, amount); + + Ok(response( + "execute-burn", + CONTRACT_NAME, + [attr_coin("amount", amount, denom)], + ) + .add_message(burn_msg)) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> ContractResult { + match msg { + QueryMsg::Config {} => { + let core_address = CORE_ADDRESS.load(deps.storage)?.into_string(); + let denom = DENOM.load(deps.storage)?; + Ok(to_json_binary(&ConfigResponse { + core_address, + denom, + })?) + } + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate( + _deps: DepsMut, + _env: Env, + _msg: MigrateMsg, +) -> ContractResult> { + Ok(Response::new()) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn reply( + deps: DepsMut, + env: Env, + msg: Reply, +) -> ContractResult> { + match msg.id { + CREATE_DENOM_REPLY_ID => { + let subdenom = DENOM.load(deps.storage)?; + let full_denom = query_full_denom(deps.as_ref(), env.contract.address, subdenom)?; + DENOM.save(deps.storage, &full_denom.denom)?; + + Ok(response( + "reply-create-denom", + CONTRACT_NAME, + [attr("denom", full_denom.denom)], + )) + } + id => Err(ContractError::UnknownReplyId { id }), + } +} diff --git a/contracts/token/src/error.rs b/contracts/token/src/error.rs new file mode 100644 index 00000000..b3dcb594 --- /dev/null +++ b/contracts/token/src/error.rs @@ -0,0 +1,24 @@ +use cosmwasm_std::StdError; + +#[derive(thiserror::Error, Debug, PartialEq)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("{0}")] + NeutronError(#[from] neutron_sdk::NeutronError), + + #[error("{0}")] + PaymentError(#[from] cw_utils::PaymentError), + + #[error("unauthorized")] + Unauthorized, + + #[error("nothing to mint")] + NothingToMint, + + #[error("unknown reply id: {id}")] + UnknownReplyId { id: u64 }, +} + +pub type ContractResult = Result; diff --git a/contracts/token/src/lib.rs b/contracts/token/src/lib.rs index f7c6768f..8a8512dd 100644 --- a/contracts/token/src/lib.rs +++ b/contracts/token/src/lib.rs @@ -1,197 +1,5 @@ -use cosmwasm_std::{ - attr, ensure_eq, ensure_ne, to_json_binary, Addr, Attribute, Binary, Deps, DepsMut, Env, Event, - MessageInfo, Reply, Response, StdError, SubMsg, Uint128, -}; -use cw_storage_plus::Item; -use neutron_sdk::{ - bindings::{msg::NeutronMsg, query::NeutronQuery}, - query::token_factory::query_full_denom, -}; - #[cfg(test)] mod tests; -#[derive(thiserror::Error, Debug, PartialEq)] -pub enum ContractError { - #[error("{0}")] - Std(#[from] StdError), - - #[error("{0}")] - NeutronError(#[from] neutron_sdk::NeutronError), - - #[error("{0}")] - PaymentError(#[from] cw_utils::PaymentError), - - #[error("unauthorized")] - Unauthorized, - - #[error("nothing to mint")] - NothingToMint, - - #[error("unknown reply id: {id}")] - UnknownReplyId { id: u64 }, -} - -pub type ContractResult = Result; - -#[cosmwasm_schema::cw_serde] -pub struct InstantiateMsg { - pub core: String, - pub subdenom: String, -} - -#[cosmwasm_schema::cw_serde] -pub enum ExecuteMsg { - Mint { amount: Uint128, receiver: String }, - Burn {}, -} - -#[cosmwasm_schema::cw_serde] -#[derive(cosmwasm_schema::QueryResponses)] -pub enum QueryMsg { - #[returns(ConfigResponse)] - Config {}, -} - -#[cosmwasm_schema::cw_serde] -pub struct ConfigResponse { - pub core: String, - pub denom: String, -} - -#[cosmwasm_schema::cw_serde] -pub enum MigrateMsg {} - -const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); -const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); - -const CORE: Item = Item::new("core"); -const DENOM: Item = Item::new("denom"); - -const CREATE_DENOM_REPLY_ID: u64 = 1; - -fn response>( - ty: &str, - attrs: impl IntoIterator, -) -> Response { - Response::new().add_event(Event::new(format!("{}-{}", CONTRACT_NAME, ty)).add_attributes(attrs)) -} - -fn attr_coin( - key: impl Into, - amount: impl std::fmt::Display, - denom: impl std::fmt::Display, -) -> Attribute { - attr(key, format!("{}{}", amount, denom)) -} - -#[cfg_attr(not(feature = "library"), cosmwasm_std::entry_point)] -pub fn instantiate( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - msg: InstantiateMsg, -) -> ContractResult> { - cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - - let core = deps.api.addr_validate(&msg.core)?; - CORE.save(deps.storage, &core)?; - - DENOM.save(deps.storage, &msg.subdenom)?; - let create_denom_msg = SubMsg::reply_on_success( - NeutronMsg::submit_create_denom(&msg.subdenom), - CREATE_DENOM_REPLY_ID, - ); - - Ok(response( - "instantiate", - [attr("core", core), attr("subdenom", msg.subdenom)], - ) - .add_submessage(create_denom_msg)) -} - -#[cfg_attr(not(feature = "library"), cosmwasm_std::entry_point)] -pub fn execute( - deps: DepsMut, - _env: Env, - info: MessageInfo, - msg: ExecuteMsg, -) -> ContractResult> { - let core = CORE.load(deps.storage)?; - ensure_eq!(info.sender, core, ContractError::Unauthorized); - - match msg { - ExecuteMsg::Mint { amount, receiver } => mint(deps, amount, receiver), - ExecuteMsg::Burn {} => burn(deps, info), - } -} - -fn mint( - deps: DepsMut, - amount: Uint128, - receiver: String, -) -> ContractResult> { - ensure_ne!(amount, Uint128::zero(), ContractError::NothingToMint); - - let denom = DENOM.load(deps.storage)?; - let mint_msg = NeutronMsg::submit_mint_tokens(&denom, amount, &receiver); - - Ok(response( - "execute-mint", - [ - attr_coin("amount", amount, denom), - attr("receiver", receiver), - ], - ) - .add_message(mint_msg)) -} - -fn burn(deps: DepsMut, info: MessageInfo) -> ContractResult> { - let denom = DENOM.load(deps.storage)?; - let amount = cw_utils::must_pay(&info, &denom)?; - - let burn_msg = NeutronMsg::submit_burn_tokens(&denom, amount); - - Ok(response("execute-burn", [attr_coin("amount", amount, denom)]).add_message(burn_msg)) -} - -#[cfg_attr(not(feature = "library"), cosmwasm_std::entry_point)] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> ContractResult { - match msg { - QueryMsg::Config {} => { - let core = CORE.load(deps.storage)?.into_string(); - let denom = DENOM.load(deps.storage)?; - Ok(to_json_binary(&ConfigResponse { core, denom })?) - } - } -} - -#[cfg_attr(not(feature = "library"), cosmwasm_std::entry_point)] -pub fn migrate( - _deps: DepsMut, - _env: Env, - _msg: MigrateMsg, -) -> ContractResult> { - Ok(Response::new()) -} - -#[cfg_attr(not(feature = "library"), cosmwasm_std::entry_point)] -pub fn reply( - deps: DepsMut, - env: Env, - msg: Reply, -) -> ContractResult> { - match msg.id { - CREATE_DENOM_REPLY_ID => { - let subdenom = DENOM.load(deps.storage)?; - let full_denom = query_full_denom(deps.as_ref(), env.contract.address, subdenom)?; - DENOM.save(deps.storage, &full_denom.denom)?; - - Ok(response( - "reply-create-denom", - [attr("denom", full_denom.denom)], - )) - } - id => Err(ContractError::UnknownReplyId { id }), - } -} +pub mod contract; +pub mod error; diff --git a/contracts/token/src/tests.rs b/contracts/token/src/tests.rs index 1e9eab52..1ada48d4 100644 --- a/contracts/token/src/tests.rs +++ b/contracts/token/src/tests.rs @@ -4,12 +4,21 @@ use cosmwasm_std::{ to_json_binary, Addr, ContractResult, CosmosMsg, Event, OwnedDeps, Querier, QuerierResult, QueryRequest, Reply, ReplyOn, SubMsgResult, SystemError, Uint128, }; +use lido_staking_base::{ + msg::token::{ConfigResponse, ExecuteMsg, InstantiateMsg, QueryMsg}, + state::token::{CORE_ADDRESS, DENOM}, +}; use neutron_sdk::{ bindings::{msg::NeutronMsg, query::NeutronQuery}, query::token_factory::FullDenomResponse, }; use std::marker::PhantomData; +use crate::{ + contract::{self, CREATE_DENOM_REPLY_ID}, + error::ContractError, +}; + fn mock_dependencies() -> OwnedDeps { OwnedDeps { storage: MockStorage::default(), @@ -22,26 +31,26 @@ fn mock_dependencies() -> OwnedDeps(); - let response = crate::instantiate( + let response = contract::instantiate( deps.as_mut(), mock_env(), mock_info("admin", &[]), - crate::InstantiateMsg { - core: "core".to_string(), + InstantiateMsg { + core_address: "core".to_string(), subdenom: "subdenom".to_string(), }, ) .unwrap(); - let core = crate::CORE.load(deps.as_ref().storage).unwrap(); + let core = CORE_ADDRESS.load(deps.as_ref().storage).unwrap(); assert_eq!(core, Addr::unchecked("core")); - let denom = crate::DENOM.load(deps.as_ref().storage).unwrap(); + let denom = DENOM.load(deps.as_ref().storage).unwrap(); assert_eq!(denom, "subdenom"); assert_eq!(response.messages.len(), 1); assert_eq!(response.messages[0].reply_on, ReplyOn::Success); - assert_eq!(response.messages[0].id, crate::CREATE_DENOM_REPLY_ID); + assert_eq!(response.messages[0].id, CREATE_DENOM_REPLY_ID); assert_eq!( response.messages[0].msg, CosmosMsg::Custom(NeutronMsg::CreateDenom { @@ -51,7 +60,7 @@ fn instantiate() { assert_eq!( response.events, vec![Event::new("lido-token-instantiate") - .add_attributes([attr("core", core), attr("subdenom", "subdenom")])] + .add_attributes([attr("core_address", core), attr("subdenom", "subdenom")])] ); assert!(response.attributes.is_empty()); } @@ -59,7 +68,7 @@ fn instantiate() { #[test] fn reply_unknown_id() { let mut deps = mock_dependencies::(); - let error = crate::reply( + let error = crate::contract::reply( deps.as_mut(), mock_env(), Reply { @@ -68,7 +77,7 @@ fn reply_unknown_id() { }, ) .unwrap_err(); - assert_eq!(error, crate::ContractError::UnknownReplyId { id: 215 }); + assert_eq!(error, ContractError::UnknownReplyId { id: 215 }); } #[test] @@ -110,20 +119,20 @@ fn reply() { } let mut deps = mock_dependencies::(); - crate::DENOM + DENOM .save(deps.as_mut().storage, &String::from("subdenom")) .unwrap(); - let response = crate::reply( + let response = crate::contract::reply( deps.as_mut(), mock_env(), Reply { - id: crate::CREATE_DENOM_REPLY_ID, + id: CREATE_DENOM_REPLY_ID, result: SubMsgResult::Err("".to_string()), }, ) .unwrap(); - let denom = crate::DENOM.load(deps.as_ref().storage).unwrap(); + let denom = DENOM.load(deps.as_ref().storage).unwrap(); assert_eq!(denom, "factory/subdenom"); assert!(response.messages.is_empty()); @@ -138,41 +147,41 @@ fn reply() { #[test] fn mint_zero() { let mut deps = mock_dependencies::(); - crate::CORE + CORE_ADDRESS .save(deps.as_mut().storage, &Addr::unchecked("core")) .unwrap(); - crate::DENOM + DENOM .save(deps.as_mut().storage, &String::from("denom")) .unwrap(); - let error = crate::execute( + let error = crate::contract::execute( deps.as_mut(), mock_env(), mock_info("core", &[]), - crate::ExecuteMsg::Mint { + ExecuteMsg::Mint { amount: Uint128::zero(), receiver: "receiver".to_string(), }, ) .unwrap_err(); - assert_eq!(error, crate::ContractError::NothingToMint); + assert_eq!(error, ContractError::NothingToMint); } #[test] fn mint() { let mut deps = mock_dependencies::(); - crate::CORE + CORE_ADDRESS .save(deps.as_mut().storage, &Addr::unchecked("core")) .unwrap(); - crate::DENOM + DENOM .save(deps.as_mut().storage, &String::from("denom")) .unwrap(); - let response = crate::execute( + let response = crate::contract::execute( deps.as_mut(), mock_env(), mock_info("core", &[]), - crate::ExecuteMsg::Mint { + ExecuteMsg::Mint { amount: Uint128::new(220), receiver: "receiver".to_string(), }, @@ -199,113 +208,111 @@ fn mint() { #[test] fn mint_stranger() { let mut deps = mock_dependencies::(); - crate::CORE + CORE_ADDRESS .save(deps.as_mut().storage, &Addr::unchecked("core")) .unwrap(); - crate::DENOM + DENOM .save(deps.as_mut().storage, &String::from("denom")) .unwrap(); - let error = crate::execute( + let error = crate::contract::execute( deps.as_mut(), mock_env(), mock_info("stranger", &[]), - crate::ExecuteMsg::Mint { + ExecuteMsg::Mint { amount: Uint128::new(220), receiver: "receiver".to_string(), }, ) .unwrap_err(); - assert_eq!(error, crate::ContractError::Unauthorized); + assert_eq!(error, ContractError::Unauthorized); } #[test] fn burn_zero() { let mut deps = mock_dependencies::(); - crate::CORE + CORE_ADDRESS .save(deps.as_mut().storage, &Addr::unchecked("core")) .unwrap(); - crate::DENOM + DENOM .save(deps.as_mut().storage, &String::from("denom")) .unwrap(); - let error = crate::execute( + let error = crate::contract::execute( deps.as_mut(), mock_env(), mock_info("core", &[]), - crate::ExecuteMsg::Burn {}, + ExecuteMsg::Burn {}, ) .unwrap_err(); assert_eq!( error, - crate::ContractError::PaymentError(cw_utils::PaymentError::NoFunds {}) + ContractError::PaymentError(cw_utils::PaymentError::NoFunds {}) ); } #[test] fn burn_multiple_coins() { let mut deps = mock_dependencies::(); - crate::CORE + CORE_ADDRESS .save(deps.as_mut().storage, &Addr::unchecked("core")) .unwrap(); - crate::DENOM + DENOM .save(deps.as_mut().storage, &String::from("denom")) .unwrap(); - let error = crate::execute( + let error = crate::contract::execute( deps.as_mut(), mock_env(), mock_info("core", &[coin(20, "coin1"), coin(10, "denom")]), - crate::ExecuteMsg::Burn {}, + ExecuteMsg::Burn {}, ) .unwrap_err(); assert_eq!( error, - crate::ContractError::PaymentError(cw_utils::PaymentError::MultipleDenoms {}) + ContractError::PaymentError(cw_utils::PaymentError::MultipleDenoms {}) ); } #[test] fn burn_invalid_coin() { let mut deps = mock_dependencies::(); - crate::CORE + CORE_ADDRESS .save(deps.as_mut().storage, &Addr::unchecked("core")) .unwrap(); - crate::DENOM + DENOM .save(deps.as_mut().storage, &String::from("denom")) .unwrap(); - let error = crate::execute( + let error = crate::contract::execute( deps.as_mut(), mock_env(), mock_info("core", &[coin(20, "coin1")]), - crate::ExecuteMsg::Burn {}, + ExecuteMsg::Burn {}, ) .unwrap_err(); assert_eq!( error, - crate::ContractError::PaymentError(cw_utils::PaymentError::MissingDenom( - "denom".to_string() - )) + ContractError::PaymentError(cw_utils::PaymentError::MissingDenom("denom".to_string())) ); } #[test] fn burn() { let mut deps = mock_dependencies::(); - crate::CORE + CORE_ADDRESS .save(deps.as_mut().storage, &Addr::unchecked("core")) .unwrap(); - crate::DENOM + DENOM .save(deps.as_mut().storage, &String::from("denom")) .unwrap(); - let response = crate::execute( + let response = crate::contract::execute( deps.as_mut(), mock_env(), mock_info("core", &[coin(140, "denom")]), - crate::ExecuteMsg::Burn {}, + ExecuteMsg::Burn {}, ) .unwrap(); @@ -328,39 +335,39 @@ fn burn() { #[test] fn burn_stranger() { let mut deps = mock_dependencies::(); - crate::CORE + CORE_ADDRESS .save(deps.as_mut().storage, &Addr::unchecked("core")) .unwrap(); - crate::DENOM + DENOM .save(deps.as_mut().storage, &String::from("denom")) .unwrap(); - let error = crate::execute( + let error = crate::contract::execute( deps.as_mut(), mock_env(), mock_info("stranger", &[coin(160, "denom")]), - crate::ExecuteMsg::Burn {}, + ExecuteMsg::Burn {}, ) .unwrap_err(); - assert_eq!(error, crate::ContractError::Unauthorized); + assert_eq!(error, ContractError::Unauthorized); } #[test] fn query_config() { let mut deps = mock_dependencies::(); - crate::CORE + CORE_ADDRESS .save(deps.as_mut().storage, &Addr::unchecked("core")) .unwrap(); - crate::DENOM + DENOM .save(deps.as_mut().storage, &String::from("denom")) .unwrap(); - let response = crate::query(deps.as_ref(), mock_env(), crate::QueryMsg::Config {}).unwrap(); + let response = crate::contract::query(deps.as_ref(), mock_env(), QueryMsg::Config {}).unwrap(); assert_eq!( response, - to_json_binary(&crate::ConfigResponse { - core: "core".to_string(), + to_json_binary(&ConfigResponse { + core_address: "core".to_string(), denom: "denom".to_string() }) .unwrap() diff --git a/integration_tests/package.json b/integration_tests/package.json index 268fcf66..595c4109 100644 --- a/integration_tests/package.json +++ b/integration_tests/package.json @@ -6,6 +6,7 @@ "scripts": { "test": "vitest --run", "test:poc-stargate": "vitest --run poc-stargate --bail 1", + "test:core": "vitest --run core --bail 1", "test:interceptor": "vitest --run interceptor.test --bail 1", "test:interceptor-authz": "vitest --run interceptor-authz.test --bail 1", "test:validators-stats": "vitest --run validators-stats.test --bail 1", @@ -44,4 +45,4 @@ }, "description": "Lido on Cosmos integration test", "repository": "git@github.com:hadronlabs-org/lionco-integration-tests.git" -} +} \ No newline at end of file diff --git a/integration_tests/src/generated/contractLib/index.ts b/integration_tests/src/generated/contractLib/index.ts index 8072cc2d..2e9dd14d 100644 --- a/integration_tests/src/generated/contractLib/index.ts +++ b/integration_tests/src/generated/contractLib/index.ts @@ -1,23 +1,29 @@ -import * as _0 from './lidoDistribution'; -export const LidoDistribution = _0; +import * as _0 from './lidoCore'; +export const LidoCore = _0; -import * as _1 from './lidoInterchainInterceptorAuthz'; -export const LidoInterchainInterceptorAuthz = _1; +import * as _1 from './lidoDistribution'; +export const LidoDistribution = _1; -import * as _2 from './lidoInterchainInterceptor'; -export const LidoInterchainInterceptor = _2; +import * as _2 from './lidoFactory'; +export const LidoFactory = _2; -import * as _3 from './lidoStargatePoc'; -export const LidoStargatePoc = _3; +import * as _3 from './lidoInterchainInterceptorAuthz'; +export const LidoInterchainInterceptorAuthz = _3; -import * as _4 from './lidoStrategy'; -export const LidoStrategy = _4; +import * as _4 from './lidoInterchainInterceptor'; +export const LidoInterchainInterceptor = _4; -import * as _5 from './lidoToken'; -export const LidoToken = _5; +import * as _5 from './lidoStargatePoc'; +export const LidoStargatePoc = _5; -import * as _6 from './lidoValidatorsSet'; -export const LidoValidatorsSet = _6; +import * as _6 from './lidoStrategy'; +export const LidoStrategy = _6; -import * as _7 from './lidoValidatorsStats'; -export const LidoValidatorsStats = _7; +import * as _7 from './lidoToken'; +export const LidoToken = _7; + +import * as _8 from './lidoValidatorsSet'; +export const LidoValidatorsSet = _8; + +import * as _9 from './lidoValidatorsStats'; +export const LidoValidatorsStats = _9; diff --git a/integration_tests/src/generated/contractLib/lidoCore.ts b/integration_tests/src/generated/contractLib/lidoCore.ts new file mode 100644 index 00000000..a485013e --- /dev/null +++ b/integration_tests/src/generated/contractLib/lidoCore.ts @@ -0,0 +1,104 @@ +import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult, InstantiateResult } from "@cosmjs/cosmwasm-stargate"; +import { StdFee } from "@cosmjs/amino"; +import { Coin } from "@cosmjs/amino"; +export interface InstantiateMsg { + owner: string; + puppeteer_contract: string; + strategy_contract: string; + token_contract: string; +} +/** + * A fixed-point decimal value with 18 fractional digits, i.e. Decimal256(1_000_000_000_000_000_000) == 1.0 + * + * The greatest possible value that can be represented is 115792089237316195423570985008687907853269984665640564039457.584007913129639935 (which is (2^256 - 1) / 10^18) + */ +export type Decimal256 = string; +/** + * 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. + * + * # Examples + * + * Use `from` to create instances of this and `u128` to get the value out: + * + * ``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123); + * + * let b = Uint128::from(42u64); assert_eq!(b.u128(), 42); + * + * let c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ``` + */ +export type Uint128 = string; + +export interface LidoCoreSchema { + responses: Config | Decimal256; + execute: BondArgs | UnbondArgs | UpdateConfigArgs; + [k: string]: unknown; +} +export interface Config { + owner: string; + puppeteer_contract: string; + strategy_contract: string; + token_contract: string; +} +export interface BondArgs { + receiver?: string | null; +} +export interface UnbondArgs { + amount: Uint128; +} +export interface UpdateConfigArgs { + owner?: string | null; + puppeteer_contract?: string | null; + strategy_contract?: string | null; + token_contract?: string | null; +} + + +function isSigningCosmWasmClient( + client: CosmWasmClient | SigningCosmWasmClient +): client is SigningCosmWasmClient { + return 'execute' in client; +} + +export class Client { + private readonly client: CosmWasmClient | SigningCosmWasmClient; + contractAddress: string; + constructor(client: CosmWasmClient | SigningCosmWasmClient, contractAddress: string) { + this.client = client; + this.contractAddress = contractAddress; + } + mustBeSigningClient() { + return new Error("This client is not a SigningCosmWasmClient"); + } + static async instantiate( + client: SigningCosmWasmClient, + sender: string, + codeId: number, + initMsg: InstantiateMsg, + label: string, + initCoins?: readonly Coin[], + fees?: StdFee | 'auto' | number, + ): Promise { + const res = await client.instantiate(sender, codeId, initMsg, label, fees, { + ...(initCoins && initCoins.length && { funds: initCoins }), + }); + return res; + } + queryConfig = async(): Promise => { + return this.client.queryContractSmart(this.contractAddress, { config: {} }); + } + queryExchangeRate = async(): Promise => { + return this.client.queryContractSmart(this.contractAddress, { exchange_rate: {} }); + } + bond = async(sender:string, args: BondArgs, fee?: number | StdFee | "auto", memo?: string, funds?: Coin[]): Promise => { + if (!isSigningCosmWasmClient(this.client)) { throw this.mustBeSigningClient(); } + return this.client.execute(sender, this.contractAddress, { bond: args }, fee || "auto", memo, funds); + } + unbond = async(sender:string, args: UnbondArgs, fee?: number | StdFee | "auto", memo?: string, funds?: Coin[]): Promise => { + if (!isSigningCosmWasmClient(this.client)) { throw this.mustBeSigningClient(); } + return this.client.execute(sender, this.contractAddress, { unbond: args }, fee || "auto", memo, funds); + } + updateConfig = async(sender:string, args: UpdateConfigArgs, fee?: number | StdFee | "auto", memo?: string, funds?: Coin[]): Promise => { + if (!isSigningCosmWasmClient(this.client)) { throw this.mustBeSigningClient(); } + return this.client.execute(sender, this.contractAddress, { update_config: args }, fee || "auto", memo, funds); + } +} diff --git a/integration_tests/src/generated/contractLib/lidoFactory.ts b/integration_tests/src/generated/contractLib/lidoFactory.ts new file mode 100644 index 00000000..a3820053 --- /dev/null +++ b/integration_tests/src/generated/contractLib/lidoFactory.ts @@ -0,0 +1,57 @@ +import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult, InstantiateResult } from "@cosmjs/cosmwasm-stargate"; +import { StdFee } from "@cosmjs/amino"; +import { Coin } from "@cosmjs/amino"; +export interface InstantiateMsg { + core_code_id: number; + salt: string; + subdenom: string; + token_code_id: number; +} +export interface LidoFactorySchema { + responses: State; + [k: string]: unknown; +} +export interface State { + core_contract: string; + token_contract: string; +} + + +function isSigningCosmWasmClient( + client: CosmWasmClient | SigningCosmWasmClient +): client is SigningCosmWasmClient { + return 'execute' in client; +} + +export class Client { + private readonly client: CosmWasmClient | SigningCosmWasmClient; + contractAddress: string; + constructor(client: CosmWasmClient | SigningCosmWasmClient, contractAddress: string) { + this.client = client; + this.contractAddress = contractAddress; + } + mustBeSigningClient() { + return new Error("This client is not a SigningCosmWasmClient"); + } + static async instantiate( + client: SigningCosmWasmClient, + sender: string, + codeId: number, + initMsg: InstantiateMsg, + label: string, + initCoins?: readonly Coin[], + fees?: StdFee | 'auto' | number, + ): Promise { + const res = await client.instantiate(sender, codeId, initMsg, label, fees, { + ...(initCoins && initCoins.length && { funds: initCoins }), + }); + return res; + } + queryState = async(): Promise => { + return this.client.queryContractSmart(this.contractAddress, { state: {} }); + } + init = async(sender: string, fee?: number | StdFee | "auto", memo?: string, funds?: Coin[]): Promise => { + if (!isSigningCosmWasmClient(this.client)) { throw this.mustBeSigningClient(); } + return this.client.execute(sender, this.contractAddress, { init: {} }, fee || "auto", memo, funds); + } +} diff --git a/integration_tests/src/generated/contractLib/lidoToken.ts b/integration_tests/src/generated/contractLib/lidoToken.ts index 0f79eea3..fe58f8a3 100644 --- a/integration_tests/src/generated/contractLib/lidoToken.ts +++ b/integration_tests/src/generated/contractLib/lidoToken.ts @@ -2,7 +2,7 @@ import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult, InstantiateResult import { StdFee } from "@cosmjs/amino"; import { Coin } from "@cosmjs/amino"; export interface InstantiateMsg { - core: string; + core_address: string; subdenom: string; } /** @@ -26,7 +26,7 @@ export interface LidoTokenSchema { [k: string]: unknown; } export interface ConfigResponse { - core: string; + core_address: string; denom: string; } export interface MintArgs { diff --git a/integration_tests/src/testcases/core.test.ts b/integration_tests/src/testcases/core.test.ts new file mode 100644 index 00000000..afbd281d --- /dev/null +++ b/integration_tests/src/testcases/core.test.ts @@ -0,0 +1,299 @@ +import { describe, expect, it, beforeAll, afterAll } from 'vitest'; +import { LidoCore, LidoFactory } from '../generated/contractLib'; +import { + QueryClient, + StakingExtension, + BankExtension, + setupStakingExtension, + setupBankExtension, + SigningStargateClient, +} from '@cosmjs/stargate'; +import { MsgTransfer } from 'cosmjs-types/ibc/applications/transfer/v1/tx'; +import { join } from 'path'; +import { Tendermint34Client } from '@cosmjs/tendermint-rpc'; +import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'; +import { Client as NeutronClient } from '@neutron-org/client-ts'; +import { AccountData, DirectSecp256k1HdWallet } from '@cosmjs/proto-signing'; +import { GasPrice } from '@cosmjs/stargate'; +import { awaitBlocks, setupPark } from '../testSuite'; +import fs from 'fs'; +import Cosmopark from '@neutron-org/cosmopark'; +import { waitFor } from '../helpers/waitFor'; + +const LidoFactoryClass = LidoFactory.Client; +const LidoCoreClass = LidoCore.Client; + +describe('Core', () => { + const context: { + park?: Cosmopark; + contractAddress?: string; + wallet?: DirectSecp256k1HdWallet; + gaiaWallet?: DirectSecp256k1HdWallet; + contractClient?: InstanceType; + coreContractClient?: InstanceType; + account?: AccountData; + icaAddress?: string; + client?: SigningCosmWasmClient; + gaiaClient?: SigningStargateClient; + gaiaUserAddress?: string; + gaiaQueryClient?: QueryClient & StakingExtension & BankExtension; + neutronClient?: InstanceType; + neutronUserAddress?: string; + neutronSecondUserAddress?: string; + validatorAddress?: string; + secondValidatorAddress?: string; + tokenizedDenomOnNeutron?: string; + coreCoreId?: number; + tokenCodeId?: number; + exchangeRate?: number; + tokenContractAddress?: string; + neutronIBCDenom?: string; + } = {}; + + beforeAll(async () => { + context.park = await setupPark('core', ['neutron', 'gaia'], true); + context.wallet = await DirectSecp256k1HdWallet.fromMnemonic( + context.park.config.wallets.demowallet1.mnemonic, + { + prefix: 'neutron', + }, + ); + context.gaiaWallet = await DirectSecp256k1HdWallet.fromMnemonic( + context.park.config.wallets.demowallet1.mnemonic, + { + prefix: 'cosmos', + }, + ); + context.account = (await context.wallet.getAccounts())[0]; + context.neutronClient = new NeutronClient({ + apiURL: `http://127.0.0.1:${context.park.ports.neutron.rest}`, + rpcURL: `127.0.0.1:${context.park.ports.neutron.rpc}`, + prefix: 'neutron', + }); + context.client = await SigningCosmWasmClient.connectWithSigner( + `http://127.0.0.1:${context.park.ports.neutron.rpc}`, + context.wallet, + { + gasPrice: GasPrice.fromString('0.025untrn'), + }, + ); + context.gaiaClient = await SigningStargateClient.connectWithSigner( + `http://127.0.0.1:${context.park.ports.gaia.rpc}`, + context.gaiaWallet, + { + gasPrice: GasPrice.fromString('0.025stake'), + }, + ); + const tmClient = await Tendermint34Client.connect( + `http://127.0.0.1:${context.park.ports.gaia.rpc}`, + ); + context.gaiaQueryClient = QueryClient.withExtensions( + tmClient, + setupStakingExtension, + setupBankExtension, + ); + + const secondWallet = await DirectSecp256k1HdWallet.fromMnemonic( + context.park.config.wallets.demo2.mnemonic, + { + prefix: 'neutron', + }, + ); + context.neutronSecondUserAddress = ( + await secondWallet.getAccounts() + )[0].address; + }); + + afterAll(async () => { + await context.park.stop(); + }); + + it('instantiate', async () => { + const { client, account } = context; + + { + const res = await client.upload( + account.address, + fs.readFileSync(join(__dirname, '../../../artifacts/lido_core.wasm')), + 1.5, + ); + expect(res.codeId).toBeGreaterThan(0); + context.coreCoreId = res.codeId; + } + { + const res = await client.upload( + account.address, + fs.readFileSync(join(__dirname, '../../../artifacts/lido_token.wasm')), + 1.5, + ); + expect(res.codeId).toBeGreaterThan(0); + context.tokenCodeId = res.codeId; + } + const res = await client.upload( + account.address, + fs.readFileSync(join(__dirname, '../../../artifacts/lido_factory.wasm')), + 1.5, + ); + expect(res.codeId).toBeGreaterThan(0); + const instantiateRes = await LidoFactory.Client.instantiate( + client, + account.address, + res.codeId, + { + core_code_id: context.coreCoreId, + token_code_id: context.tokenCodeId, + salt: 'salt', + subdenom: 'lido', + }, + 'label', + [], + 'auto', + ); + expect(instantiateRes.contractAddress).toHaveLength(66); + context.contractAddress = instantiateRes.contractAddress; + context.contractClient = new LidoFactory.Client( + client, + context.contractAddress, + ); + context.gaiaUserAddress = ( + await context.gaiaWallet.getAccounts() + )[0].address; + context.neutronUserAddress = ( + await context.wallet.getAccounts() + )[0].address; + }); + it('init', async () => { + const { contractClient } = context; + const res = await contractClient.init(context.neutronUserAddress); + expect(res.transactionHash).toHaveLength(64); + }); + it('query factory state', async () => { + const { contractClient, neutronClient } = context; + const res = await contractClient.queryState(); + expect(res).toBeTruthy(); + const tokenContractInfo = + await neutronClient.CosmwasmWasmV1.query.queryContractInfo( + res.token_contract, + ); + expect(tokenContractInfo.data.contract_info.label).toBe('token'); + const coreContractInfo = + await neutronClient.CosmwasmWasmV1.query.queryContractInfo( + res.core_contract, + ); + expect(coreContractInfo.data.contract_info.label).toBe('core'); + context.coreContractClient = new LidoCore.Client( + context.client, + res.core_contract, + ); + context.tokenContractAddress = res.token_contract; + }); + it('query exchange rate', async () => { + const { coreContractClient } = context; + context.exchangeRate = parseFloat( + await coreContractClient.queryExchangeRate(), + ); + expect(context.exchangeRate).toBeGreaterThan(1); + }); + it('transfer tokens to neutron', async () => { + const { gaiaClient, gaiaUserAddress, neutronUserAddress, neutronClient } = + context; + const res = await gaiaClient.signAndBroadcast( + gaiaUserAddress, + [ + { + typeUrl: '/ibc.applications.transfer.v1.MsgTransfer', + value: MsgTransfer.fromPartial({ + sender: gaiaUserAddress, + sourceChannel: 'channel-0', + sourcePort: 'transfer', + receiver: neutronUserAddress, + token: { denom: 'stake', amount: '1000000' }, + timeoutTimestamp: BigInt((Date.now() + 10 * 60 * 1000) * 1e6), + timeoutHeight: { + revisionHeight: BigInt(0), + revisionNumber: BigInt(0), + }, + }), + }, + ], + 1.5, + ); + expect(res.transactionHash).toHaveLength(64); + await waitFor(async () => { + const balances = + await neutronClient.CosmosBankV1Beta1.query.queryAllBalances( + neutronUserAddress, + ); + context.neutronIBCDenom = balances.data.balances.find((b) => + b.denom.startsWith('ibc/'), + )?.denom; + return balances.data.balances.length > 1; + }); + expect(context.neutronIBCDenom).toBeTruthy(); + }); + it('bond w/o receiver', async () => { + const { + coreContractClient, + neutronClient, + neutronUserAddress, + neutronIBCDenom, + } = context; + const res = await coreContractClient.bond( + neutronUserAddress, + {}, + 1.6, + undefined, + [ + { + amount: '500000', + denom: neutronIBCDenom, + }, + ], + ); + expect(res.transactionHash).toHaveLength(64); + await awaitBlocks(`http://127.0.0.1:${context.park.ports.gaia.rpc}`, 1); + const balances = + await neutronClient.CosmosBankV1Beta1.query.queryAllBalances( + neutronUserAddress, + ); + expect( + balances.data.balances.find((one) => one.denom.startsWith('factory')), + ).toEqual({ + denom: `factory/${context.tokenContractAddress}/lido`, + amount: String(500_000 * context.exchangeRate), + }); + }); + it('bond with receiver', async () => { + const { + coreContractClient, + neutronClient, + neutronUserAddress, + neutronIBCDenom, + neutronSecondUserAddress, + } = context; + const res = await coreContractClient.bond( + neutronUserAddress, + { receiver: neutronSecondUserAddress }, + 1.6, + undefined, + [ + { + amount: '500000', + denom: neutronIBCDenom, + }, + ], + ); + expect(res.transactionHash).toHaveLength(64); + await awaitBlocks(`http://127.0.0.1:${context.park.ports.gaia.rpc}`, 1); + const balances = + await neutronClient.CosmosBankV1Beta1.query.queryAllBalances( + neutronSecondUserAddress, + ); + expect( + balances.data.balances.find((one) => one.denom.startsWith('factory')), + ).toEqual({ + denom: `factory/${context.tokenContractAddress}/lido`, + amount: String(500_000 * context.exchangeRate), + }); + }); +}); diff --git a/integration_tests/src/testcases/interceptor-authz.test.ts b/integration_tests/src/testcases/interceptor-authz.test.ts index a497cb00..9c3bffa9 100644 --- a/integration_tests/src/testcases/interceptor-authz.test.ts +++ b/integration_tests/src/testcases/interceptor-authz.test.ts @@ -49,7 +49,7 @@ describe('Interchain interceptor with AuthZ support', () => { beforeAll(async () => { context.park = await setupPark( - 'interceptor-authz', + 'interceptorauthz', ['neutron', 'gaia'], true, ); diff --git a/packages/base/Cargo.toml b/packages/base/Cargo.toml new file mode 100644 index 00000000..206dfff3 --- /dev/null +++ b/packages/base/Cargo.toml @@ -0,0 +1,33 @@ +[package] +authors = ["Sergey Ratiashvili "] +description = "Package for Lido Interchain Interceptor Base" +edition = "2021" +name = "lido-staking-base" +version = "1.0.0" + +exclude = [ + # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. + "contract.wasm", + "hash.txt", +] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[dependencies] +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +neutron-sdk = { workspace = true } + +[dev-dependencies] +cosmwasm-storage = { workspace = true } +cw-multi-test = { workspace = true } diff --git a/packages/base/src/helpers/answer.rs b/packages/base/src/helpers/answer.rs new file mode 100644 index 00000000..5a62582f --- /dev/null +++ b/packages/base/src/helpers/answer.rs @@ -0,0 +1,18 @@ +use cosmwasm_std::{attr, Attribute, Event, Response}; +use neutron_sdk::bindings::msg::NeutronMsg; + +pub fn response>( + ty: &str, + contract_name: &str, + attrs: impl IntoIterator, +) -> Response { + Response::new().add_event(Event::new(format!("{}-{}", contract_name, ty)).add_attributes(attrs)) +} + +pub fn attr_coin( + key: impl Into, + amount: impl std::fmt::Display, + denom: impl std::fmt::Display, +) -> Attribute { + attr(key, format!("{}{}", amount, denom)) +} diff --git a/packages/base/src/helpers/mod.rs b/packages/base/src/helpers/mod.rs new file mode 100644 index 00000000..7813b98c --- /dev/null +++ b/packages/base/src/helpers/mod.rs @@ -0,0 +1 @@ +pub mod answer; diff --git a/packages/base/src/lib.rs b/packages/base/src/lib.rs new file mode 100644 index 00000000..a801d48d --- /dev/null +++ b/packages/base/src/lib.rs @@ -0,0 +1,3 @@ +pub mod helpers; +pub mod msg; +pub mod state; diff --git a/packages/base/src/msg/core.rs b/packages/base/src/msg/core.rs new file mode 100644 index 00000000..77f7cced --- /dev/null +++ b/packages/base/src/msg/core.rs @@ -0,0 +1,51 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{Decimal256, Uint128}; + +use crate::state::core::Config; + +#[cw_serde] +pub struct InstantiateMsg { + pub token_contract: String, + pub puppeteer_contract: String, + pub strategy_contract: String, + pub owner: String, +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(Config)] + Config {}, + #[returns(Decimal256)] + ExchangeRate {}, +} + +#[cw_serde] +pub enum ExecuteMsg { + Bond { + receiver: Option, + }, + Unbond { + amount: Uint128, + }, + //permissioned + UpdateConfig { + token_contract: Option, + puppeteer_contract: Option, + strategy_contract: Option, + owner: Option, + }, +} +#[cw_serde] +pub enum MigrateMsg {} + +impl From for Config { + fn from(val: InstantiateMsg) -> Self { + Config { + token_contract: val.token_contract, + puppeteer_contract: val.puppeteer_contract, + strategy_contract: val.strategy_contract, + owner: val.owner, + } + } +} diff --git a/packages/base/src/msg/mod.rs b/packages/base/src/msg/mod.rs new file mode 100644 index 00000000..fc69d71e --- /dev/null +++ b/packages/base/src/msg/mod.rs @@ -0,0 +1,2 @@ +pub mod core; +pub mod token; diff --git a/packages/base/src/msg/token.rs b/packages/base/src/msg/token.rs new file mode 100644 index 00000000..3aea5c9e --- /dev/null +++ b/packages/base/src/msg/token.rs @@ -0,0 +1,29 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::Uint128; + +#[cosmwasm_schema::cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(ConfigResponse)] + Config {}, +} + +#[cosmwasm_schema::cw_serde] +pub struct ConfigResponse { + pub core_address: String, + pub denom: String, +} + +#[cw_serde] +pub enum ExecuteMsg { + Mint { amount: Uint128, receiver: String }, + Burn {}, +} +#[cw_serde] +pub struct InstantiateMsg { + pub core_address: String, + pub subdenom: String, +} + +#[cosmwasm_schema::cw_serde] +pub enum MigrateMsg {} diff --git a/packages/base/src/state/core.rs b/packages/base/src/state/core.rs new file mode 100644 index 00000000..4b978bc5 --- /dev/null +++ b/packages/base/src/state/core.rs @@ -0,0 +1,12 @@ +use cosmwasm_schema::cw_serde; +use cw_storage_plus::Item; + +#[cw_serde] +pub struct Config { + pub token_contract: String, + pub puppeteer_contract: String, + pub strategy_contract: String, + pub owner: String, +} + +pub const CONFIG: Item = Item::new("config"); diff --git a/packages/base/src/state/mod.rs b/packages/base/src/state/mod.rs new file mode 100644 index 00000000..fc69d71e --- /dev/null +++ b/packages/base/src/state/mod.rs @@ -0,0 +1,2 @@ +pub mod core; +pub mod token; diff --git a/packages/base/src/state/token.rs b/packages/base/src/state/token.rs new file mode 100644 index 00000000..838e2a48 --- /dev/null +++ b/packages/base/src/state/token.rs @@ -0,0 +1,5 @@ +use cosmwasm_std::Addr; +use cw_storage_plus::Item; + +pub const CORE_ADDRESS: Item = Item::new("core"); +pub const DENOM: Item = Item::new("denom");