From 259e9469ee4aebdfc2bfb7ca66c5fb4279e89f08 Mon Sep 17 00:00:00 2001 From: Guy Garcia Date: Mon, 28 Oct 2024 09:26:28 -0400 Subject: [PATCH 1/7] complete overhaul --- Cargo.lock | 90 ++++++++++++++++++++++++++-- Cargo.toml | 7 ++- src/{ => fungible}/cw20.rs | 48 +++++++++------ src/fungible/info.rs | 77 ++++++++++++++++++++++++ src/fungible/mod.rs | 17 ++++++ src/{ => fungible}/native.rs | 39 ++++++------ src/{ => fungible}/token.rs | 67 ++++++++++++--------- src/info.rs | 83 -------------------------- src/lib.rs | 44 +++++++++----- src/non_fungible/cw721.rs | 112 +++++++++++++++++++++++++++++++++++ src/non_fungible/info.rs | 83 ++++++++++++++++++++++++++ src/non_fungible/mod.rs | 20 +++++++ src/non_fungible/token.rs | 55 +++++++++++++++++ 13 files changed, 574 insertions(+), 168 deletions(-) rename src/{ => fungible}/cw20.rs (70%) create mode 100644 src/fungible/info.rs create mode 100644 src/fungible/mod.rs rename src/{ => fungible}/native.rs (63%) rename src/{ => fungible}/token.rs (50%) delete mode 100644 src/info.rs create mode 100644 src/non_fungible/cw721.rs create mode 100644 src/non_fungible/info.rs create mode 100644 src/non_fungible/mod.rs create mode 100644 src/non_fungible/token.rs diff --git a/Cargo.lock b/Cargo.lock index d0edee3..0e21e4a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -81,10 +81,12 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "cosmos_coin" -version = "0.1.0" +version = "0.2.0" dependencies = [ "cosmwasm-std", "cw20-base", + "cw721", + "cw721-base-updatable", "schemars", "serde", "serde_json", @@ -203,6 +205,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "cw-storage-plus" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "648b1507290bbc03a8d88463d7cd9b04b1fa0155e5eef366c4fa052b9caaac7a" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + [[package]] name = "cw-storage-plus" version = "1.2.0" @@ -214,6 +227,18 @@ dependencies = [ "serde", ] +[[package]] +name = "cw-utils" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dbaecb78c8e8abfd6b4258c7f4fbeb5c49a5e45ee4d910d3240ee8e1d714e1b" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "cw-utils" version = "1.0.3" @@ -222,13 +247,25 @@ checksum = "1c4a657e5caacc3a0d00ee96ca8618745d050b8f757c709babafb81208d4239c" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw2", + "cw2 1.1.2", "schemars", "semver", "serde", "thiserror", ] +[[package]] +name = "cw2" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cf4639517490dd36b333bbd6c4fbd92e325fd0acf4683b41753bc5eb63bfc1" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus 0.13.4", + "schemars", + "serde", +] + [[package]] name = "cw2" version = "1.1.2" @@ -237,7 +274,7 @@ checksum = "c6c120b24fbbf5c3bedebb97f2cc85fbfa1c3287e09223428e7e597b5293c1fa" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus", + "cw-storage-plus 1.2.0", "schemars", "semver", "serde", @@ -252,7 +289,7 @@ checksum = "526e39bb20534e25a1cd0386727f0038f4da294e5e535729ba3ef54055246abd" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-utils", + "cw-utils 1.0.3", "schemars", "serde", ] @@ -265,8 +302,8 @@ checksum = "17ad79e86ea3707229bf78df94e08732e8f713207b4a77b2699755596725e7d9" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus", - "cw2", + "cw-storage-plus 1.2.0", + "cw2 1.1.2", "cw20", "schemars", "semver", @@ -274,6 +311,47 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cw721" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c4d286625ccadc957fe480dd3bdc54ada19e0e6b5b9325379db3130569e914" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-utils 1.0.3", + "schemars", + "serde", +] + +[[package]] +name = "cw721-base-updatable" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d9c7b94747c481691e0cfbd5482f70f0a7b1738e24e59bf01e2ae0ac0d247e6" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus 0.13.4", + "cw-utils 0.13.4", + "cw2 0.13.4", + "cw721-updatable", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw721-updatable" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4e1e852df297049ac0a0dca3077ab3f83262c4d17e746738af4662eb8ff74ff" +dependencies = [ + "cosmwasm-std", + "cw-utils 0.13.4", + "schemars", + "serde", +] + [[package]] name = "der" version = "0.7.9" diff --git a/Cargo.toml b/Cargo.toml index dccc78f..a75379e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,16 @@ [package] name = "cosmos_coin" -version = "0.1.0" +version = "0.2.0" edition = "2021" +# TODO: custom serde stuff should be feature blocked + [dependencies] cosmwasm-std = "1.3.1" cw20-base = { version = "1.0.0", features = ["library"] } +cw721 = "0.18.0" +# TODO: this can be feature blocked +cw721-base-updatable = { version = "1.0.5", features = ["library"] } schemars = "0.8.12" serde = { version = "1.0.183", default-features = false, features = ["derive"] } diff --git a/src/cw20.rs b/src/fungible/cw20.rs similarity index 70% rename from src/cw20.rs rename to src/fungible/cw20.rs index e09dacf..6fcefd0 100644 --- a/src/cw20.rs +++ b/src/fungible/cw20.rs @@ -1,4 +1,4 @@ -use crate::GenericToken; +use crate::{AttributeBuilder, Fungible}; use cosmwasm_std::{to_json_binary, Addr, Attribute, CosmosMsg, Deps, StdResult, Uint128, WasmMsg}; use schemars::JsonSchema; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -12,7 +12,7 @@ pub struct Cw20Token { impl Serialize for Cw20Token { fn serialize(&self, serializer: S) -> Result where - S: Serializer + S: Serializer, { serializer.serialize_str(&self.address.as_str()) } @@ -21,7 +21,7 @@ impl Serialize for Cw20Token { impl<'de> Deserialize<'de> for Cw20Token { fn deserialize(deserializer: D) -> Result where - D: Deserializer<'de> + D: Deserializer<'de>, { String::deserialize(deserializer).map(|addr| Self::new(Addr::unchecked(addr))) } @@ -32,7 +32,12 @@ impl Cw20Token { Self { address } } - pub fn transfer_from(&self, owner: &Addr, recipient: &Addr, amount: Uint128) -> StdResult { + pub fn transfer_from( + &self, + owner: &Addr, + recipient: &Addr, + amount: Uint128, + ) -> StdResult { Ok(CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: self.address.to_string(), msg: to_json_binary(&cw20_base::msg::ExecuteMsg::TransferFrom { @@ -45,42 +50,46 @@ impl Cw20Token { } } -impl GenericToken for Cw20Token { - fn send(&self, target: &Addr, amount: Uint128) -> StdResult { +impl Fungible for Cw20Token { + fn send(&self, target: impl Into, amount: impl Into) -> StdResult { Ok(CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: self.address.to_string(), msg: to_json_binary(&cw20_base::msg::ExecuteMsg::Transfer { - recipient: target.to_string(), - amount, + recipient: target.into(), + amount: amount.into(), })?, funds: vec![], })) } - fn burn(&self, amount: Uint128) -> StdResult { + fn burn(&self, amount: impl Into) -> StdResult { Ok(CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: self.address.to_string(), - msg: to_json_binary(&cw20_base::msg::ExecuteMsg::Burn { amount })?, + msg: to_json_binary(&cw20_base::msg::ExecuteMsg::Burn { + amount: amount.into(), + })?, funds: vec![], })) } - fn balance(&self, address: &Addr, deps: Deps) -> StdResult { + fn balance(&self, address: impl Into, deps: Deps) -> StdResult { deps.querier .query_wasm_smart::( &self.address, &cw20_base::msg::QueryMsg::Balance { - address: address.to_string(), + address: address.into(), }, ) .map(|res| res.balance) } +} - fn attributes(&self) -> Vec { - vec![ +impl AttributeBuilder for Cw20Token { + fn attributes(&self) -> StdResult> { + Ok(vec![ Attribute::new("type", "cw20"), Attribute::new("address", &self.address), - ] + ]) } } @@ -92,8 +101,8 @@ struct BalanceResponse { #[cfg(test)] mod test { + use crate::fungible::Cw20Token; use cosmwasm_std::Addr; - use crate::Cw20Token; #[test] fn serde() { @@ -102,6 +111,9 @@ mod test { let expected_serialized = "\"some_token\"".to_string(); assert_eq!(got_serialized, expected_serialized); - assert_eq!(serde_json::from_str::(&expected_serialized).unwrap(), cw20); + assert_eq!( + serde_json::from_str::(&expected_serialized).unwrap(), + cw20 + ); } -} \ No newline at end of file +} diff --git a/src/fungible/info.rs b/src/fungible/info.rs new file mode 100644 index 0000000..eff9168 --- /dev/null +++ b/src/fungible/info.rs @@ -0,0 +1,77 @@ +use crate::{AttributeBuilder, Cw20Token, Fungible, NativeToken, TokenKey}; +use cosmwasm_std::{Addr, Attribute, CosmosMsg, Deps, StdResult, Uint128}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum FungibleTokenInfo { + Cw20(Cw20Token), + Native(NativeToken), +} + +impl FungibleTokenInfo { + pub fn cw20(address: Addr) -> Self { + Self::Cw20(Cw20Token::new(address)) + } + + pub fn native(denom: impl Into) -> Self { + Self::Native(NativeToken::new(denom)) + } + + pub fn is_native(&self) -> bool { + matches!(self, Self::Native(_)) + } + + pub fn is_cw20(&self) -> bool { + matches!(self, Self::Cw20(_)) + } + + pub fn key(&self) -> TokenKey { + match &self { + FungibleTokenInfo::Cw20(info) => (0, info.address.as_ref().to_string()), + FungibleTokenInfo::Native(info) => (1, info.denom.clone()), + } + } + + pub fn from_key(key: TokenKey) -> Self { + let (id, data) = key; + + match id { + 0 => Self::cw20(Addr::unchecked(data)), + _ => Self::native(data), + } + } +} + +impl Fungible for FungibleTokenInfo { + fn send(&self, target: impl Into, amount: impl Into) -> StdResult { + match self { + FungibleTokenInfo::Cw20(info) => info.send(target, amount), + FungibleTokenInfo::Native(info) => info.send(target, amount), + } + } + + fn burn(&self, amount: impl Into) -> StdResult { + match self { + FungibleTokenInfo::Cw20(info) => info.burn(amount), + FungibleTokenInfo::Native(info) => info.burn(amount), + } + } + + fn balance(&self, address: impl Into, deps: Deps) -> StdResult { + match self { + FungibleTokenInfo::Cw20(info) => info.balance(address, deps), + FungibleTokenInfo::Native(info) => info.balance(address, deps), + } + } +} + +impl AttributeBuilder for FungibleTokenInfo { + fn attributes(&self) -> StdResult> { + match self { + FungibleTokenInfo::Cw20(info) => info.attributes(), + FungibleTokenInfo::Native(info) => info.attributes(), + } + } +} diff --git a/src/fungible/mod.rs b/src/fungible/mod.rs new file mode 100644 index 0000000..2c6d6a2 --- /dev/null +++ b/src/fungible/mod.rs @@ -0,0 +1,17 @@ +use cosmwasm_std::{CosmosMsg, Deps, StdResult, Uint128}; + +mod cw20; +mod info; +mod native; +mod token; + +pub use cw20::*; +pub use info::*; +pub use native::*; +pub use token::*; + +pub trait Fungible { + fn send(&self, target: impl Into, amount: impl Into) -> StdResult; + fn burn(&self, amount: impl Into) -> StdResult; + fn balance(&self, address: impl Into, deps: Deps) -> StdResult; +} diff --git a/src/native.rs b/src/fungible/native.rs similarity index 63% rename from src/native.rs rename to src/fungible/native.rs index db13f3f..a3edb3b 100644 --- a/src/native.rs +++ b/src/fungible/native.rs @@ -1,5 +1,5 @@ -use crate::GenericToken; -use cosmwasm_std::{Addr, Attribute, BankMsg, Coin, CosmosMsg, Deps, StdResult, Uint128}; +use crate::{AttributeBuilder, Fungible}; +use cosmwasm_std::{Attribute, BankMsg, Coin, CosmosMsg, Deps, StdResult, Uint128}; use schemars::JsonSchema; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -11,7 +11,7 @@ pub struct NativeToken { impl Serialize for NativeToken { fn serialize(&self, serializer: S) -> Result where - S: Serializer + S: Serializer, { serializer.serialize_str(&self.denom) } @@ -20,7 +20,7 @@ impl Serialize for NativeToken { impl<'de> Deserialize<'de> for NativeToken { fn deserialize(deserializer: D) -> Result where - D: Deserializer<'de> + D: Deserializer<'de>, { String::deserialize(deserializer).map(|denom| Self::new(denom)) } @@ -34,43 +34,45 @@ impl NativeToken { } } -impl GenericToken for NativeToken { - fn send(&self, target: &Addr, amount: Uint128) -> StdResult { +impl Fungible for NativeToken { + fn send(&self, target: impl Into, amount: impl Into) -> StdResult { Ok(CosmosMsg::Bank(BankMsg::Send { - to_address: target.to_string(), + to_address: target.into(), amount: vec![Coin { denom: self.denom.clone(), - amount, + amount: amount.into(), }], })) } - fn burn(&self, amount: Uint128) -> StdResult { + fn burn(&self, amount: impl Into) -> StdResult { Ok(CosmosMsg::Bank(BankMsg::Burn { amount: vec![Coin { denom: self.denom.clone(), - amount, + amount: amount.into(), }], })) } - fn balance(&self, address: &Addr, deps: Deps) -> StdResult { + fn balance(&self, address: impl Into, deps: Deps) -> StdResult { deps.querier .query_balance(address, &self.denom) .map(|coin| coin.amount) } +} - fn attributes(&self) -> Vec { - vec![ +impl AttributeBuilder for NativeToken { + fn attributes(&self) -> StdResult> { + Ok(vec![ Attribute::new("type", "native"), Attribute::new("denom", &self.denom), - ] + ]) } } #[cfg(test)] mod test { - use crate::NativeToken; + use crate::fungible::NativeToken; #[test] fn serde() { @@ -79,6 +81,9 @@ mod test { let expected_serialized = "\"some_token\"".to_string(); assert_eq!(got_serialized, expected_serialized); - assert_eq!(serde_json::from_str::(&expected_serialized).unwrap(), native); + assert_eq!( + serde_json::from_str::(&expected_serialized).unwrap(), + native + ); } -} \ No newline at end of file +} diff --git a/src/token.rs b/src/fungible/token.rs similarity index 50% rename from src/token.rs rename to src/fungible/token.rs index 9ec161e..7f0750b 100644 --- a/src/token.rs +++ b/src/fungible/token.rs @@ -1,26 +1,30 @@ -use crate::{GenericToken, TokenInfo, TokenKey}; +use crate::fungible::{Fungible, FungibleTokenInfo}; +use crate::{AttributeBuilder, TokenKey}; use cosmwasm_std::{Addr, Attribute, Coin, CosmosMsg, StdResult, Uint128}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct Token { - pub info: TokenInfo, +pub struct FungibleToken { + pub info: FungibleTokenInfo, pub amount: Uint128, } -impl From for Token { +impl From for FungibleToken { fn from(value: Coin) -> Self { Self { - info: TokenInfo::native(value.denom), + info: FungibleTokenInfo::native(value.denom), amount: value.amount, } } } -impl Token { - pub fn new(info: TokenInfo, amount: Uint128) -> Self { - Self { info, amount } +impl FungibleToken { + pub fn new(info: FungibleTokenInfo, amount: impl Into) -> Self { + Self { + info, + amount: amount.into(), + } } pub fn send(&self, target: &Addr) -> StdResult { @@ -31,60 +35,67 @@ impl Token { self.info.burn(self.amount) } - pub fn attributes(&self) -> Vec { - let mut attributes = self.info.attributes(); - attributes.push(Attribute::new("amount", self.amount)); - attributes - } - pub fn key(&self) -> TokenKey { self.info.key() } pub fn from_key(key: TokenKey, amount: Uint128) -> Self { Self { - info: TokenInfo::from_key(key), + info: FungibleTokenInfo::from_key(key), amount, } } - pub fn native(denom: impl Into, amount: Uint128) -> Self { + pub fn native(denom: impl Into, amount: impl Into) -> Self { Self { - info: TokenInfo::native(denom), - amount, + info: FungibleTokenInfo::native(denom), + amount: amount.into(), } } - pub fn cw20(address: Addr, amount: Uint128) -> Self { + pub fn cw20(address: Addr, amount: impl Into) -> Self { Self { - info: TokenInfo::cw20(address), - amount, + info: FungibleTokenInfo::cw20(address), + amount: amount.into(), } } } +impl AttributeBuilder for FungibleToken { + fn attributes(&self) -> StdResult> { + let mut attributes = self.info.attributes()?; + attributes.push(Attribute::new("amount", self.amount)); + Ok(attributes) + } +} + #[cfg(test)] mod test { + use crate::*; use cosmwasm_std::Addr; - use crate::TokenInfo; - #[test] fn serde_cw20() { - let cw20 = TokenInfo::cw20(Addr::unchecked("some_token")); + let cw20 = FungibleTokenInfo::cw20(Addr::unchecked("some_token")); let got_serialized = serde_json::to_string(&cw20).unwrap(); let expected_serialized = "{\"cw20\":\"some_token\"}".to_string(); assert_eq!(got_serialized, expected_serialized); - assert_eq!(serde_json::from_str::(&expected_serialized).unwrap(), cw20); + assert_eq!( + serde_json::from_str::(&expected_serialized).unwrap(), + cw20 + ); } #[test] fn serde_native() { - let native = TokenInfo::native("some_token"); + let native = FungibleTokenInfo::native("some_token"); let got_serialized = serde_json::to_string(&native).unwrap(); let expected_serialized = "{\"native\":\"some_token\"}".to_string(); assert_eq!(got_serialized, expected_serialized); - assert_eq!(serde_json::from_str::(&expected_serialized).unwrap(), native); + assert_eq!( + serde_json::from_str::(&expected_serialized).unwrap(), + native + ); } -} \ No newline at end of file +} diff --git a/src/info.rs b/src/info.rs deleted file mode 100644 index 682152f..0000000 --- a/src/info.rs +++ /dev/null @@ -1,83 +0,0 @@ -use crate::{Cw20Token, GenericToken, NativeToken}; -use cosmwasm_std::{Addr, Attribute, CosmosMsg, Deps, StdResult, Uint128}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum TokenInfo { - Cw20(Cw20Token), - Native(NativeToken), -} - -pub type TokenKey = (u8, String); - -impl TokenInfo { - pub fn cw20(address: Addr) -> Self { - Self::Cw20(Cw20Token::new(address)) - } - - pub fn native(denom: impl Into) -> Self { - Self::Native(NativeToken::new(denom)) - } - - pub fn is_native(&self) -> bool { - match self { - TokenInfo::Cw20(_) => false, - TokenInfo::Native(_) => true - } - } - - pub fn is_cw20(&self) -> bool { - match self { - TokenInfo::Cw20(_) => true, - TokenInfo::Native(_) => false - } - } - - pub fn key(&self) -> TokenKey { - match &self { - TokenInfo::Cw20(info) => (0, info.address.as_ref().to_string()), - TokenInfo::Native(info) => (1, info.denom.clone()), - } - } - - pub fn from_key(key: TokenKey) -> Self { - let (id, data) = key; - - match id { - 0 => Self::cw20(Addr::unchecked(data)), - _ => Self::native(data), - } - } -} - -impl GenericToken for TokenInfo { - fn send(&self, target: &Addr, amount: Uint128) -> StdResult { - match self { - TokenInfo::Cw20(info) => info.send(target, amount), - TokenInfo::Native(info) => info.send(target, amount), - } - } - - fn burn(&self, amount: Uint128) -> StdResult { - match self { - TokenInfo::Cw20(info) => info.burn(amount), - TokenInfo::Native(info) => info.burn(amount), - } - } - - fn balance(&self, address: &Addr, deps: Deps) -> StdResult { - match self { - TokenInfo::Cw20(info) => info.balance(address, deps), - TokenInfo::Native(info) => info.balance(address, deps), - } - } - - fn attributes(&self) -> Vec { - match self { - TokenInfo::Cw20(info) => info.attributes(), - TokenInfo::Native(info) => info.attributes(), - } - } -} diff --git a/src/lib.rs b/src/lib.rs index 2fb3f33..0e584c8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,18 +1,32 @@ -mod cw20; -mod info; -mod native; -mod token; +pub mod fungible; +pub mod non_fungible; -pub use crate::cw20::Cw20Token; -pub use crate::info::{TokenInfo, TokenKey}; -pub use crate::native::NativeToken; -pub use crate::token::Token; -use cosmwasm_std::{Addr, Attribute, CosmosMsg, Deps, StdResult, Uint128}; +use cosmwasm_std::{to_json_string, Attribute, Response, StdResult}; +pub use fungible::*; +use serde::Serialize; -pub trait GenericToken { - fn send(&self, target: &Addr, amount: Uint128) -> StdResult; - fn burn(&self, amount: Uint128) -> StdResult; - fn balance(&self, address: &Addr, deps: Deps) -> StdResult; - fn attributes(&self) -> Vec; - // TODO: add grant function and disclaimer that CW20 granting is the one that works on smart contracts +// TODO: implement custom JsonSchema + +// TODO: implement batch send for native token +// TODO: implement Auth for native +// TODO: implement Perms for Cw20 +// TODO: implement Send from Cw20 that allows sending from a message +// TODO: implement mint for Cw20 + +pub type TokenKey = (u8, String); + +pub trait AttributeBuilder: Serialize { + fn write_json_attributes(&self, key: &str, resp: &mut Response) -> StdResult<()> { + resp.attributes.push(self.json_attributes(key)?); + Ok(()) + } + fn write_attributes(&self, resp: &mut Response) -> StdResult<()> { + resp.attributes.append(&mut self.attributes()?); + Ok(()) + } + fn json_attributes(&self, key: &str) -> StdResult { + let value = to_json_string(self)?; + Ok(Attribute::new(key, value)) + } + fn attributes(&self) -> StdResult>; } diff --git a/src/non_fungible/cw721.rs b/src/non_fungible/cw721.rs new file mode 100644 index 0000000..f7df25b --- /dev/null +++ b/src/non_fungible/cw721.rs @@ -0,0 +1,112 @@ +use crate::non_fungible::NonFungible; +use crate::AttributeBuilder; +use cosmwasm_std::{to_json_binary, Addr, Attribute, CosmosMsg, Deps, StdResult, Uint128, WasmMsg}; +use cw721::{NumTokensResponse, OwnerOfResponse, TokensResponse}; +use schemars::JsonSchema; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +#[derive(Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct Cw721Token { + pub address: Addr, +} + +impl Serialize for Cw721Token { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.address.as_str()) + } +} + +impl<'de> Deserialize<'de> for Cw721Token { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + String::deserialize(deserializer).map(|addr| Self::new(Addr::unchecked(addr))) + } +} + +impl Cw721Token { + pub fn new(address: Addr) -> Self { + Self { address } + } +} + +impl NonFungible for Cw721Token { + fn send(&self, target: impl Into, token: impl Into) -> StdResult { + Ok(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: self.address.to_string(), + msg: to_json_binary(&cw721::Cw721ExecuteMsg::TransferNft { + recipient: target.into(), + token_id: token.into(), + })?, + funds: vec![], + })) + } + + fn burn(&self, token: impl Into) -> StdResult { + Ok(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: self.address.to_string(), + msg: to_json_binary(&cw721::Cw721ExecuteMsg::Burn { + token_id: token.into(), + })?, + funds: vec![], + })) + } + + fn owner_of(&self, token: impl Into, deps: Deps) -> StdResult { + deps.querier.query_wasm_smart::( + &self.address, + &cw721::Cw721QueryMsg::OwnerOf { + token_id: token.into(), + include_expired: None, + }, + ) + } + + fn total_tokens(&self, deps: Deps) -> StdResult { + deps.querier + .query_wasm_smart::( + &self.address, + &cw721::Cw721QueryMsg::NumTokens {}, + ) + .map(|num| num.count) + } + + fn tokens( + &self, + owner: Option, + start_after: Option, + limit: Option, + deps: Deps, + ) -> StdResult> { + if let Some(owner) = owner { + deps.querier.query_wasm_smart::( + &self.address, + &cw721::Cw721QueryMsg::Tokens { + owner: owner.into(), + start_after, + limit, + }, + ) + } else { + deps.querier.query_wasm_smart::( + &self.address, + &cw721::Cw721QueryMsg::AllTokens { start_after, limit }, + ) + } + .map(|tokens| tokens.tokens) + } +} + +impl AttributeBuilder for Cw721Token { + fn attributes(&self) -> StdResult> { + Ok(vec![ + Attribute::new("type", "cw721"), + Attribute::new("address", &self.address), + ]) + } +} diff --git a/src/non_fungible/info.rs b/src/non_fungible/info.rs new file mode 100644 index 0000000..d46ee08 --- /dev/null +++ b/src/non_fungible/info.rs @@ -0,0 +1,83 @@ +use crate::non_fungible::cw721::Cw721Token; +use crate::non_fungible::NonFungible; +use crate::{AttributeBuilder, TokenKey}; +use cosmwasm_std::{Addr, Attribute, CosmosMsg, Deps, StdResult}; +use cw721::OwnerOfResponse; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum NonFungibleTokenInfo { + Base(Cw721Token), +} + +impl NonFungibleTokenInfo { + pub fn base(address: Addr) -> Self { + Self::Base(Cw721Token::new(address)) + } + + pub fn is_base(&self) -> bool { + matches!(self, Self::Base(_)) + } + + pub fn key(&self) -> TokenKey { + match &self { + Self::Base(info) => (0, info.address.to_string()), + } + } + + pub fn from_key(key: TokenKey) -> Self { + let (id, data) = key; + + match id { + _ => Self::base(Addr::unchecked(data)), + } + } +} + +impl NonFungible for NonFungibleTokenInfo { + fn send(&self, target: impl Into, token: impl Into) -> StdResult { + match self { + Self::Base(cw721) => cw721.send(target, token), + } + } + + fn burn(&self, token: impl Into) -> StdResult { + match self { + Self::Base(cw721) => cw721.burn(token), + } + } + + fn owner_of(&self, token: impl Into, deps: Deps) -> StdResult { + match self { + Self::Base(cw721) => cw721.owner_of(token, deps), + } + } + + fn total_tokens(&self, deps: Deps) -> StdResult { + match self { + Self::Base(cw721) => cw721.total_tokens(deps), + } + } + + fn tokens( + &self, + owner: Option, + start_after: Option, + limit: Option, + deps: Deps, + ) -> StdResult> { + match self { + Self::Base(cw721) => cw721.tokens(owner, start_after, limit, deps), + } + } +} + +impl AttributeBuilder for NonFungibleTokenInfo { + fn attributes(&self) -> StdResult> { + match self { + Self::Base(cw721) => cw721.attributes(), + } + } +} diff --git a/src/non_fungible/mod.rs b/src/non_fungible/mod.rs new file mode 100644 index 0000000..6378b5f --- /dev/null +++ b/src/non_fungible/mod.rs @@ -0,0 +1,20 @@ +mod cw721; +mod info; +mod token; + +use ::cw721::OwnerOfResponse; +use cosmwasm_std::{CosmosMsg, Deps, StdResult}; + +pub trait NonFungible { + fn send(&self, target: impl Into, token: impl Into) -> StdResult; + fn burn(&self, token: impl Into) -> StdResult; + fn owner_of(&self, token: impl Into, deps: Deps) -> StdResult; + fn total_tokens(&self, deps: Deps) -> StdResult; + fn tokens( + &self, + owner: Option, + start_after: Option, + limit: Option, + deps: Deps, + ) -> StdResult>; +} diff --git a/src/non_fungible/token.rs b/src/non_fungible/token.rs new file mode 100644 index 0000000..8c063b2 --- /dev/null +++ b/src/non_fungible/token.rs @@ -0,0 +1,55 @@ +use crate::non_fungible::info::NonFungibleTokenInfo; +use crate::non_fungible::NonFungible; +use crate::{AttributeBuilder, TokenKey}; +use cosmwasm_std::{Addr, Attribute, CosmosMsg, StdResult}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct NonFungibleToken { + pub info: NonFungibleTokenInfo, + pub token: String, +} + +impl NonFungibleToken { + pub fn new(info: NonFungibleTokenInfo, token: impl Into) -> Self { + Self { + info, + token: token.into(), + } + } + + pub fn send(&self, target: &Addr) -> StdResult { + self.info.send(target, &self.token) + } + + pub fn burn(&self) -> StdResult { + self.info.burn(&self.token) + } + + pub fn key(&self) -> TokenKey { + self.info.key() + } + + pub fn from_key(key: TokenKey, token: impl Into) -> Self { + Self { + info: NonFungibleTokenInfo::from_key(key), + token: token.into(), + } + } + + pub fn base(address: Addr, token: impl Into) -> Self { + Self { + info: NonFungibleTokenInfo::base(address), + token: token.into(), + } + } +} + +impl AttributeBuilder for NonFungibleToken { + fn attributes(&self) -> StdResult> { + let mut attributes = self.info.attributes()?; + attributes.push(Attribute::new("token", &self.token)); + Ok(attributes) + } +} From 7346ab6f26f6c2639af658d23bedc78ab1b001f8 Mon Sep 17 00:00:00 2001 From: Guy Garcia Date: Mon, 28 Oct 2024 13:36:48 -0400 Subject: [PATCH 2/7] fixed incorrect schema generation --- src/fungible/cw20.rs | 51 ++++++++++++++++----------- src/fungible/native.rs | 52 +++++++++++++++++----------- src/lib.rs | 1 + src/non_fungible/cw721.rs | 72 +++++++++++++++++++++++++++------------ src/non_fungible/mod.rs | 4 +++ src/non_fungible/token.rs | 19 +++++++++++ 6 files changed, 138 insertions(+), 61 deletions(-) diff --git a/src/fungible/cw20.rs b/src/fungible/cw20.rs index 6fcefd0..d3ec454 100644 --- a/src/fungible/cw20.rs +++ b/src/fungible/cw20.rs @@ -3,30 +3,11 @@ use cosmwasm_std::{to_json_binary, Addr, Attribute, CosmosMsg, Deps, StdResult, use schemars::JsonSchema; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -#[derive(Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[derive(Clone, Debug, PartialEq)] pub struct Cw20Token { pub address: Addr, } -impl Serialize for Cw20Token { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&self.address.as_str()) - } -} - -impl<'de> Deserialize<'de> for Cw20Token { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - String::deserialize(deserializer).map(|addr| Self::new(Addr::unchecked(addr))) - } -} - impl Cw20Token { pub fn new(address: Addr) -> Self { Self { address } @@ -93,6 +74,36 @@ impl AttributeBuilder for Cw20Token { } } +impl Serialize for Cw20Token { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.address.as_str()) + } +} + +impl<'de> Deserialize<'de> for Cw20Token { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + String::deserialize(deserializer).map(|addr| Self::new(Addr::unchecked(addr))) + } +} + +impl JsonSchema for Cw20Token { + fn schema_name() -> String { + "Cw20Token".to_owned() + } + fn schema_id() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed(concat!(module_path!(), "::", "Cw20Token")) + } + fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { + gen.subschema_for::() + } +} + /// For some reason the cw20 library has differing versions for types #[derive(Deserialize)] struct BalanceResponse { diff --git a/src/fungible/native.rs b/src/fungible/native.rs index a3edb3b..879d296 100644 --- a/src/fungible/native.rs +++ b/src/fungible/native.rs @@ -1,31 +1,13 @@ use crate::{AttributeBuilder, Fungible}; -use cosmwasm_std::{Attribute, BankMsg, Coin, CosmosMsg, Deps, StdResult, Uint128}; +use cosmwasm_std::{Addr, Attribute, BankMsg, Coin, CosmosMsg, Deps, StdResult, Uint128}; use schemars::JsonSchema; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -#[derive(Clone, Debug, PartialEq, JsonSchema)] +#[derive(Clone, Debug, PartialEq)] pub struct NativeToken { pub denom: String, } -impl Serialize for NativeToken { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&self.denom) - } -} - -impl<'de> Deserialize<'de> for NativeToken { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - String::deserialize(deserializer).map(|denom| Self::new(denom)) - } -} - impl NativeToken { pub fn new(denom: impl Into) -> Self { Self { @@ -70,6 +52,36 @@ impl AttributeBuilder for NativeToken { } } +impl Serialize for NativeToken { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.denom) + } +} + +impl<'de> Deserialize<'de> for NativeToken { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + String::deserialize(deserializer).map(|denom| Self::new(denom)) + } +} + +impl JsonSchema for NativeToken { + fn schema_name() -> String { + "NativeToken".to_owned() + } + fn schema_id() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed(concat!(module_path!(), "::", "NativeToken")) + } + fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { + gen.subschema_for::() + } +} + #[cfg(test)] mod test { use crate::fungible::NativeToken; diff --git a/src/lib.rs b/src/lib.rs index 0e584c8..309ef05 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ pub mod non_fungible; use cosmwasm_std::{to_json_string, Attribute, Response, StdResult}; pub use fungible::*; +pub use non_fungible::*; use serde::Serialize; // TODO: implement custom JsonSchema diff --git a/src/non_fungible/cw721.rs b/src/non_fungible/cw721.rs index f7df25b..7fdfc05 100644 --- a/src/non_fungible/cw721.rs +++ b/src/non_fungible/cw721.rs @@ -1,34 +1,15 @@ use crate::non_fungible::NonFungible; use crate::AttributeBuilder; -use cosmwasm_std::{to_json_binary, Addr, Attribute, CosmosMsg, Deps, StdResult, Uint128, WasmMsg}; +use cosmwasm_std::{to_json_binary, Addr, Attribute, CosmosMsg, Deps, StdResult, WasmMsg}; use cw721::{NumTokensResponse, OwnerOfResponse, TokensResponse}; use schemars::JsonSchema; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -#[derive(Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[derive(Clone, Debug, PartialEq)] pub struct Cw721Token { pub address: Addr, } -impl Serialize for Cw721Token { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&self.address.as_str()) - } -} - -impl<'de> Deserialize<'de> for Cw721Token { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - String::deserialize(deserializer).map(|addr| Self::new(Addr::unchecked(addr))) - } -} - impl Cw721Token { pub fn new(address: Addr) -> Self { Self { address } @@ -110,3 +91,52 @@ impl AttributeBuilder for Cw721Token { ]) } } + +impl Serialize for Cw721Token { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.address.as_str()) + } +} + +impl<'de> Deserialize<'de> for Cw721Token { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + String::deserialize(deserializer).map(|addr| Self::new(Addr::unchecked(addr))) + } +} + +impl JsonSchema for Cw721Token { + fn schema_name() -> String { + "Cw721Token".to_owned() + } + fn schema_id() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed(concat!(module_path!(), "::", "Cw721Token")) + } + fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { + gen.subschema_for::() + } +} + +#[cfg(test)] +mod test { + use crate::non_fungible::cw721::Cw721Token; + use cosmwasm_std::Addr; + + #[test] + fn serde() { + let cw721 = Cw721Token::new(Addr::unchecked("some_token")); + let got_serialized = serde_json::to_string(&cw721).unwrap(); + let expected_serialized = "\"some_token\"".to_string(); + + assert_eq!(got_serialized, expected_serialized); + assert_eq!( + serde_json::from_str::(&expected_serialized).unwrap(), + cw721 + ); + } +} diff --git a/src/non_fungible/mod.rs b/src/non_fungible/mod.rs index 6378b5f..85b30c1 100644 --- a/src/non_fungible/mod.rs +++ b/src/non_fungible/mod.rs @@ -2,6 +2,10 @@ mod cw721; mod info; mod token; +pub use cw721::*; +pub use info::*; +pub use token::*; + use ::cw721::OwnerOfResponse; use cosmwasm_std::{CosmosMsg, Deps, StdResult}; diff --git a/src/non_fungible/token.rs b/src/non_fungible/token.rs index 8c063b2..b9abfc8 100644 --- a/src/non_fungible/token.rs +++ b/src/non_fungible/token.rs @@ -53,3 +53,22 @@ impl AttributeBuilder for NonFungibleToken { Ok(attributes) } } + +#[cfg(test)] +mod test { + use crate::*; + use cosmwasm_std::Addr; + + #[test] + fn serde_base() { + let base = NonFungibleTokenInfo::base(Addr::unchecked("some_token")); + let got_serialized = serde_json::to_string(&base).unwrap(); + let expected_serialized = "{\"base\":\"some_token\"}".to_string(); + + assert_eq!(got_serialized, expected_serialized); + assert_eq!( + serde_json::from_str::(&expected_serialized).unwrap(), + base + ); + } +} From 5d89e197e736d071f23f91c7417f6f7c26b95885 Mon Sep 17 00:00:00 2001 From: Guy Garcia Date: Tue, 29 Oct 2024 07:26:02 -0400 Subject: [PATCH 3/7] added a wasm exec helper function and implemented allowance functions for cw20 --- src/fungible/cw20.rs | 138 ++++++++++++++++++++++++++++++-------- src/lib.rs | 17 ++++- src/non_fungible/cw721.rs | 24 +++---- 3 files changed, 136 insertions(+), 43 deletions(-) diff --git a/src/fungible/cw20.rs b/src/fungible/cw20.rs index d3ec454..e90a31e 100644 --- a/src/fungible/cw20.rs +++ b/src/fungible/cw20.rs @@ -1,5 +1,8 @@ -use crate::{AttributeBuilder, Fungible}; -use cosmwasm_std::{to_json_binary, Addr, Attribute, CosmosMsg, Deps, StdResult, Uint128, WasmMsg}; +use crate::{execute_wasm, AttributeBuilder, Fungible}; +use cosmwasm_std::{ + to_json_binary, Addr, Attribute, Binary, CosmosMsg, Deps, StdResult, Uint128, WasmMsg, +}; +use cw721::Expiration; use schemars::JsonSchema; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -13,44 +16,125 @@ impl Cw20Token { Self { address } } - pub fn transfer_from( + /// Send transaction that uses the cw20 Send smart contract interface + pub fn contract_send( &self, - owner: &Addr, - recipient: &Addr, - amount: Uint128, + contract: impl Into, + amount: impl Into, + msg: Binary, ) -> StdResult { - Ok(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: self.address.to_string(), - msg: to_json_binary(&cw20_base::msg::ExecuteMsg::TransferFrom { - owner: owner.to_string(), - recipient: recipient.to_string(), - amount, - })?, - funds: vec![], - })) + execute_wasm( + &self.address, + &cw20_base::msg::ExecuteMsg::Send { + contract: contract.into(), + msg, + amount: amount.into(), + }, + ) + } + + /// Send from transaction that uses the cw20 Send smart contract interface + pub fn contract_send_from( + &self, + owner: impl Into, + contract: impl Into, + amount: impl Into, + msg: Binary, + ) -> StdResult { + execute_wasm( + &self.address, + &cw20_base::msg::ExecuteMsg::SendFrom { + owner: owner.into(), + contract: contract.into(), + msg, + amount: amount.into(), + }, + ) + } + + /// Standard send from another owner's address + pub fn send_from( + &self, + owner: impl Into, + recipient: impl Into, + amount: impl Into, + ) -> StdResult { + execute_wasm( + &self.address, + &cw20_base::msg::ExecuteMsg::TransferFrom { + owner: owner.into(), + recipient: recipient.into(), + amount: amount.into(), + }, + ) + } + + /// Burn from another owner's address + pub fn burn_from( + &self, + owner: impl Into, + amount: impl Into, + ) -> StdResult { + execute_wasm( + &self.address, + &cw20_base::msg::ExecuteMsg::BurnFrom { + owner: owner.into(), + amount: amount.into(), + }, + ) + } + + pub fn increase_allowance( + &self, + spender: impl Into, + amount: impl Into, + expires: Option, + ) -> StdResult { + execute_wasm( + &self.address, + &cw20_base::msg::ExecuteMsg::IncreaseAllowance { + spender: spender.into(), + amount: amount.into(), + expires, + }, + ) + } + + pub fn decrease_allowance( + &self, + spender: impl Into, + amount: impl Into, + expires: Option, + ) -> StdResult { + execute_wasm( + &self.address, + &cw20_base::msg::ExecuteMsg::DecreaseAllowance { + spender: spender.into(), + amount: amount.into(), + expires, + }, + ) } } impl Fungible for Cw20Token { fn send(&self, target: impl Into, amount: impl Into) -> StdResult { - Ok(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: self.address.to_string(), - msg: to_json_binary(&cw20_base::msg::ExecuteMsg::Transfer { + execute_wasm( + &self.address, + &cw20_base::msg::ExecuteMsg::Transfer { recipient: target.into(), amount: amount.into(), - })?, - funds: vec![], - })) + }, + ) } fn burn(&self, amount: impl Into) -> StdResult { - Ok(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: self.address.to_string(), - msg: to_json_binary(&cw20_base::msg::ExecuteMsg::Burn { + execute_wasm( + &self.address, + &cw20_base::msg::ExecuteMsg::Burn { amount: amount.into(), - })?, - funds: vec![], - })) + }, + ) } fn balance(&self, address: impl Into, deps: Deps) -> StdResult { diff --git a/src/lib.rs b/src/lib.rs index 309ef05..d8fb0db 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,13 +1,13 @@ pub mod fungible; pub mod non_fungible; -use cosmwasm_std::{to_json_string, Attribute, Response, StdResult}; +use cosmwasm_std::{ + to_json_binary, to_json_string, Attribute, CosmosMsg, Response, StdResult, WasmMsg, +}; pub use fungible::*; pub use non_fungible::*; use serde::Serialize; -// TODO: implement custom JsonSchema - // TODO: implement batch send for native token // TODO: implement Auth for native // TODO: implement Perms for Cw20 @@ -16,6 +16,17 @@ use serde::Serialize; pub type TokenKey = (u8, String); +pub(crate) fn execute_wasm( + contract: impl Into, + msg: &T, +) -> StdResult { + Ok(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: contract.into(), + msg: to_json_binary(msg)?, + funds: vec![], + })) +} + pub trait AttributeBuilder: Serialize { fn write_json_attributes(&self, key: &str, resp: &mut Response) -> StdResult<()> { resp.attributes.push(self.json_attributes(key)?); diff --git a/src/non_fungible/cw721.rs b/src/non_fungible/cw721.rs index 7fdfc05..1a3cc3a 100644 --- a/src/non_fungible/cw721.rs +++ b/src/non_fungible/cw721.rs @@ -1,5 +1,5 @@ use crate::non_fungible::NonFungible; -use crate::AttributeBuilder; +use crate::{execute_wasm, AttributeBuilder}; use cosmwasm_std::{to_json_binary, Addr, Attribute, CosmosMsg, Deps, StdResult, WasmMsg}; use cw721::{NumTokensResponse, OwnerOfResponse, TokensResponse}; use schemars::JsonSchema; @@ -18,24 +18,22 @@ impl Cw721Token { impl NonFungible for Cw721Token { fn send(&self, target: impl Into, token: impl Into) -> StdResult { - Ok(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: self.address.to_string(), - msg: to_json_binary(&cw721::Cw721ExecuteMsg::TransferNft { + execute_wasm( + &self.address, + &cw721::Cw721ExecuteMsg::TransferNft { recipient: target.into(), token_id: token.into(), - })?, - funds: vec![], - })) + }, + ) } fn burn(&self, token: impl Into) -> StdResult { - Ok(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: self.address.to_string(), - msg: to_json_binary(&cw721::Cw721ExecuteMsg::Burn { + execute_wasm( + &self.address, + &cw721::Cw721ExecuteMsg::Burn { token_id: token.into(), - })?, - funds: vec![], - })) + }, + ) } fn owner_of(&self, token: impl Into, deps: Deps) -> StdResult { From 6417ab76cec81057f929647c8d54c5c371bfa511 Mon Sep 17 00:00:00 2001 From: Guy Garcia Date: Tue, 29 Oct 2024 09:05:15 -0400 Subject: [PATCH 4/7] added a generic minting interface and cw721 is now generic --- src/non_fungible/cw721.rs | 68 ++++++++++++++++++++++++++++----------- src/non_fungible/info.rs | 23 ++++++++++--- src/non_fungible/mod.rs | 8 +++++ 3 files changed, 76 insertions(+), 23 deletions(-) diff --git a/src/non_fungible/cw721.rs b/src/non_fungible/cw721.rs index 1a3cc3a..06be064 100644 --- a/src/non_fungible/cw721.rs +++ b/src/non_fungible/cw721.rs @@ -1,26 +1,40 @@ use crate::non_fungible::NonFungible; use crate::{execute_wasm, AttributeBuilder}; -use cosmwasm_std::{to_json_binary, Addr, Attribute, CosmosMsg, Deps, StdResult, WasmMsg}; +use cosmwasm_std::{Addr, Attribute, CosmosMsg, Deps, Empty, StdResult, WasmMsg}; use cw721::{NumTokensResponse, OwnerOfResponse, TokensResponse}; use schemars::JsonSchema; +use serde::de::DeserializeOwned; use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::marker::PhantomData; + +pub type BaseCw721Token = Cw721Token; #[derive(Clone, Debug, PartialEq)] -pub struct Cw721Token { +pub struct Cw721Token { pub address: Addr, + phantom_data: PhantomData<(T, E, Q)>, } -impl Cw721Token { +impl Cw721Token { pub fn new(address: Addr) -> Self { - Self { address } + Self { + address, + phantom_data: Default::default(), + } } } -impl NonFungible for Cw721Token { +impl NonFungible for Cw721Token +where + T: Serialize + DeserializeOwned + Clone, + E: Serialize + DeserializeOwned + Clone, + Q: Serialize + DeserializeOwned + Clone + JsonSchema, +{ + type Extension = T; fn send(&self, target: impl Into, token: impl Into) -> StdResult { execute_wasm( &self.address, - &cw721::Cw721ExecuteMsg::TransferNft { + &cw721_base::ExecuteMsg::::TransferNft { recipient: target.into(), token_id: token.into(), }, @@ -30,8 +44,26 @@ impl NonFungible for Cw721Token { fn burn(&self, token: impl Into) -> StdResult { execute_wasm( &self.address, - &cw721::Cw721ExecuteMsg::Burn { + &cw721_base::ExecuteMsg::::Burn { + token_id: token.into(), + }, + ) + } + + fn mint( + &self, + token: impl Into, + owner: impl Into, + token_uri: Option, + extension: Self::Extension, + ) -> StdResult { + execute_wasm( + &self.address, + &cw721_base::ExecuteMsg::::Mint { token_id: token.into(), + owner: owner.into(), + token_uri, + extension, }, ) } @@ -39,7 +71,7 @@ impl NonFungible for Cw721Token { fn owner_of(&self, token: impl Into, deps: Deps) -> StdResult { deps.querier.query_wasm_smart::( &self.address, - &cw721::Cw721QueryMsg::OwnerOf { + &cw721_base::QueryMsg::::OwnerOf { token_id: token.into(), include_expired: None, }, @@ -50,7 +82,7 @@ impl NonFungible for Cw721Token { deps.querier .query_wasm_smart::( &self.address, - &cw721::Cw721QueryMsg::NumTokens {}, + &cw721_base::QueryMsg::::NumTokens {}, ) .map(|num| num.count) } @@ -65,7 +97,7 @@ impl NonFungible for Cw721Token { if let Some(owner) = owner { deps.querier.query_wasm_smart::( &self.address, - &cw721::Cw721QueryMsg::Tokens { + &cw721_base::QueryMsg::::Tokens { owner: owner.into(), start_after, limit, @@ -74,14 +106,14 @@ impl NonFungible for Cw721Token { } else { deps.querier.query_wasm_smart::( &self.address, - &cw721::Cw721QueryMsg::AllTokens { start_after, limit }, + &cw721_base::QueryMsg::::AllTokens { start_after, limit }, ) } .map(|tokens| tokens.tokens) } } -impl AttributeBuilder for Cw721Token { +impl AttributeBuilder for Cw721Token { fn attributes(&self) -> StdResult> { Ok(vec![ Attribute::new("type", "cw721"), @@ -90,7 +122,7 @@ impl AttributeBuilder for Cw721Token { } } -impl Serialize for Cw721Token { +impl Serialize for Cw721Token { fn serialize(&self, serializer: S) -> Result where S: Serializer, @@ -99,7 +131,7 @@ impl Serialize for Cw721Token { } } -impl<'de> Deserialize<'de> for Cw721Token { +impl<'de, T, E, Q> Deserialize<'de> for Cw721Token { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, @@ -108,7 +140,7 @@ impl<'de> Deserialize<'de> for Cw721Token { } } -impl JsonSchema for Cw721Token { +impl JsonSchema for Cw721Token { fn schema_name() -> String { "Cw721Token".to_owned() } @@ -122,18 +154,18 @@ impl JsonSchema for Cw721Token { #[cfg(test)] mod test { - use crate::non_fungible::cw721::Cw721Token; + use crate::non_fungible::cw721::BaseCw721Token; use cosmwasm_std::Addr; #[test] fn serde() { - let cw721 = Cw721Token::new(Addr::unchecked("some_token")); + let cw721 = BaseCw721Token::new(Addr::unchecked("some_token")); let got_serialized = serde_json::to_string(&cw721).unwrap(); let expected_serialized = "\"some_token\"".to_string(); assert_eq!(got_serialized, expected_serialized); assert_eq!( - serde_json::from_str::(&expected_serialized).unwrap(), + serde_json::from_str::(&expected_serialized).unwrap(), cw721 ); } diff --git a/src/non_fungible/info.rs b/src/non_fungible/info.rs index d46ee08..50b65e1 100644 --- a/src/non_fungible/info.rs +++ b/src/non_fungible/info.rs @@ -1,7 +1,6 @@ -use crate::non_fungible::cw721::Cw721Token; use crate::non_fungible::NonFungible; -use crate::{AttributeBuilder, TokenKey}; -use cosmwasm_std::{Addr, Attribute, CosmosMsg, Deps, StdResult}; +use crate::{AttributeBuilder, BaseCw721Token, TokenKey}; +use cosmwasm_std::{Addr, Attribute, CosmosMsg, Deps, Empty, StdResult}; use cw721::OwnerOfResponse; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -9,12 +8,12 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum NonFungibleTokenInfo { - Base(Cw721Token), + Base(BaseCw721Token), } impl NonFungibleTokenInfo { pub fn base(address: Addr) -> Self { - Self::Base(Cw721Token::new(address)) + Self::Base(BaseCw721Token::new(address)) } pub fn is_base(&self) -> bool { @@ -37,6 +36,8 @@ impl NonFungibleTokenInfo { } impl NonFungible for NonFungibleTokenInfo { + type Extension = Empty; + fn send(&self, target: impl Into, token: impl Into) -> StdResult { match self { Self::Base(cw721) => cw721.send(target, token), @@ -49,6 +50,18 @@ impl NonFungible for NonFungibleTokenInfo { } } + fn mint( + &self, + token: impl Into, + owner: impl Into, + token_uri: Option, + extension: Self::Extension, + ) -> StdResult { + match self { + Self::Base(cw721) => cw721.mint(token, owner, token_uri, extension), + } + } + fn owner_of(&self, token: impl Into, deps: Deps) -> StdResult { match self { Self::Base(cw721) => cw721.owner_of(token, deps), diff --git a/src/non_fungible/mod.rs b/src/non_fungible/mod.rs index 85b30c1..8b6c617 100644 --- a/src/non_fungible/mod.rs +++ b/src/non_fungible/mod.rs @@ -10,8 +10,16 @@ use ::cw721::OwnerOfResponse; use cosmwasm_std::{CosmosMsg, Deps, StdResult}; pub trait NonFungible { + type Extension; fn send(&self, target: impl Into, token: impl Into) -> StdResult; fn burn(&self, token: impl Into) -> StdResult; + fn mint( + &self, + token: impl Into, + owner: impl Into, + token_uri: Option, + extension: Self::Extension, + ) -> StdResult; fn owner_of(&self, token: impl Into, deps: Deps) -> StdResult; fn total_tokens(&self, deps: Deps) -> StdResult; fn tokens( From 6783bd1a13ff10cd7591c16e0c5f99a99ae44118 Mon Sep 17 00:00:00 2001 From: Guy Garcia Date: Tue, 29 Oct 2024 09:05:37 -0400 Subject: [PATCH 5/7] added cw20 mint support --- src/fungible/cw20.rs | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/fungible/cw20.rs b/src/fungible/cw20.rs index e90a31e..7384a20 100644 --- a/src/fungible/cw20.rs +++ b/src/fungible/cw20.rs @@ -1,8 +1,6 @@ use crate::{execute_wasm, AttributeBuilder, Fungible}; -use cosmwasm_std::{ - to_json_binary, Addr, Attribute, Binary, CosmosMsg, Deps, StdResult, Uint128, WasmMsg, -}; -use cw721::Expiration; +use cosmwasm_std::{Addr, Attribute, Binary, CosmosMsg, Deps, StdResult, Uint128, WasmMsg}; +use cw_utils::Expiration; use schemars::JsonSchema; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -115,6 +113,20 @@ impl Cw20Token { }, ) } + + pub fn mint( + &self, + recipient: impl Into, + amount: impl Into, + ) -> StdResult { + execute_wasm( + &self.address, + &cw20_base::msg::ExecuteMsg::Mint { + recipient: recipient.into(), + amount: amount.into(), + }, + ) + } } impl Fungible for Cw20Token { From e12899f30b6893b6b6b5ac83eadbdf8701495c35 Mon Sep 17 00:00:00 2001 From: Guy Garcia Date: Tue, 29 Oct 2024 09:05:48 -0400 Subject: [PATCH 6/7] deps and cleanup --- Cargo.lock | 136 ++++++++++++++++++++++++++++++++++++++++++++++++++--- Cargo.toml | 4 +- src/lib.rs | 6 --- 3 files changed, 133 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0e21e4a..70a8040 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -84,8 +84,10 @@ name = "cosmos_coin" version = "0.2.0" dependencies = [ "cosmwasm-std", + "cw-utils 1.0.3", "cw20-base", - "cw721", + "cw721 0.18.0", + "cw721-base 0.18.0", "cw721-base-updatable", "schemars", "serde", @@ -94,12 +96,11 @@ dependencies = [ [[package]] name = "cosmwasm-crypto" -version = "1.5.5" +version = "1.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd50718a2b6830ce9eb5d465de5a018a12e71729d66b70807ce97e6dd14f931d" +checksum = "58535cbcd599b3c193e3967c8292fe1dbbb5de7c2a2d87380661091dd4744044" dependencies = [ "digest 0.10.7", - "ecdsa", "ed25519-zebra", "k256", "rand_core 0.6.4", @@ -205,6 +206,41 @@ dependencies = [ "zeroize", ] +[[package]] +name = "cw-address-like" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451a4691083a88a3c0630a8a88799e9d4cd6679b7ce8ff22b8da2873ff31d380" +dependencies = [ + "cosmwasm-std", +] + +[[package]] +name = "cw-ownable" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093dfb4520c48b5848274dd88ea99e280a04bc08729603341c7fb0d758c74321" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-address-like", + "cw-ownable-derive", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "thiserror", +] + +[[package]] +name = "cw-ownable-derive" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d3bf2e0f341bb6cc100d7d441d31cf713fbd3ce0c511f91e79f14b40a889af" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "cw-storage-plus" version = "0.13.4" @@ -216,6 +252,17 @@ dependencies = [ "serde", ] +[[package]] +name = "cw-storage-plus" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b6f91c0b94481a3e9ef1ceb183c37d00764f8751e39b45fc09f4d9b970d469" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + [[package]] name = "cw-storage-plus" version = "1.2.0" @@ -239,6 +286,21 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cw-utils" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6a84c6c1c0acc3616398eba50783934bd6c964bad6974241eaee3460c8f5b26" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw2 0.16.0", + "schemars", + "semver", + "serde", + "thiserror", +] + [[package]] name = "cw-utils" version = "1.0.3" @@ -266,6 +328,19 @@ dependencies = [ "serde", ] +[[package]] +name = "cw2" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91398113b806f4d2a8d5f8d05684704a20ffd5968bf87e3473e1973710b884ad" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.16.0", + "schemars", + "serde", +] + [[package]] name = "cw2" version = "1.1.2" @@ -311,6 +386,19 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cw721" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94a1ea6e6277bdd6dfc043a9b1380697fe29d6e24b072597439523658d21d791" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-utils 0.16.0", + "schemars", + "serde", +] + [[package]] name = "cw721" version = "0.18.0" @@ -324,6 +412,42 @@ dependencies = [ "serde", ] +[[package]] +name = "cw721-base" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77518e27431d43214cff4cdfbd788a7508f68d9b1f32389e6fce513e7eaccbef" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.16.0", + "cw-utils 0.16.0", + "cw2 0.16.0", + "cw721 0.16.0", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw721-base" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da518d9f68bfda7d972cbaca2e8fcf04651d0edc3de72b04ae2bcd9289c81614" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-ownable", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw2 1.1.2", + "cw721 0.18.0", + "cw721-base 0.16.0", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "cw721-base-updatable" version = "1.0.5" @@ -529,9 +653,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "k256" -version = "0.13.1" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" dependencies = [ "cfg-if", "ecdsa", diff --git a/Cargo.toml b/Cargo.toml index a75379e..20d0452 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,8 +7,10 @@ edition = "2021" [dependencies] cosmwasm-std = "1.3.1" -cw20-base = { version = "1.0.0", features = ["library"] } +cw-utils = "1.0.3" cw721 = "0.18.0" +cw20-base = { version = "1.0.0", features = ["library"] } +cw721-base = { version = "0.18.0", features = ["library"] } # TODO: this can be feature blocked cw721-base-updatable = { version = "1.0.5", features = ["library"] } schemars = "0.8.12" diff --git a/src/lib.rs b/src/lib.rs index d8fb0db..02ea3c0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,12 +8,6 @@ pub use fungible::*; pub use non_fungible::*; use serde::Serialize; -// TODO: implement batch send for native token -// TODO: implement Auth for native -// TODO: implement Perms for Cw20 -// TODO: implement Send from Cw20 that allows sending from a message -// TODO: implement mint for Cw20 - pub type TokenKey = (u8, String); pub(crate) fn execute_wasm( From 19c02bfe80dbcd349ee2e6489ad33131f6ee6e78 Mon Sep 17 00:00:00 2001 From: Guy Garcia Date: Wed, 6 Nov 2024 10:08:27 -0400 Subject: [PATCH 7/7] clippy cleanup --- src/fungible/cw20.rs | 4 ++-- src/fungible/native.rs | 2 +- src/non_fungible/cw721.rs | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/fungible/cw20.rs b/src/fungible/cw20.rs index 7384a20..943517b 100644 --- a/src/fungible/cw20.rs +++ b/src/fungible/cw20.rs @@ -1,5 +1,5 @@ use crate::{execute_wasm, AttributeBuilder, Fungible}; -use cosmwasm_std::{Addr, Attribute, Binary, CosmosMsg, Deps, StdResult, Uint128, WasmMsg}; +use cosmwasm_std::{Addr, Attribute, Binary, CosmosMsg, Deps, StdResult, Uint128}; use cw_utils::Expiration; use schemars::JsonSchema; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -175,7 +175,7 @@ impl Serialize for Cw20Token { where S: Serializer, { - serializer.serialize_str(&self.address.as_str()) + serializer.serialize_str(self.address.as_str()) } } diff --git a/src/fungible/native.rs b/src/fungible/native.rs index 879d296..5348108 100644 --- a/src/fungible/native.rs +++ b/src/fungible/native.rs @@ -66,7 +66,7 @@ impl<'de> Deserialize<'de> for NativeToken { where D: Deserializer<'de>, { - String::deserialize(deserializer).map(|denom| Self::new(denom)) + String::deserialize(deserializer).map(Self::new) } } diff --git a/src/non_fungible/cw721.rs b/src/non_fungible/cw721.rs index 06be064..7376298 100644 --- a/src/non_fungible/cw721.rs +++ b/src/non_fungible/cw721.rs @@ -1,6 +1,6 @@ use crate::non_fungible::NonFungible; use crate::{execute_wasm, AttributeBuilder}; -use cosmwasm_std::{Addr, Attribute, CosmosMsg, Deps, Empty, StdResult, WasmMsg}; +use cosmwasm_std::{Addr, Attribute, CosmosMsg, Deps, Empty, StdResult}; use cw721::{NumTokensResponse, OwnerOfResponse, TokensResponse}; use schemars::JsonSchema; use serde::de::DeserializeOwned; @@ -98,7 +98,7 @@ where deps.querier.query_wasm_smart::( &self.address, &cw721_base::QueryMsg::::Tokens { - owner: owner.into(), + owner, start_after, limit, }, @@ -127,7 +127,7 @@ impl Serialize for Cw721Token { where S: Serializer, { - serializer.serialize_str(&self.address.as_str()) + serializer.serialize_str(self.address.as_str()) } }