From 41ad940b0b1f6ec13c8dabd72049e79f3290e4df Mon Sep 17 00:00:00 2001 From: Sergey Ratiashvili Date: Wed, 13 Dec 2023 14:34:30 +0100 Subject: [PATCH 01/13] feat: bond --- Cargo.lock | 38 +++- Cargo.toml | 3 + contracts/core/Cargo.toml | 47 +++++ contracts/core/README.md | 1 + contracts/core/src/contract.rs | 166 ++++++++++++++++++ contracts/core/src/error.rs | 23 +++ contracts/core/src/lib.rs | 4 + contracts/core/src/msg.rs | 38 ++++ contracts/core/src/state.rs | 22 +++ .../interchain-interceptor-authz/Cargo.toml | 34 ++-- contracts/token/Cargo.toml | 17 +- contracts/token/src/bin/lido-token-schema.rs | 5 +- contracts/token/src/lib.rs | 13 +- contracts/token/src/tests.rs | 16 +- packages/base/Cargo.toml | 32 ++++ packages/base/src/lib.rs | 1 + packages/base/src/msg.rs | 8 + 17 files changed, 423 insertions(+), 45 deletions(-) create mode 100644 contracts/core/Cargo.toml create mode 100644 contracts/core/README.md create mode 100644 contracts/core/src/contract.rs create mode 100644 contracts/core/src/error.rs create mode 100644 contracts/core/src/lib.rs create mode 100644 contracts/core/src/msg.rs create mode 100644 contracts/core/src/state.rs create mode 100644 packages/base/Cargo.toml create mode 100644 packages/base/src/lib.rs create mode 100644 packages/base/src/msg.rs diff --git a/Cargo.lock b/Cargo.lock index 777e3011..57e8b203 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" @@ -675,7 +700,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 +740,16 @@ dependencies = [ "thiserror", ] +[[package]] +name = "lido-staking-base" +version = "1.0.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-storage", + "cw-multi-test 0.20.0", +] + [[package]] name = "lido-stargate-poc" version = "1.0.0" @@ -771,6 +806,7 @@ dependencies = [ "cw-storage-plus", "cw-utils", "cw2", + "lido-staking-base", "neutron-sdk", "thiserror", ] diff --git a/Cargo.toml b/Cargo.toml index 46bb87bf..cf64559d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,9 @@ members = [ "contracts/token", "contracts/validators-set", "contracts/strategy", + "contracts/core", "packages/interchain-interceptor-base", + "packages/base", ] resolver = "2" @@ -40,6 +42,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/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/contract.rs b/contracts/core/src/contract.rs new file mode 100644 index 00000000..ae42fbc3 --- /dev/null +++ b/contracts/core/src/contract.rs @@ -0,0 +1,166 @@ +use std::str::FromStr; + +use cosmwasm_std::{ + attr, ensure_eq, ensure_ne, entry_point, to_json_binary, Binary, CosmosMsg, Decimal, Deps, + DepsMut, Env, MessageInfo, Response, StdResult, Uint128, WasmMsg, +}; + +use cw2::set_contract_version; +use lido_staking_base::msg::TokenExecuteMsg; +use neutron_sdk::{ + bindings::{msg::NeutronMsg, query::NeutronQuery}, + NeutronResult, +}; + +use crate::{ + error::{ContractError, ContractResult}, + msg::{ExecuteMsg, InstantiateMsg}, + state::{QueryMsg, CONFIG}, +}; +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, +) -> NeutronResult { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + CONFIG.save(deps.storage, &msg.into())?; + Ok(Response::default()) +} + +#[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 {} => execute_bond(deps, env, info), + 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, +) -> 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 msgs = vec![CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: config.token_contract, + msg: to_json_binary(&TokenExecuteMsg::Mint { + amount: issue_amount, + receiver: info.sender.to_string(), + })?, + funds: vec![], + })]; + + Ok(Response::default().add_messages(msgs).add_attributes(attrs)) +} + +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::default().add_attributes(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..a5abdbb0 --- /dev/null +++ b/contracts/core/src/lib.rs @@ -0,0 +1,4 @@ +pub mod contract; +pub mod error; +pub mod msg; +pub mod state; diff --git a/contracts/core/src/msg.rs b/contracts/core/src/msg.rs new file mode 100644 index 00000000..073f075e --- /dev/null +++ b/contracts/core/src/msg.rs @@ -0,0 +1,38 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::Uint128; + +use crate::state::Config; + +#[cw_serde] +pub struct InstantiateMsg { + pub token_contract: String, + pub puppeteer_contract: String, + pub strategy_contract: String, + pub owner: String, +} + +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, + } + } +} + +#[cw_serde] +pub enum ExecuteMsg { + Bond {}, + Unbond { + amount: Uint128, + }, + //permissioned + UpdateConfig { + token_contract: Option, + puppeteer_contract: Option, + strategy_contract: Option, + owner: Option, + }, +} diff --git a/contracts/core/src/state.rs b/contracts/core/src/state.rs new file mode 100644 index 00000000..44653773 --- /dev/null +++ b/contracts/core/src/state.rs @@ -0,0 +1,22 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::Decimal256; +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, +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(Config)] + Config {}, + #[returns(Decimal256)] + ExchangeRate {}, +} + +pub const CONFIG: Item = Item::new("config"); 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..37f97853 100644 --- a/contracts/token/src/bin/lido-token-schema.rs +++ b/contracts/token/src/bin/lido-token-schema.rs @@ -1,11 +1,12 @@ use cosmwasm_schema::write_api; -use lido_token::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; +use lido_staking_base::msg::TokenExecuteMsg; +use lido_token::{InstantiateMsg, MigrateMsg, QueryMsg}; fn main() { write_api! { instantiate: InstantiateMsg, query: QueryMsg, - execute: ExecuteMsg, + execute: TokenExecuteMsg, migrate: MigrateMsg } } diff --git a/contracts/token/src/lib.rs b/contracts/token/src/lib.rs index f7c6768f..1aff4c71 100644 --- a/contracts/token/src/lib.rs +++ b/contracts/token/src/lib.rs @@ -3,6 +3,7 @@ use cosmwasm_std::{ MessageInfo, Reply, Response, StdError, SubMsg, Uint128, }; use cw_storage_plus::Item; +use lido_staking_base::msg::TokenExecuteMsg; use neutron_sdk::{ bindings::{msg::NeutronMsg, query::NeutronQuery}, query::token_factory::query_full_denom, @@ -40,12 +41,6 @@ pub struct InstantiateMsg { 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 { @@ -115,14 +110,14 @@ pub fn execute( deps: DepsMut, _env: Env, info: MessageInfo, - msg: ExecuteMsg, + msg: TokenExecuteMsg, ) -> 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), + TokenExecuteMsg::Mint { amount, receiver } => mint(deps, amount, receiver), + TokenExecuteMsg::Burn {} => burn(deps, info), } } diff --git a/contracts/token/src/tests.rs b/contracts/token/src/tests.rs index 1e9eab52..ca7029e3 100644 --- a/contracts/token/src/tests.rs +++ b/contracts/token/src/tests.rs @@ -149,7 +149,7 @@ fn mint_zero() { deps.as_mut(), mock_env(), mock_info("core", &[]), - crate::ExecuteMsg::Mint { + crate::TokenExecuteMsg::Mint { amount: Uint128::zero(), receiver: "receiver".to_string(), }, @@ -172,7 +172,7 @@ fn mint() { deps.as_mut(), mock_env(), mock_info("core", &[]), - crate::ExecuteMsg::Mint { + crate::TokenExecuteMsg::Mint { amount: Uint128::new(220), receiver: "receiver".to_string(), }, @@ -210,7 +210,7 @@ fn mint_stranger() { deps.as_mut(), mock_env(), mock_info("stranger", &[]), - crate::ExecuteMsg::Mint { + crate::TokenExecuteMsg::Mint { amount: Uint128::new(220), receiver: "receiver".to_string(), }, @@ -234,7 +234,7 @@ fn burn_zero() { deps.as_mut(), mock_env(), mock_info("core", &[]), - crate::ExecuteMsg::Burn {}, + crate::TokenExecuteMsg::Burn {}, ) .unwrap_err(); assert_eq!( @@ -257,7 +257,7 @@ fn burn_multiple_coins() { deps.as_mut(), mock_env(), mock_info("core", &[coin(20, "coin1"), coin(10, "denom")]), - crate::ExecuteMsg::Burn {}, + crate::TokenExecuteMsg::Burn {}, ) .unwrap_err(); assert_eq!( @@ -280,7 +280,7 @@ fn burn_invalid_coin() { deps.as_mut(), mock_env(), mock_info("core", &[coin(20, "coin1")]), - crate::ExecuteMsg::Burn {}, + crate::TokenExecuteMsg::Burn {}, ) .unwrap_err(); assert_eq!( @@ -305,7 +305,7 @@ fn burn() { deps.as_mut(), mock_env(), mock_info("core", &[coin(140, "denom")]), - crate::ExecuteMsg::Burn {}, + crate::TokenExecuteMsg::Burn {}, ) .unwrap(); @@ -339,7 +339,7 @@ fn burn_stranger() { deps.as_mut(), mock_env(), mock_info("stranger", &[coin(160, "denom")]), - crate::ExecuteMsg::Burn {}, + crate::TokenExecuteMsg::Burn {}, ) .unwrap_err(); diff --git a/packages/base/Cargo.toml b/packages/base/Cargo.toml new file mode 100644 index 00000000..e1e18bc5 --- /dev/null +++ b/packages/base/Cargo.toml @@ -0,0 +1,32 @@ +[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 } + + +[dev-dependencies] +cosmwasm-storage = { workspace = true } +cw-multi-test = { workspace = true } diff --git a/packages/base/src/lib.rs b/packages/base/src/lib.rs new file mode 100644 index 00000000..d0e87a0d --- /dev/null +++ b/packages/base/src/lib.rs @@ -0,0 +1 @@ +pub mod msg; diff --git a/packages/base/src/msg.rs b/packages/base/src/msg.rs new file mode 100644 index 00000000..d9704446 --- /dev/null +++ b/packages/base/src/msg.rs @@ -0,0 +1,8 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::Uint128; + +#[cw_serde] +pub enum TokenExecuteMsg { + Mint { amount: Uint128, receiver: String }, + Burn {}, +} From 43e91edd23b2973aae720e76284a07f5241e27d9 Mon Sep 17 00:00:00 2001 From: Sergey Ratiashvili Date: Wed, 13 Dec 2023 20:04:12 +0100 Subject: [PATCH 02/13] feat: refactor and schemas --- Cargo.lock | 25 ++++ Cargo.toml | 2 + contracts/core/.cargo/config | 2 + contracts/core/src/bin/lido-core-schema.rs | 15 +++ contracts/core/src/contract.rs | 6 +- contracts/core/src/msg.rs | 15 +-- contracts/factory/.cargo/config | 2 + contracts/factory/Cargo.toml | 47 ++++++++ contracts/factory/README.md | 1 + .../factory/src/bin/lido-factory-schema.rs | 11 ++ contracts/factory/src/contract.rs | 107 ++++++++++++++++++ contracts/factory/src/error.rs | 11 ++ contracts/factory/src/lib.rs | 4 + contracts/factory/src/msg.rs | 25 ++++ contracts/factory/src/state.rs | 20 ++++ contracts/token/src/bin/lido-token-schema.rs | 6 +- contracts/token/src/lib.rs | 29 +++-- contracts/token/src/tests.rs | 29 ++--- packages/base/src/msg.rs | 13 +++ 19 files changed, 324 insertions(+), 46 deletions(-) create mode 100644 contracts/core/.cargo/config create mode 100644 contracts/core/src/bin/lido-core-schema.rs create mode 100644 contracts/factory/.cargo/config create mode 100644 contracts/factory/Cargo.toml create mode 100644 contracts/factory/README.md create mode 100644 contracts/factory/src/bin/lido-factory-schema.rs create mode 100644 contracts/factory/src/contract.rs create mode 100644 contracts/factory/src/error.rs create mode 100644 contracts/factory/src/lib.rs create mode 100644 contracts/factory/src/msg.rs create mode 100644 contracts/factory/src/state.rs diff --git a/Cargo.lock b/Cargo.lock index 57e8b203..bb176c3f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -667,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" diff --git a/Cargo.toml b/Cargo.toml index cf64559d..8d8a1c1c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ members = [ "contracts/stargate-poc", "contracts/distribution", + "contracts/factory", "contracts/interchain-interceptor", "contracts/interchain-interceptor-authz", "contracts/validators-stats", @@ -27,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 } 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/src/bin/lido-core-schema.rs b/contracts/core/src/bin/lido-core-schema.rs new file mode 100644 index 00000000..7bc67351 --- /dev/null +++ b/contracts/core/src/bin/lido-core-schema.rs @@ -0,0 +1,15 @@ +use cosmwasm_schema::write_api; +use lido_core::{ + msg::{ExecuteMsg, MigrateMsg}, + state::QueryMsg, +}; +use lido_staking_base::msg::CoreInstantiateMsg; + +fn main() { + write_api! { + instantiate: CoreInstantiateMsg, + query: QueryMsg, + execute: ExecuteMsg, + migrate: MigrateMsg + } +} diff --git a/contracts/core/src/contract.rs b/contracts/core/src/contract.rs index ae42fbc3..5590cd42 100644 --- a/contracts/core/src/contract.rs +++ b/contracts/core/src/contract.rs @@ -6,7 +6,7 @@ use cosmwasm_std::{ }; use cw2::set_contract_version; -use lido_staking_base::msg::TokenExecuteMsg; +use lido_staking_base::msg::{CoreInstantiateMsg, TokenExecuteMsg}; use neutron_sdk::{ bindings::{msg::NeutronMsg, query::NeutronQuery}, NeutronResult, @@ -14,7 +14,7 @@ use neutron_sdk::{ use crate::{ error::{ContractError, ContractResult}, - msg::{ExecuteMsg, InstantiateMsg}, + msg::ExecuteMsg, state::{QueryMsg, CONFIG}, }; const CONTRACT_NAME: &str = concat!("crates.io:lido-neutron-contracts__", env!("CARGO_PKG_NAME")); @@ -25,7 +25,7 @@ pub fn instantiate( deps: DepsMut, _env: Env, _info: MessageInfo, - msg: InstantiateMsg, + msg: CoreInstantiateMsg, ) -> NeutronResult { set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; diff --git a/contracts/core/src/msg.rs b/contracts/core/src/msg.rs index 073f075e..2306edaf 100644 --- a/contracts/core/src/msg.rs +++ b/contracts/core/src/msg.rs @@ -1,18 +1,11 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::Uint128; +use lido_staking_base::msg::CoreInstantiateMsg; use crate::state::Config; -#[cw_serde] -pub struct InstantiateMsg { - pub token_contract: String, - pub puppeteer_contract: String, - pub strategy_contract: String, - pub owner: String, -} - -impl From for Config { - fn from(val: InstantiateMsg) -> Self { +impl From for Config { + fn from(val: CoreInstantiateMsg) -> Self { Config { token_contract: val.token_contract, puppeteer_contract: val.puppeteer_contract, @@ -36,3 +29,5 @@ pub enum ExecuteMsg { owner: Option, }, } +#[cw_serde] +pub enum MigrateMsg {} 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..55b468b8 --- /dev/null +++ b/contracts/factory/src/contract.rs @@ -0,0 +1,107 @@ +use crate::{ + msg::{ExecuteMsg, InstantiateMsg}, + 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, StdError, WasmMsg, +}; +use cw2::set_contract_version; +use lido_staking_base::msg::{CoreInstantiateMsg, TokenInstantiateMsg}; +use neutron_sdk::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, +) -> NeutronResult { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + CONFIG.save( + deps.storage, + &Config { + salt: msg.salt, + token_code_id: msg.token_code_id, + core_code_id: msg.core_code_id, + owner: info.sender.to_string(), + subdenom: msg.subdenom, + }, + )?; + Ok(Response::default()) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> NeutronResult { + match msg { + ExecuteMsg::Init {} => execute_init(deps, env, info), + } +} + +fn execute_init(deps: DepsMut, env: Env, _info: MessageInfo) -> NeutronResult { + 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.token_code_id)?; + let salt = config.salt.as_bytes(); + + let token_address = + instantiate2_address(&token_contract_checksum, &canonical_self_address, salt) + .map_err(|e| StdError::generic_err(format!("failed to calc token address: {e:?}")))?; + attrs.push(attr("token_address", token_address.to_string())); + let core_address = instantiate2_address(&core_contract_checksum, &canonical_self_address, salt) + .map_err(|e| StdError::generic_err(format!("failed to calc core address: {e:?}")))?; + 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.core_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.token_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::default().add_messages(msgs).add_attributes(attrs)) +} + +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..19407370 --- /dev/null +++ b/contracts/factory/src/error.rs @@ -0,0 +1,11 @@ +use cosmwasm_std::Instantiate2AddressError; + +pub enum ContractError { + Instantiate2Address { + err: Instantiate2AddressError, + code_id: u64, + }, + Unauthorized {}, + Unimplemented {}, + Unknown {}, +} 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/token/src/bin/lido-token-schema.rs b/contracts/token/src/bin/lido-token-schema.rs index 37f97853..d57efe2f 100644 --- a/contracts/token/src/bin/lido-token-schema.rs +++ b/contracts/token/src/bin/lido-token-schema.rs @@ -1,10 +1,10 @@ use cosmwasm_schema::write_api; -use lido_staking_base::msg::TokenExecuteMsg; -use lido_token::{InstantiateMsg, MigrateMsg, QueryMsg}; +use lido_staking_base::msg::{TokenExecuteMsg, TokenInstantiateMsg}; +use lido_token::{MigrateMsg, QueryMsg}; fn main() { write_api! { - instantiate: InstantiateMsg, + instantiate: TokenInstantiateMsg, query: QueryMsg, execute: TokenExecuteMsg, migrate: MigrateMsg diff --git a/contracts/token/src/lib.rs b/contracts/token/src/lib.rs index 1aff4c71..8a1f2a25 100644 --- a/contracts/token/src/lib.rs +++ b/contracts/token/src/lib.rs @@ -3,7 +3,7 @@ use cosmwasm_std::{ MessageInfo, Reply, Response, StdError, SubMsg, Uint128, }; use cw_storage_plus::Item; -use lido_staking_base::msg::TokenExecuteMsg; +use lido_staking_base::msg::{TokenExecuteMsg, TokenInstantiateMsg}; use neutron_sdk::{ bindings::{msg::NeutronMsg, query::NeutronQuery}, query::token_factory::query_full_denom, @@ -35,12 +35,6 @@ pub enum ContractError { pub type ContractResult = Result; -#[cosmwasm_schema::cw_serde] -pub struct InstantiateMsg { - pub core: String, - pub subdenom: String, -} - #[cosmwasm_schema::cw_serde] #[derive(cosmwasm_schema::QueryResponses)] pub enum QueryMsg { @@ -50,7 +44,7 @@ pub enum QueryMsg { #[cosmwasm_schema::cw_serde] pub struct ConfigResponse { - pub core: String, + pub core_address: String, pub denom: String, } @@ -60,7 +54,7 @@ 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 CORE_ADDRESS: Item = Item::new("core"); const DENOM: Item = Item::new("denom"); const CREATE_DENOM_REPLY_ID: u64 = 1; @@ -85,12 +79,12 @@ pub fn instantiate( deps: DepsMut, _env: Env, _info: MessageInfo, - msg: InstantiateMsg, + msg: TokenInstantiateMsg, ) -> ContractResult> { cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - let core = deps.api.addr_validate(&msg.core)?; - CORE.save(deps.storage, &core)?; + 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( @@ -100,7 +94,7 @@ pub fn instantiate( Ok(response( "instantiate", - [attr("core", core), attr("subdenom", msg.subdenom)], + [attr("core_address", core), attr("subdenom", msg.subdenom)], ) .add_submessage(create_denom_msg)) } @@ -112,7 +106,7 @@ pub fn execute( info: MessageInfo, msg: TokenExecuteMsg, ) -> ContractResult> { - let core = CORE.load(deps.storage)?; + let core = CORE_ADDRESS.load(deps.storage)?; ensure_eq!(info.sender, core, ContractError::Unauthorized); match msg { @@ -154,9 +148,12 @@ fn burn(deps: DepsMut, info: MessageInfo) -> ContractResult, _env: Env, msg: QueryMsg) -> ContractResult { match msg { QueryMsg::Config {} => { - let core = CORE.load(deps.storage)?.into_string(); + let core_address = CORE_ADDRESS.load(deps.storage)?.into_string(); let denom = DENOM.load(deps.storage)?; - Ok(to_json_binary(&ConfigResponse { core, denom })?) + Ok(to_json_binary(&ConfigResponse { + core_address, + denom, + })?) } } } diff --git a/contracts/token/src/tests.rs b/contracts/token/src/tests.rs index ca7029e3..dbdf8bbc 100644 --- a/contracts/token/src/tests.rs +++ b/contracts/token/src/tests.rs @@ -4,6 +4,7 @@ use cosmwasm_std::{ to_json_binary, Addr, ContractResult, CosmosMsg, Event, OwnedDeps, Querier, QuerierResult, QueryRequest, Reply, ReplyOn, SubMsgResult, SystemError, Uint128, }; +use lido_staking_base::msg::TokenInstantiateMsg; use neutron_sdk::{ bindings::{msg::NeutronMsg, query::NeutronQuery}, query::token_factory::FullDenomResponse, @@ -26,14 +27,14 @@ fn instantiate() { deps.as_mut(), mock_env(), mock_info("admin", &[]), - crate::InstantiateMsg { - core: "core".to_string(), + TokenInstantiateMsg { + core_address: "core".to_string(), subdenom: "subdenom".to_string(), }, ) .unwrap(); - let core = crate::CORE.load(deps.as_ref().storage).unwrap(); + let core = crate::CORE_ADDRESS.load(deps.as_ref().storage).unwrap(); assert_eq!(core, Addr::unchecked("core")); let denom = crate::DENOM.load(deps.as_ref().storage).unwrap(); @@ -51,7 +52,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()); } @@ -138,7 +139,7 @@ fn reply() { #[test] fn mint_zero() { let mut deps = mock_dependencies::(); - crate::CORE + crate::CORE_ADDRESS .save(deps.as_mut().storage, &Addr::unchecked("core")) .unwrap(); crate::DENOM @@ -161,7 +162,7 @@ fn mint_zero() { #[test] fn mint() { let mut deps = mock_dependencies::(); - crate::CORE + crate::CORE_ADDRESS .save(deps.as_mut().storage, &Addr::unchecked("core")) .unwrap(); crate::DENOM @@ -199,7 +200,7 @@ fn mint() { #[test] fn mint_stranger() { let mut deps = mock_dependencies::(); - crate::CORE + crate::CORE_ADDRESS .save(deps.as_mut().storage, &Addr::unchecked("core")) .unwrap(); crate::DENOM @@ -223,7 +224,7 @@ fn mint_stranger() { #[test] fn burn_zero() { let mut deps = mock_dependencies::(); - crate::CORE + crate::CORE_ADDRESS .save(deps.as_mut().storage, &Addr::unchecked("core")) .unwrap(); crate::DENOM @@ -246,7 +247,7 @@ fn burn_zero() { #[test] fn burn_multiple_coins() { let mut deps = mock_dependencies::(); - crate::CORE + crate::CORE_ADDRESS .save(deps.as_mut().storage, &Addr::unchecked("core")) .unwrap(); crate::DENOM @@ -269,7 +270,7 @@ fn burn_multiple_coins() { #[test] fn burn_invalid_coin() { let mut deps = mock_dependencies::(); - crate::CORE + crate::CORE_ADDRESS .save(deps.as_mut().storage, &Addr::unchecked("core")) .unwrap(); crate::DENOM @@ -294,7 +295,7 @@ fn burn_invalid_coin() { #[test] fn burn() { let mut deps = mock_dependencies::(); - crate::CORE + crate::CORE_ADDRESS .save(deps.as_mut().storage, &Addr::unchecked("core")) .unwrap(); crate::DENOM @@ -328,7 +329,7 @@ fn burn() { #[test] fn burn_stranger() { let mut deps = mock_dependencies::(); - crate::CORE + crate::CORE_ADDRESS .save(deps.as_mut().storage, &Addr::unchecked("core")) .unwrap(); crate::DENOM @@ -349,7 +350,7 @@ fn burn_stranger() { #[test] fn query_config() { let mut deps = mock_dependencies::(); - crate::CORE + crate::CORE_ADDRESS .save(deps.as_mut().storage, &Addr::unchecked("core")) .unwrap(); crate::DENOM @@ -360,7 +361,7 @@ fn query_config() { assert_eq!( response, to_json_binary(&crate::ConfigResponse { - core: "core".to_string(), + core_address: "core".to_string(), denom: "denom".to_string() }) .unwrap() diff --git a/packages/base/src/msg.rs b/packages/base/src/msg.rs index d9704446..7b4d9c00 100644 --- a/packages/base/src/msg.rs +++ b/packages/base/src/msg.rs @@ -6,3 +6,16 @@ pub enum TokenExecuteMsg { Mint { amount: Uint128, receiver: String }, Burn {}, } +#[cw_serde] +pub struct TokenInstantiateMsg { + pub core_address: String, + pub subdenom: String, +} + +#[cw_serde] +pub struct CoreInstantiateMsg { + pub token_contract: String, + pub puppeteer_contract: String, + pub strategy_contract: String, + pub owner: String, +} From 0a989165f54d15e7270017b7447a4e321367fb0c Mon Sep 17 00:00:00 2001 From: Sergey Ratiashvili Date: Wed, 13 Dec 2023 21:29:51 +0100 Subject: [PATCH 03/13] feat: ts clients --- .../src/generated/contractLib/index.ts | 38 ++++--- .../src/generated/contractLib/lidoCore.ts | 101 ++++++++++++++++++ .../src/generated/contractLib/lidoFactory.ts | 57 ++++++++++ .../src/generated/contractLib/lidoToken.ts | 4 +- 4 files changed, 182 insertions(+), 18 deletions(-) create mode 100644 integration_tests/src/generated/contractLib/lidoCore.ts create mode 100644 integration_tests/src/generated/contractLib/lidoFactory.ts 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..1fa073fa --- /dev/null +++ b/integration_tests/src/generated/contractLib/lidoCore.ts @@ -0,0 +1,101 @@ +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: UnbondArgs | UpdateConfigArgs; + [k: string]: unknown; +} +export interface Config { + owner: string; + puppeteer_contract: string; + strategy_contract: string; + token_contract: string; +} +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, fee?: number | StdFee | "auto", memo?: string, funds?: Coin[]): Promise => { + if (!isSigningCosmWasmClient(this.client)) { throw this.mustBeSigningClient(); } + return this.client.execute(sender, this.contractAddress, { bond: {} }, 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 { From 6eeea935551ed290c3f385aff7ac435d596adb03 Mon Sep 17 00:00:00 2001 From: Sergey Ratiashvili Date: Wed, 13 Dec 2023 23:14:05 +0100 Subject: [PATCH 04/13] fix: swap code ids --- contracts/factory/src/contract.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/contracts/factory/src/contract.rs b/contracts/factory/src/contract.rs index 55b468b8..51312e08 100644 --- a/contracts/factory/src/contract.rs +++ b/contracts/factory/src/contract.rs @@ -51,6 +51,7 @@ fn execute_init(deps: DepsMut, env: Env, _info: MessageInfo) -> NeutronResult NeutronResult NeutronResult Date: Wed, 13 Dec 2023 23:35:35 +0100 Subject: [PATCH 05/13] feat: factory tests --- contracts/factory/src/contract.rs | 15 +- integration_tests/package.json | 3 +- integration_tests/src/testcases/core.test.ts | 168 +++++++++++++++++++ 3 files changed, 181 insertions(+), 5 deletions(-) create mode 100644 integration_tests/src/testcases/core.test.ts diff --git a/contracts/factory/src/contract.rs b/contracts/factory/src/contract.rs index 51312e08..a5e0dcac 100644 --- a/contracts/factory/src/contract.rs +++ b/contracts/factory/src/contract.rs @@ -1,14 +1,14 @@ use crate::{ - msg::{ExecuteMsg, InstantiateMsg}, + 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, StdError, WasmMsg, + Deps, DepsMut, Env, HexBinary, MessageInfo, Response, StdError, StdResult, WasmMsg, }; use cw2::set_contract_version; use lido_staking_base::msg::{CoreInstantiateMsg, TokenInstantiateMsg}; -use neutron_sdk::NeutronResult; +use neutron_sdk::{bindings::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"); @@ -35,6 +35,13 @@ pub fn instantiate( Ok(Response::default()) } +#[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, @@ -53,7 +60,7 @@ fn execute_init(deps: DepsMut, env: Env, _info: MessageInfo) -> NeutronResult { + const context: { + park?: Cosmopark; + contractAddress?: string; + wallet?: DirectSecp256k1HdWallet; + gaiaWallet?: DirectSecp256k1HdWallet; + contractClient?: InstanceType; + account?: AccountData; + icaAddress?: string; + client?: SigningCosmWasmClient; + gaiaClient?: SigningCosmWasmClient; + gaiaUserAddress?: string; + gaiaQueryClient?: QueryClient & StakingExtension & BankExtension; + neutronClient?: InstanceType; + neutronUserAddress?: string; + validatorAddress?: string; + secondValidatorAddress?: string; + tokenizedDenomOnNeutron?: string; + coreCoreId?: number; + tokenCodeId?: number; + } = {}; + + 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 SigningCosmWasmClient.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, + ); + }); + + 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'); + }); +}); From 73cd18d7eced89c33ec051f3ac1418e1619d0c3b Mon Sep 17 00:00:00 2001 From: Sergey Ratiashvili Date: Thu, 14 Dec 2023 09:24:14 +0100 Subject: [PATCH 06/13] feat: refactor --- Cargo.lock | 1 + contracts/core/src/bin/lido-core-schema.rs | 9 +++-- contracts/core/src/contract.rs | 17 ++++----- contracts/core/src/lib.rs | 2 -- contracts/factory/src/contract.rs | 3 +- contracts/token/src/bin/lido-token-schema.rs | 11 +++--- contracts/token/src/lib.rs | 36 ++++++------------- contracts/token/src/tests.rs | 20 +++++------ .../src/testcases/interceptor-authz.test.ts | 2 +- packages/base/Cargo.toml | 1 + packages/base/src/lib.rs | 1 + packages/base/src/msg.rs | 21 ----------- .../msg.rs => packages/base/src/msg/core.rs | 29 +++++++++------ packages/base/src/msg/mod.rs | 2 ++ packages/base/src/msg/token.rs | 16 +++++++++ .../base/src/state/core.rs | 0 packages/base/src/state/mod.rs | 2 ++ packages/base/src/state/token.rs | 18 ++++++++++ 18 files changed, 99 insertions(+), 92 deletions(-) delete mode 100644 packages/base/src/msg.rs rename contracts/core/src/msg.rs => packages/base/src/msg/core.rs (68%) create mode 100644 packages/base/src/msg/mod.rs create mode 100644 packages/base/src/msg/token.rs rename contracts/core/src/state.rs => packages/base/src/state/core.rs (100%) create mode 100644 packages/base/src/state/mod.rs create mode 100644 packages/base/src/state/token.rs diff --git a/Cargo.lock b/Cargo.lock index bb176c3f..1bae864c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -773,6 +773,7 @@ dependencies = [ "cosmwasm-std", "cosmwasm-storage", "cw-multi-test 0.20.0", + "cw-storage-plus", ] [[package]] diff --git a/contracts/core/src/bin/lido-core-schema.rs b/contracts/core/src/bin/lido-core-schema.rs index 7bc67351..eb460abc 100644 --- a/contracts/core/src/bin/lido-core-schema.rs +++ b/contracts/core/src/bin/lido-core-schema.rs @@ -1,13 +1,12 @@ use cosmwasm_schema::write_api; -use lido_core::{ - msg::{ExecuteMsg, MigrateMsg}, - state::QueryMsg, +use lido_staking_base::{ + msg::core::{ExecuteMsg, InstantiateMsg, MigrateMsg}, + state::core::QueryMsg, }; -use lido_staking_base::msg::CoreInstantiateMsg; fn main() { write_api! { - instantiate: CoreInstantiateMsg, + instantiate: InstantiateMsg, query: QueryMsg, execute: ExecuteMsg, migrate: MigrateMsg diff --git a/contracts/core/src/contract.rs b/contracts/core/src/contract.rs index 5590cd42..bbef1fd2 100644 --- a/contracts/core/src/contract.rs +++ b/contracts/core/src/contract.rs @@ -1,22 +1,17 @@ -use std::str::FromStr; - +use crate::error::{ContractError, ContractResult}; use cosmwasm_std::{ attr, ensure_eq, ensure_ne, entry_point, to_json_binary, Binary, CosmosMsg, Decimal, Deps, DepsMut, Env, MessageInfo, Response, StdResult, Uint128, WasmMsg, }; - use cw2::set_contract_version; -use lido_staking_base::msg::{CoreInstantiateMsg, TokenExecuteMsg}; +use lido_staking_base::msg::core::{ExecuteMsg, InstantiateMsg}; +use lido_staking_base::msg::token::ExecuteMsg as TokenExecuteMsg; +use lido_staking_base::state::core::{QueryMsg, CONFIG}; use neutron_sdk::{ bindings::{msg::NeutronMsg, query::NeutronQuery}, NeutronResult, }; - -use crate::{ - error::{ContractError, ContractResult}, - msg::ExecuteMsg, - state::{QueryMsg, CONFIG}, -}; +use std::str::FromStr; const CONTRACT_NAME: &str = concat!("crates.io:lido-neutron-contracts__", env!("CARGO_PKG_NAME")); const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -25,7 +20,7 @@ pub fn instantiate( deps: DepsMut, _env: Env, _info: MessageInfo, - msg: CoreInstantiateMsg, + msg: InstantiateMsg, ) -> NeutronResult { set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; diff --git a/contracts/core/src/lib.rs b/contracts/core/src/lib.rs index a5abdbb0..ed729875 100644 --- a/contracts/core/src/lib.rs +++ b/contracts/core/src/lib.rs @@ -1,4 +1,2 @@ pub mod contract; pub mod error; -pub mod msg; -pub mod state; diff --git a/contracts/factory/src/contract.rs b/contracts/factory/src/contract.rs index a5e0dcac..54ba4f54 100644 --- a/contracts/factory/src/contract.rs +++ b/contracts/factory/src/contract.rs @@ -7,7 +7,8 @@ use cosmwasm_std::{ Deps, DepsMut, Env, HexBinary, MessageInfo, Response, StdError, StdResult, WasmMsg, }; use cw2::set_contract_version; -use lido_staking_base::msg::{CoreInstantiateMsg, TokenInstantiateMsg}; +use lido_staking_base::msg::core::InstantiateMsg as CoreInstantiateMsg; +use lido_staking_base::msg::token::InstantiateMsg as TokenInstantiateMsg; use neutron_sdk::{bindings::query::NeutronQuery, NeutronResult}; const CONTRACT_NAME: &str = concat!("crates.io:lido-neutron-contracts__", env!("CARGO_PKG_NAME")); diff --git a/contracts/token/src/bin/lido-token-schema.rs b/contracts/token/src/bin/lido-token-schema.rs index d57efe2f..555f2cf1 100644 --- a/contracts/token/src/bin/lido-token-schema.rs +++ b/contracts/token/src/bin/lido-token-schema.rs @@ -1,12 +1,15 @@ use cosmwasm_schema::write_api; -use lido_staking_base::msg::{TokenExecuteMsg, TokenInstantiateMsg}; -use lido_token::{MigrateMsg, QueryMsg}; + +use lido_staking_base::{ + msg::token::{ExecuteMsg, InstantiateMsg, MigrateMsg}, + state::token::QueryMsg, +}; fn main() { write_api! { - instantiate: TokenInstantiateMsg, + instantiate: InstantiateMsg, query: QueryMsg, - execute: TokenExecuteMsg, + execute: ExecuteMsg, migrate: MigrateMsg } } diff --git a/contracts/token/src/lib.rs b/contracts/token/src/lib.rs index 8a1f2a25..6a543a1d 100644 --- a/contracts/token/src/lib.rs +++ b/contracts/token/src/lib.rs @@ -1,9 +1,12 @@ use cosmwasm_std::{ - attr, ensure_eq, ensure_ne, to_json_binary, Addr, Attribute, Binary, Deps, DepsMut, Env, Event, + attr, ensure_eq, ensure_ne, to_json_binary, Attribute, Binary, Deps, DepsMut, Env, Event, MessageInfo, Reply, Response, StdError, SubMsg, Uint128, }; -use cw_storage_plus::Item; -use lido_staking_base::msg::{TokenExecuteMsg, TokenInstantiateMsg}; + +use lido_staking_base::{ + msg::token::{ExecuteMsg, InstantiateMsg, MigrateMsg}, + state::token::{ConfigResponse, QueryMsg, CORE_ADDRESS, DENOM}, +}; use neutron_sdk::{ bindings::{msg::NeutronMsg, query::NeutronQuery}, query::token_factory::query_full_denom, @@ -35,28 +38,9 @@ pub enum ContractError { pub type ContractResult = Result; -#[cosmwasm_schema::cw_serde] -#[derive(cosmwasm_schema::QueryResponses)] -pub enum QueryMsg { - #[returns(ConfigResponse)] - Config {}, -} - -#[cosmwasm_schema::cw_serde] -pub struct ConfigResponse { - pub core_address: 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_ADDRESS: Item = Item::new("core"); -const DENOM: Item = Item::new("denom"); - const CREATE_DENOM_REPLY_ID: u64 = 1; fn response>( @@ -79,7 +63,7 @@ pub fn instantiate( deps: DepsMut, _env: Env, _info: MessageInfo, - msg: TokenInstantiateMsg, + msg: InstantiateMsg, ) -> ContractResult> { cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; @@ -104,14 +88,14 @@ pub fn execute( deps: DepsMut, _env: Env, info: MessageInfo, - msg: TokenExecuteMsg, + msg: ExecuteMsg, ) -> ContractResult> { let core = CORE_ADDRESS.load(deps.storage)?; ensure_eq!(info.sender, core, ContractError::Unauthorized); match msg { - TokenExecuteMsg::Mint { amount, receiver } => mint(deps, amount, receiver), - TokenExecuteMsg::Burn {} => burn(deps, info), + ExecuteMsg::Mint { amount, receiver } => mint(deps, amount, receiver), + ExecuteMsg::Burn {} => burn(deps, info), } } diff --git a/contracts/token/src/tests.rs b/contracts/token/src/tests.rs index dbdf8bbc..3b94f1eb 100644 --- a/contracts/token/src/tests.rs +++ b/contracts/token/src/tests.rs @@ -4,7 +4,7 @@ use cosmwasm_std::{ to_json_binary, Addr, ContractResult, CosmosMsg, Event, OwnedDeps, Querier, QuerierResult, QueryRequest, Reply, ReplyOn, SubMsgResult, SystemError, Uint128, }; -use lido_staking_base::msg::TokenInstantiateMsg; +use lido_staking_base::msg::token::InstantiateMsg; use neutron_sdk::{ bindings::{msg::NeutronMsg, query::NeutronQuery}, query::token_factory::FullDenomResponse, @@ -27,7 +27,7 @@ fn instantiate() { deps.as_mut(), mock_env(), mock_info("admin", &[]), - TokenInstantiateMsg { + InstantiateMsg { core_address: "core".to_string(), subdenom: "subdenom".to_string(), }, @@ -150,7 +150,7 @@ fn mint_zero() { deps.as_mut(), mock_env(), mock_info("core", &[]), - crate::TokenExecuteMsg::Mint { + crate::ExecuteMsg::Mint { amount: Uint128::zero(), receiver: "receiver".to_string(), }, @@ -173,7 +173,7 @@ fn mint() { deps.as_mut(), mock_env(), mock_info("core", &[]), - crate::TokenExecuteMsg::Mint { + crate::ExecuteMsg::Mint { amount: Uint128::new(220), receiver: "receiver".to_string(), }, @@ -211,7 +211,7 @@ fn mint_stranger() { deps.as_mut(), mock_env(), mock_info("stranger", &[]), - crate::TokenExecuteMsg::Mint { + crate::ExecuteMsg::Mint { amount: Uint128::new(220), receiver: "receiver".to_string(), }, @@ -235,7 +235,7 @@ fn burn_zero() { deps.as_mut(), mock_env(), mock_info("core", &[]), - crate::TokenExecuteMsg::Burn {}, + crate::ExecuteMsg::Burn {}, ) .unwrap_err(); assert_eq!( @@ -258,7 +258,7 @@ fn burn_multiple_coins() { deps.as_mut(), mock_env(), mock_info("core", &[coin(20, "coin1"), coin(10, "denom")]), - crate::TokenExecuteMsg::Burn {}, + crate::ExecuteMsg::Burn {}, ) .unwrap_err(); assert_eq!( @@ -281,7 +281,7 @@ fn burn_invalid_coin() { deps.as_mut(), mock_env(), mock_info("core", &[coin(20, "coin1")]), - crate::TokenExecuteMsg::Burn {}, + crate::ExecuteMsg::Burn {}, ) .unwrap_err(); assert_eq!( @@ -306,7 +306,7 @@ fn burn() { deps.as_mut(), mock_env(), mock_info("core", &[coin(140, "denom")]), - crate::TokenExecuteMsg::Burn {}, + crate::ExecuteMsg::Burn {}, ) .unwrap(); @@ -340,7 +340,7 @@ fn burn_stranger() { deps.as_mut(), mock_env(), mock_info("stranger", &[coin(160, "denom")]), - crate::TokenExecuteMsg::Burn {}, + crate::ExecuteMsg::Burn {}, ) .unwrap_err(); 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 index e1e18bc5..e922d147 100644 --- a/packages/base/Cargo.toml +++ b/packages/base/Cargo.toml @@ -25,6 +25,7 @@ library = [] [dependencies] cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } [dev-dependencies] diff --git a/packages/base/src/lib.rs b/packages/base/src/lib.rs index d0e87a0d..685329d1 100644 --- a/packages/base/src/lib.rs +++ b/packages/base/src/lib.rs @@ -1 +1,2 @@ pub mod msg; +pub mod state; diff --git a/packages/base/src/msg.rs b/packages/base/src/msg.rs deleted file mode 100644 index 7b4d9c00..00000000 --- a/packages/base/src/msg.rs +++ /dev/null @@ -1,21 +0,0 @@ -use cosmwasm_schema::cw_serde; -use cosmwasm_std::Uint128; - -#[cw_serde] -pub enum TokenExecuteMsg { - Mint { amount: Uint128, receiver: String }, - Burn {}, -} -#[cw_serde] -pub struct TokenInstantiateMsg { - pub core_address: String, - pub subdenom: String, -} - -#[cw_serde] -pub struct CoreInstantiateMsg { - pub token_contract: String, - pub puppeteer_contract: String, - pub strategy_contract: String, - pub owner: String, -} diff --git a/contracts/core/src/msg.rs b/packages/base/src/msg/core.rs similarity index 68% rename from contracts/core/src/msg.rs rename to packages/base/src/msg/core.rs index 2306edaf..c03f9392 100644 --- a/contracts/core/src/msg.rs +++ b/packages/base/src/msg/core.rs @@ -1,18 +1,14 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::Uint128; -use lido_staking_base::msg::CoreInstantiateMsg; -use crate::state::Config; +use crate::state::core::Config; -impl From for Config { - fn from(val: CoreInstantiateMsg) -> Self { - Config { - token_contract: val.token_contract, - puppeteer_contract: val.puppeteer_contract, - strategy_contract: val.strategy_contract, - owner: val.owner, - } - } +#[cw_serde] +pub struct InstantiateMsg { + pub token_contract: String, + pub puppeteer_contract: String, + pub strategy_contract: String, + pub owner: String, } #[cw_serde] @@ -31,3 +27,14 @@ pub enum ExecuteMsg { } #[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..d6f98a42 --- /dev/null +++ b/packages/base/src/msg/token.rs @@ -0,0 +1,16 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::Uint128; + +#[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/contracts/core/src/state.rs b/packages/base/src/state/core.rs similarity index 100% rename from contracts/core/src/state.rs rename to packages/base/src/state/core.rs 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..6af60a57 --- /dev/null +++ b/packages/base/src/state/token.rs @@ -0,0 +1,18 @@ +use cosmwasm_std::Addr; +use cw_storage_plus::Item; + +#[cosmwasm_schema::cw_serde] +#[derive(cosmwasm_schema::QueryResponses)] +pub enum QueryMsg { + #[returns(ConfigResponse)] + Config {}, +} + +#[cosmwasm_schema::cw_serde] +pub struct ConfigResponse { + pub core_address: String, + pub denom: String, +} + +pub const CORE_ADDRESS: Item = Item::new("core"); +pub const DENOM: Item = Item::new("denom"); From ac15cc0c08aa2e0ec4006ff81dcddf5953a38717 Mon Sep 17 00:00:00 2001 From: Sergey Ratiashvili Date: Thu, 14 Dec 2023 09:34:04 +0100 Subject: [PATCH 07/13] feat: cosmwasm_1_2 capability --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 2514fbc98c5ae50e4e1ef261e11578fa92a7a14c Mon Sep 17 00:00:00 2001 From: Sergey Ratiashvili Date: Thu, 14 Dec 2023 09:56:44 +0100 Subject: [PATCH 08/13] feat: move queries to the messages --- contracts/core/src/bin/lido-core-schema.rs | 5 +---- contracts/core/src/contract.rs | 4 ++-- contracts/token/src/bin/lido-token-schema.rs | 5 +---- contracts/token/src/lib.rs | 4 ++-- packages/base/src/msg/core.rs | 13 +++++++++++-- packages/base/src/msg/token.rs | 15 ++++++++++++++- packages/base/src/state/core.rs | 12 +----------- packages/base/src/state/token.rs | 13 ------------- 8 files changed, 32 insertions(+), 39 deletions(-) diff --git a/contracts/core/src/bin/lido-core-schema.rs b/contracts/core/src/bin/lido-core-schema.rs index eb460abc..cac8e638 100644 --- a/contracts/core/src/bin/lido-core-schema.rs +++ b/contracts/core/src/bin/lido-core-schema.rs @@ -1,8 +1,5 @@ use cosmwasm_schema::write_api; -use lido_staking_base::{ - msg::core::{ExecuteMsg, InstantiateMsg, MigrateMsg}, - state::core::QueryMsg, -}; +use lido_staking_base::msg::core::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; fn main() { write_api! { diff --git a/contracts/core/src/contract.rs b/contracts/core/src/contract.rs index bbef1fd2..862fc23a 100644 --- a/contracts/core/src/contract.rs +++ b/contracts/core/src/contract.rs @@ -4,9 +4,9 @@ use cosmwasm_std::{ DepsMut, Env, MessageInfo, Response, StdResult, Uint128, WasmMsg, }; use cw2::set_contract_version; -use lido_staking_base::msg::core::{ExecuteMsg, InstantiateMsg}; +use lido_staking_base::msg::core::{ExecuteMsg, InstantiateMsg, QueryMsg}; use lido_staking_base::msg::token::ExecuteMsg as TokenExecuteMsg; -use lido_staking_base::state::core::{QueryMsg, CONFIG}; +use lido_staking_base::state::core::CONFIG; use neutron_sdk::{ bindings::{msg::NeutronMsg, query::NeutronQuery}, NeutronResult, diff --git a/contracts/token/src/bin/lido-token-schema.rs b/contracts/token/src/bin/lido-token-schema.rs index 555f2cf1..ecb0a4aa 100644 --- a/contracts/token/src/bin/lido-token-schema.rs +++ b/contracts/token/src/bin/lido-token-schema.rs @@ -1,9 +1,6 @@ use cosmwasm_schema::write_api; -use lido_staking_base::{ - msg::token::{ExecuteMsg, InstantiateMsg, MigrateMsg}, - state::token::QueryMsg, -}; +use lido_staking_base::msg::token::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; fn main() { write_api! { diff --git a/contracts/token/src/lib.rs b/contracts/token/src/lib.rs index 6a543a1d..b8397773 100644 --- a/contracts/token/src/lib.rs +++ b/contracts/token/src/lib.rs @@ -4,8 +4,8 @@ use cosmwasm_std::{ }; use lido_staking_base::{ - msg::token::{ExecuteMsg, InstantiateMsg, MigrateMsg}, - state::token::{ConfigResponse, QueryMsg, CORE_ADDRESS, DENOM}, + msg::token::{ConfigResponse, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}, + state::token::{CORE_ADDRESS, DENOM}, }; use neutron_sdk::{ bindings::{msg::NeutronMsg, query::NeutronQuery}, diff --git a/packages/base/src/msg/core.rs b/packages/base/src/msg/core.rs index c03f9392..d742411b 100644 --- a/packages/base/src/msg/core.rs +++ b/packages/base/src/msg/core.rs @@ -1,5 +1,5 @@ -use cosmwasm_schema::cw_serde; -use cosmwasm_std::Uint128; +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{Decimal256, Uint128}; use crate::state::core::Config; @@ -11,6 +11,15 @@ pub struct InstantiateMsg { pub owner: String, } +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(Config)] + Config {}, + #[returns(Decimal256)] + ExchangeRate {}, +} + #[cw_serde] pub enum ExecuteMsg { Bond {}, diff --git a/packages/base/src/msg/token.rs b/packages/base/src/msg/token.rs index d6f98a42..3aea5c9e 100644 --- a/packages/base/src/msg/token.rs +++ b/packages/base/src/msg/token.rs @@ -1,6 +1,19 @@ -use cosmwasm_schema::cw_serde; +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 }, diff --git a/packages/base/src/state/core.rs b/packages/base/src/state/core.rs index 44653773..4b978bc5 100644 --- a/packages/base/src/state/core.rs +++ b/packages/base/src/state/core.rs @@ -1,5 +1,4 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::Decimal256; +use cosmwasm_schema::cw_serde; use cw_storage_plus::Item; #[cw_serde] @@ -10,13 +9,4 @@ pub struct Config { pub owner: String, } -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - #[returns(Config)] - Config {}, - #[returns(Decimal256)] - ExchangeRate {}, -} - pub const CONFIG: Item = Item::new("config"); diff --git a/packages/base/src/state/token.rs b/packages/base/src/state/token.rs index 6af60a57..838e2a48 100644 --- a/packages/base/src/state/token.rs +++ b/packages/base/src/state/token.rs @@ -1,18 +1,5 @@ use cosmwasm_std::Addr; use cw_storage_plus::Item; -#[cosmwasm_schema::cw_serde] -#[derive(cosmwasm_schema::QueryResponses)] -pub enum QueryMsg { - #[returns(ConfigResponse)] - Config {}, -} - -#[cosmwasm_schema::cw_serde] -pub struct ConfigResponse { - pub core_address: String, - pub denom: String, -} - pub const CORE_ADDRESS: Item = Item::new("core"); pub const DENOM: Item = Item::new("denom"); From 33980f4f577783cb9fc8d8e3a9f50970bd9f0f27 Mon Sep 17 00:00:00 2001 From: Sergey Ratiashvili Date: Thu, 14 Dec 2023 12:43:26 +0100 Subject: [PATCH 09/13] feat: receiver for bond --- contracts/core/src/contract.rs | 11 +- .../src/generated/contractLib/lidoCore.ts | 9 +- integration_tests/src/testcases/core.test.ts | 143 +++++++++++++++++- packages/base/src/msg/core.rs | 4 +- 4 files changed, 155 insertions(+), 12 deletions(-) diff --git a/contracts/core/src/contract.rs b/contracts/core/src/contract.rs index 862fc23a..8bbc0a6a 100644 --- a/contracts/core/src/contract.rs +++ b/contracts/core/src/contract.rs @@ -48,7 +48,7 @@ pub fn execute( msg: ExecuteMsg, ) -> ContractResult> { match msg { - ExecuteMsg::Bond {} => execute_bond(deps, env, info), + ExecuteMsg::Bond { receiver } => execute_bond(deps, env, info, receiver), ExecuteMsg::Unbond { amount } => execute_unbond(deps, env, info, amount), ExecuteMsg::UpdateConfig { token_contract, @@ -71,6 +71,7 @@ fn execute_bond( deps: DepsMut, env: Env, info: MessageInfo, + receiver: Option, ) -> ContractResult> { let config = CONFIG.load(deps.storage)?; @@ -101,11 +102,17 @@ fn execute_bond( 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: info.sender.to_string(), + receiver, })?, funds: vec![], })]; diff --git a/integration_tests/src/generated/contractLib/lidoCore.ts b/integration_tests/src/generated/contractLib/lidoCore.ts index 1fa073fa..a485013e 100644 --- a/integration_tests/src/generated/contractLib/lidoCore.ts +++ b/integration_tests/src/generated/contractLib/lidoCore.ts @@ -30,7 +30,7 @@ export type Uint128 = string; export interface LidoCoreSchema { responses: Config | Decimal256; - execute: UnbondArgs | UpdateConfigArgs; + execute: BondArgs | UnbondArgs | UpdateConfigArgs; [k: string]: unknown; } export interface Config { @@ -39,6 +39,9 @@ export interface Config { strategy_contract: string; token_contract: string; } +export interface BondArgs { + receiver?: string | null; +} export interface UnbondArgs { amount: Uint128; } @@ -86,9 +89,9 @@ export class Client { queryExchangeRate = async(): Promise => { return this.client.queryContractSmart(this.contractAddress, { exchange_rate: {} }); } - bond = async(sender: string, fee?: number | StdFee | "auto", memo?: string, funds?: Coin[]): Promise => { + 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: {} }, fee || "auto", memo, funds); + 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(); } diff --git a/integration_tests/src/testcases/core.test.ts b/integration_tests/src/testcases/core.test.ts index 26108412..afbd281d 100644 --- a/integration_tests/src/testcases/core.test.ts +++ b/integration_tests/src/testcases/core.test.ts @@ -1,23 +1,27 @@ import { describe, expect, it, beforeAll, afterAll } from 'vitest'; -import { LidoFactory } from '../generated/contractLib'; +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 { setupPark } from '../testSuite'; +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: { @@ -26,19 +30,24 @@ describe('Core', () => { wallet?: DirectSecp256k1HdWallet; gaiaWallet?: DirectSecp256k1HdWallet; contractClient?: InstanceType; + coreContractClient?: InstanceType; account?: AccountData; icaAddress?: string; client?: SigningCosmWasmClient; - gaiaClient?: 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 () => { @@ -49,7 +58,6 @@ describe('Core', () => { prefix: 'neutron', }, ); - context.gaiaWallet = await DirectSecp256k1HdWallet.fromMnemonic( context.park.config.wallets.demowallet1.mnemonic, { @@ -62,7 +70,6 @@ describe('Core', () => { 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, @@ -70,7 +77,7 @@ describe('Core', () => { gasPrice: GasPrice.fromString('0.025untrn'), }, ); - context.gaiaClient = await SigningCosmWasmClient.connectWithSigner( + context.gaiaClient = await SigningStargateClient.connectWithSigner( `http://127.0.0.1:${context.park.ports.gaia.rpc}`, context.gaiaWallet, { @@ -85,6 +92,16 @@ describe('Core', () => { setupStakingExtension, setupBankExtension, ); + + const secondWallet = await DirectSecp256k1HdWallet.fromMnemonic( + context.park.config.wallets.demo2.mnemonic, + { + prefix: 'neutron', + }, + ); + context.neutronSecondUserAddress = ( + await secondWallet.getAccounts() + )[0].address; }); afterAll(async () => { @@ -164,5 +181,119 @@ describe('Core', () => { 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/packages/base/src/msg/core.rs b/packages/base/src/msg/core.rs index d742411b..77f7cced 100644 --- a/packages/base/src/msg/core.rs +++ b/packages/base/src/msg/core.rs @@ -22,7 +22,9 @@ pub enum QueryMsg { #[cw_serde] pub enum ExecuteMsg { - Bond {}, + Bond { + receiver: Option, + }, Unbond { amount: Uint128, }, From 95f7eafd62fa7d6b1d7e8380d80c28a05047e6ba Mon Sep 17 00:00:00 2001 From: Sergey Ratiashvili Date: Thu, 14 Dec 2023 14:55:20 +0100 Subject: [PATCH 10/13] feat: refactor token --- Cargo.lock | 1 + contracts/token/src/contract.rs | 142 +++++++++++++++++++++++ contracts/token/src/error.rs | 24 ++++ contracts/token/src/lib.rs | 172 +--------------------------- contracts/token/src/tests.rs | 116 ++++++++++--------- packages/base/Cargo.toml | 2 +- packages/base/src/helpers/answer.rs | 18 +++ packages/base/src/helpers/mod.rs | 1 + packages/base/src/lib.rs | 1 + 9 files changed, 251 insertions(+), 226 deletions(-) create mode 100644 contracts/token/src/contract.rs create mode 100644 contracts/token/src/error.rs create mode 100644 packages/base/src/helpers/answer.rs create mode 100644 packages/base/src/helpers/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 1bae864c..187e2d9b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -774,6 +774,7 @@ dependencies = [ "cosmwasm-storage", "cw-multi-test 0.20.0", "cw-storage-plus", + "neutron-sdk", ] [[package]] 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 b8397773..8a8512dd 100644 --- a/contracts/token/src/lib.rs +++ b/contracts/token/src/lib.rs @@ -1,173 +1,5 @@ -use cosmwasm_std::{ - attr, ensure_eq, ensure_ne, to_json_binary, Attribute, Binary, Deps, DepsMut, Env, Event, - MessageInfo, Reply, Response, StdError, SubMsg, Uint128, -}; - -use lido_staking_base::{ - 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, -}; - #[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; - -const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); -const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); - -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_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", - [attr("core_address", 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_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", - [ - 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_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"), 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 3b94f1eb..1ada48d4 100644 --- a/contracts/token/src/tests.rs +++ b/contracts/token/src/tests.rs @@ -4,13 +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::InstantiateMsg; +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(), @@ -23,7 +31,7 @@ fn mock_dependencies() -> OwnedDeps(); - let response = crate::instantiate( + let response = contract::instantiate( deps.as_mut(), mock_env(), mock_info("admin", &[]), @@ -34,15 +42,15 @@ fn instantiate() { ) .unwrap(); - let core = crate::CORE_ADDRESS.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 { @@ -60,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 { @@ -69,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] @@ -111,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()); @@ -139,41 +147,41 @@ fn reply() { #[test] fn mint_zero() { let mut deps = mock_dependencies::(); - crate::CORE_ADDRESS + 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_ADDRESS + 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(), }, @@ -200,113 +208,111 @@ fn mint() { #[test] fn mint_stranger() { let mut deps = mock_dependencies::(); - crate::CORE_ADDRESS + 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_ADDRESS + 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_ADDRESS + 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_ADDRESS + 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_ADDRESS + 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(); @@ -329,38 +335,38 @@ fn burn() { #[test] fn burn_stranger() { let mut deps = mock_dependencies::(); - crate::CORE_ADDRESS + 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_ADDRESS + 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 { + to_json_binary(&ConfigResponse { core_address: "core".to_string(), denom: "denom".to_string() }) diff --git a/packages/base/Cargo.toml b/packages/base/Cargo.toml index e922d147..206dfff3 100644 --- a/packages/base/Cargo.toml +++ b/packages/base/Cargo.toml @@ -26,7 +26,7 @@ library = [] cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } cw-storage-plus = { workspace = true } - +neutron-sdk = { workspace = true } [dev-dependencies] cosmwasm-storage = { 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 index 685329d1..a801d48d 100644 --- a/packages/base/src/lib.rs +++ b/packages/base/src/lib.rs @@ -1,2 +1,3 @@ +pub mod helpers; pub mod msg; pub mod state; From b3dc684f486cdf4f1eec28cabef928dc5599e12a Mon Sep 17 00:00:00 2001 From: Sergey Ratiashvili Date: Thu, 14 Dec 2023 15:32:20 +0100 Subject: [PATCH 11/13] feat: refactor factory --- contracts/core/src/contract.rs | 28 ++++++++++--------- contracts/factory/src/contract.rs | 45 +++++++++++++++++++++---------- contracts/factory/src/error.rs | 22 +++++++++++---- 3 files changed, 64 insertions(+), 31 deletions(-) diff --git a/contracts/core/src/contract.rs b/contracts/core/src/contract.rs index 8bbc0a6a..9d1a3a01 100644 --- a/contracts/core/src/contract.rs +++ b/contracts/core/src/contract.rs @@ -1,17 +1,16 @@ use crate::error::{ContractError, ContractResult}; use cosmwasm_std::{ - attr, ensure_eq, ensure_ne, entry_point, to_json_binary, Binary, CosmosMsg, Decimal, Deps, - DepsMut, Env, MessageInfo, Response, StdResult, Uint128, WasmMsg, + 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}, - NeutronResult, -}; +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"); @@ -21,11 +20,17 @@ pub fn instantiate( _env: Env, _info: MessageInfo, msg: InstantiateMsg, -) -> NeutronResult { +) -> ContractResult> { set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - CONFIG.save(deps.storage, &msg.into())?; - Ok(Response::default()) + 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)] @@ -116,8 +121,7 @@ fn execute_bond( })?, funds: vec![], })]; - - Ok(Response::default().add_messages(msgs).add_attributes(attrs)) + Ok(response("execute-bond", CONTRACT_NAME, attrs).add_messages(msgs)) } fn check_denom(_denom: String) -> ContractResult<()> { @@ -155,7 +159,7 @@ fn execute_update_config( attrs.push(attr("owner", owner)); } CONFIG.save(deps.storage, &config)?; - Ok(Response::default().add_attributes(attrs)) + Ok(response("execute-update_config", CONTRACT_NAME, attrs)) } fn execute_unbond( diff --git a/contracts/factory/src/contract.rs b/contracts/factory/src/contract.rs index 54ba4f54..64ae1d99 100644 --- a/contracts/factory/src/contract.rs +++ b/contracts/factory/src/contract.rs @@ -1,15 +1,21 @@ 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, StdError, StdResult, WasmMsg, + Deps, DepsMut, Env, HexBinary, MessageInfo, Response, StdResult, WasmMsg, }; use cw2::set_contract_version; -use lido_staking_base::msg::core::InstantiateMsg as CoreInstantiateMsg; use lido_staking_base::msg::token::InstantiateMsg as TokenInstantiateMsg; -use neutron_sdk::{bindings::query::NeutronQuery, NeutronResult}; +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"); @@ -20,20 +26,28 @@ pub fn instantiate( _env: Env, info: MessageInfo, msg: InstantiateMsg, -) -> NeutronResult { +) -> ContractResult> { set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; CONFIG.save( deps.storage, &Config { - salt: msg.salt, + 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, + subdenom: msg.subdenom.to_string(), }, )?; - Ok(Response::default()) + + 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)] @@ -49,13 +63,17 @@ pub fn execute( env: Env, info: MessageInfo, msg: ExecuteMsg, -) -> NeutronResult { +) -> ContractResult> { match msg { ExecuteMsg::Init {} => execute_init(deps, env, info), } } -fn execute_init(deps: DepsMut, env: Env, _info: MessageInfo) -> NeutronResult { +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")]; @@ -65,11 +83,10 @@ fn execute_init(deps: DepsMut, env: Env, _info: MessageInfo) -> NeutronResult NeutronResult NeutronResult { diff --git a/contracts/factory/src/error.rs b/contracts/factory/src/error.rs index 19407370..ac50d00c 100644 --- a/contracts/factory/src/error.rs +++ b/contracts/factory/src/error.rs @@ -1,11 +1,23 @@ -use cosmwasm_std::Instantiate2AddressError; +use cosmwasm_std::{Instantiate2AddressError, StdError}; +use neutron_sdk::NeutronError; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] pub enum ContractError { - Instantiate2Address { - err: Instantiate2AddressError, - code_id: u64, - }, + #[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; From 50e1d5cf66cb8b0d48d9e173b15982be67863a58 Mon Sep 17 00:00:00 2001 From: Sergey Ratiashvili Date: Thu, 14 Dec 2023 16:47:23 +0100 Subject: [PATCH 12/13] fix: split workflow in 2 --- .github/workflows/tests.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 384294de..8e5be336 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -88,4 +88,6 @@ 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 --run shard 1/2 --bail 1 + cd integration_tests && yarn vitest --run shard 2/2 --bail 1 \ No newline at end of file From 971ad97acb9c65f185def015fcae1fdd46334310 Mon Sep 17 00:00:00 2001 From: Sergey Ratiashvili Date: Thu, 14 Dec 2023 17:14:30 +0100 Subject: [PATCH 13/13] fix: fix --- .github/workflows/tests.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8e5be336..082eab8b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -89,5 +89,6 @@ jobs: - run: make compile - name: Run tests run: | - cd integration_tests && yarn vitest --run shard 1/2 --bail 1 - cd integration_tests && yarn vitest --run shard 2/2 --bail 1 \ No newline at end of file + 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