Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ICS721 v2: cw721 v19 with creator and onchain extensioms (aka metadata) #103

Merged
merged 21 commits into from
Aug 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
394 changes: 271 additions & 123 deletions Cargo.lock

Large diffs are not rendered by default.

11 changes: 8 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,23 @@ edition = "2021"
[workspace.dependencies]
# common libs
anyhow = "^1.0"
bech32 = "^0.9"
bech32 = "^0.11"
cosmwasm-std = "^1.5"
cosmwasm-schema = "^1.5"
cosmwasm-storage = "^1.5"
cw-ownable = "^0.5"
cw-paginate-storage = { version = "^2.4", git = "https://github.com/DA0-DA0/dao-contracts.git" }
cw-storage-plus = "1.1"
cw2 = "1.1"
cw721 = { git = "https://github.com/CosmWasm/cw-nfts", branch = "main"} # TODO switch to version 0.18.1/0.19.0, once released
cw721 = { git = "https://github.com/public-awesome/cw-nfts", branch = "minor_changes"} # TODO switch to version 0.18.1/0.19.0, once released
cw721-016 = { version = "0.16.0", package = "cw721" }
cw721-base = { git = "https://github.com/CosmWasm/cw-nfts", branch = "main"} # TODO switch to version 0.18.1/0.19.0, once released
cw721-017 = { git = "https://github.com/public-awesome/cw-nfts", branch = "release/v0.17.0", package = "cw721"} # TODO switch to version 0.18.1/0.19.0, once released
cw721-018 = { git = "https://github.com/public-awesome/cw-nfts", branch = "release/v0.18.0", package = "cw721"} # TODO switch to version 0.18.1/0.19.0, once released
cw721-metadata-onchain = { git = "https://github.com/public-awesome/cw-nfts", branch = "minor_changes", package = "cw721-metadata-onchain"} # TODO switch to version 0.18.1/0.19.0, once released
cw721-base = { git = "https://github.com/public-awesome/cw-nfts", branch = "minor_changes"} # TODO switch to version 0.18.1/0.19.0, once released
cw721-base-016 = { version = "0.16.0", package = "cw721-base" }
cw721-base-017 = { git = "https://github.com/public-awesome/cw-nfts", branch = "release/v0.17.0", package = "cw721-base"} # TODO switch to version 0.18.1/0.19.0, once released
cw721-base-018 = { git = "https://github.com/public-awesome/cw-nfts", branch = "release/v0.18.0", package = "cw721-base"} # TODO switch to version 0.18.1/0.19.0, once released
cw-ics721-incoming-proxy = { git = "https://github.com/arkprotocol/cw-ics721-proxy.git", tag = "v0.1.0" }
cw-ics721-incoming-proxy-base = { git = "https://github.com/arkprotocol/cw-ics721-proxy.git", tag = "v0.1.0" }
cw-ics721-outgoing-proxy-rate-limit = { git = "https://github.com/arkprotocol/cw-ics721-proxy.git", tag = "v0.1.0" }
Expand Down
3 changes: 2 additions & 1 deletion contracts/cw721-tester/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ cosmwasm-schema = { workspace = true }
cw-storage-plus = { workspace = true }
cw2 = { workspace = true }
thiserror = { workspace = true }
cw721-base = { workspace = true, features = [ "library" ] }
cw721-metadata-onchain = { workspace = true, features = [ "library" ] }
cw721 = { workspace = true}
55 changes: 39 additions & 16 deletions contracts/cw721-tester/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,38 @@
use cosmwasm_schema::cw_serde;
#[cfg(not(feature = "library"))]
use cosmwasm_std::entry_point;
use cosmwasm_std::{Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult};
use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response};
use cw2::set_contract_version;
use cw721_base::{msg, ContractError, Extension};
use cw721::{
error::Cw721ContractError,
msg::Cw721InstantiateMsg,
traits::{Cw721Execute, Cw721Query},
DefaultOptionalCollectionExtensionMsg,
};
use cw721_metadata_onchain::Cw721MetadataContract;
use cw_storage_plus::Item;

pub type ExecuteMsg = msg::ExecuteMsg<Extension, Empty>;
pub type QueryMsg = msg::QueryMsg<Empty>;
pub type ExecuteMsg = cw721_metadata_onchain::msg::ExecuteMsg;
pub type QueryMsg = cw721_metadata_onchain::msg::QueryMsg;

#[cw_serde]
pub struct InstantiateMsg {
/// Name of the NFT contract
pub name: String,
/// Symbol of the NFT contract
pub symbol: String,
pub minter: String,
/// Optional extension of the collection metadata
pub collection_info_extension: DefaultOptionalCollectionExtensionMsg,

/// The minter is the only one who can create new NFTs.
/// This is designed for a base NFT that is controlled by an external program
/// or contract. You will likely replace this with custom logic in custom NFTs
pub minter: Option<String>,

/// Sets the creator of collection. The creator is the only one eligible to update `CollectionInfo`.
pub creator: Option<String>,

pub withdraw_address: Option<String>,
/// An address which will be unable receive NFT on `TransferNft` message
/// If `TransferNft` message attempts sending to banned recipient
/// it will fail with an out-of-gas error.
Expand All @@ -31,17 +50,21 @@ pub fn instantiate(
env: Env,
info: MessageInfo,
msg: InstantiateMsg,
) -> Result<Response, ContractError> {
let response = cw721_base::entry::instantiate(
) -> Result<Response, Cw721ContractError> {
let response = Cw721MetadataContract::default().instantiate_with_version(
deps.branch(),
env,
info,
msg::InstantiateMsg {
&env,
&info,
Cw721InstantiateMsg {
name: msg.name,
symbol: msg.symbol,
minter: Some(msg.minter),
minter: msg.minter,
withdraw_address: None,
collection_info_extension: msg.collection_info_extension,
creator: msg.creator,
},
CONTRACT_NAME,
CONTRACT_VERSION,
)?;
BANNED_RECIPIENT.save(deps.storage, &msg.banned_recipient)?;
set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
Expand All @@ -55,7 +78,7 @@ pub fn execute(
env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
) -> Result<Response, Cw721ContractError> {
match msg.clone() {
ExecuteMsg::TransferNft { recipient, .. } => {
if recipient == BANNED_RECIPIENT.load(deps.storage)? {
Expand All @@ -64,13 +87,13 @@ pub fn execute(
panic!("gotem")
// loop {}
}
cw721_base::entry::execute(deps, env, info, msg)
Cw721MetadataContract::default().execute(deps, &env, &info, msg)
}
_ => cw721_base::entry::execute(deps, env, info, msg),
_ => Cw721MetadataContract::default().execute(deps, &env, &info, msg),
}
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult<Binary> {
cw721_base::entry::query(deps, env, msg)
pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result<Binary, Cw721ContractError> {
Cw721MetadataContract::default().query(deps, &env, msg)
}
32 changes: 23 additions & 9 deletions contracts/ics721-base-tester/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
#[cfg(not(feature = "library"))]
use cosmwasm_std::entry_point;
use cosmwasm_std::{
to_json_binary, Binary, Deps, DepsMut, Env, IbcMsg, IbcTimeout, MessageInfo, Response,
to_json_binary, Binary, Deps, DepsMut, Empty, Env, IbcMsg, IbcTimeout, MessageInfo, Response,
StdResult, WasmMsg,
};
use cw2::set_contract_version;
use cw721::{DefaultOptionalCollectionExtensionMsg, DefaultOptionalNftExtensionMsg};
use ics721_types::ibc_types::{IbcOutgoingMsg, NonFungibleTokenPacketData};

use crate::{
Expand Down Expand Up @@ -63,7 +64,8 @@ pub fn execute(
}

mod receive_callbacks {
use cosmwasm_std::{ensure_eq, from_json, DepsMut, MessageInfo, Response};
use cosmwasm_std::{ensure_eq, from_json, DepsMut, Empty, MessageInfo, Response};
use cw721::{DefaultOptionalCollectionExtension, DefaultOptionalNftExtension};
use ics721_types::{
ibc_types::NonFungibleTokenPacketData,
types::{Ics721AckCallbackMsg, Ics721ReceiveCallbackMsg, Ics721Status},
Expand All @@ -77,7 +79,7 @@ mod receive_callbacks {

pub(crate) fn handle_receive_cw_callback(
deps: DepsMut,
_msg: cw721::Cw721ReceiveMsg,
_msg: cw721::receiver::Cw721ReceiveMsg,
) -> Result<Response, ContractError> {
// We got the callback, so its working
CW721_RECEIVE.save(deps.storage, &"success".to_string())?;
Expand Down Expand Up @@ -128,11 +130,15 @@ mod receive_callbacks {

NFT_CONTRACT.save(deps.storage, &deps.api.addr_validate(&nft_contract)?)?;

let owner: Option<cw721::OwnerOfResponse> = deps
let owner: Option<cw721::msg::OwnerOfResponse> = deps
.querier
.query_wasm_smart::<cw721::OwnerOfResponse>(
.query_wasm_smart::<cw721::msg::OwnerOfResponse>(
nft_contract,
&cw721::Cw721QueryMsg::OwnerOf {
&cw721::msg::Cw721QueryMsg::<
DefaultOptionalNftExtension,
DefaultOptionalCollectionExtension,
Empty,
>::OwnerOf {
token_id: packet.token_ids[0].clone().into(),
include_expired: None,
},
Expand Down Expand Up @@ -173,9 +179,13 @@ mod receive_callbacks {

let owner = deps
.querier
.query_wasm_smart::<cw721::OwnerOfResponse>(
.query_wasm_smart::<cw721::msg::OwnerOfResponse>(
nft_contract,
&cw721::Cw721QueryMsg::OwnerOf {
&cw721::msg::Cw721QueryMsg::<
DefaultOptionalNftExtension,
DefaultOptionalCollectionExtension,
Empty,
>::OwnerOf {
token_id: packet.token_ids[0].clone().into(),
include_expired: None,
},
Expand Down Expand Up @@ -217,7 +227,11 @@ fn execute_send_nft(
// Send send msg to cw721, send it to ics721 with the correct msg.
let msg = WasmMsg::Execute {
contract_addr: cw721,
msg: to_json_binary(&cw721::Cw721ExecuteMsg::SendNft {
msg: to_json_binary(&cw721::msg::Cw721ExecuteMsg::<
DefaultOptionalNftExtensionMsg,
DefaultOptionalCollectionExtensionMsg,
Empty,
>::SendNft {
contract: ics721,
token_id,
msg: to_json_binary(&IbcOutgoingMsg {
Expand Down
2 changes: 1 addition & 1 deletion contracts/ics721-base-tester/src/msg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ pub struct InstantiateMsg {
#[allow(clippy::large_enum_variant)] // `data` field is a bit large
// for clippy's taste.
pub enum ExecuteMsg {
ReceiveNft(cw721::Cw721ReceiveMsg),
ReceiveNft(cw721::receiver::Cw721ReceiveMsg),
Ics721ReceiveCallback(ics721_types::types::Ics721ReceiveCallbackMsg),
Ics721AckCallback(ics721_types::types::Ics721AckCallbackMsg),
SendNft {
Expand Down
4 changes: 2 additions & 2 deletions contracts/ics721-base-tester/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ pub const ACK_MODE: Item<AckMode> = Item::new("ack_mode");
pub const LAST_ACK: Item<AckMode> = Item::new("ack_mode");

pub const ICS721: Item<Addr> = Item::new("ics721");
pub const SENT_CALLBACK: Item<Option<cw721::OwnerOfResponse>> = Item::new("sent");
pub const RECEIVED_CALLBACK: Item<Option<cw721::OwnerOfResponse>> = Item::new("received");
pub const SENT_CALLBACK: Item<Option<cw721::msg::OwnerOfResponse>> = Item::new("sent");
pub const RECEIVED_CALLBACK: Item<Option<cw721::msg::OwnerOfResponse>> = Item::new("received");
pub const NFT_CONTRACT: Item<Addr> = Item::new("nft_contract");
pub const CW721_RECEIVE: Item<String> = Item::new("cw721_received");
4 changes: 3 additions & 1 deletion contracts/sg-ics721/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ library = []
cosmwasm-std = { workspace = true, features = ["ibc3"] }
cosmwasm-schema = { workspace = true }
cw2 = { workspace = true }
cw721 = { workspace = true }
ics721 = { workspace = true }
ics721-types = { workspace = true }
sg-std = { workspace = true}
Expand All @@ -31,7 +32,8 @@ cw-multi-test = { workspace = true }
cw-pause-once = { workspace = true }
cw-storage-plus = { workspace = true }
cw721 = { workspace = true}
cw721-018 = { workspace = true}
cw-ics721-incoming-proxy-base = { workspace = true }
cw-ics721-outgoing-proxy-rate-limit = { workspace = true }
cw721-base = { workspace = true}
cw721-base-018 = { workspace = true}
sha2 = { workspace = true }
64 changes: 54 additions & 10 deletions contracts/sg-ics721/src/execute.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
use cosmwasm_std::{from_json, to_json_binary, Addr, Binary, Deps, DepsMut, Env, StdResult};
use cosmwasm_std::{
from_json, to_json_binary, Addr, Binary, ContractInfoResponse, Deps, DepsMut, Env, StdResult,
};
use cw721::{CollectionExtension, RoyaltyInfo};
use ics721::{execute::Ics721Execute, state::CollectionData, utils::get_collection_data};
use ics721_types::token_types::Class;

use sg721::RoyaltyInfoResponse;
use sg721_base::msg::{CollectionInfoResponse, QueryMsg};

use crate::state::{SgCollectionData, SgIcs721Contract, STARGAZE_ICON_PLACEHOLDER};
use crate::state::{SgIcs721Contract, STARGAZE_ICON_PLACEHOLDER};

impl Ics721Execute for SgIcs721Contract {
type ClassData = SgCollectionData;
type ClassData = CollectionData;

/// sg-ics721 sends custom SgCollectionData, basically it extends ics721-base::state::CollectionData with additional collection_info.
fn get_class_data(&self, deps: &DepsMut, sender: &Addr) -> StdResult<Option<Self::ClassData>> {
Expand All @@ -16,28 +20,52 @@ impl Ics721Execute for SgIcs721Contract {
contract_info,
name,
symbol,
extension: _, // ignore extension coming from standard cw721, since sg721 has its own extension (collection info)
num_tokens,
} = get_collection_data(deps, sender)?;
let collection_info: CollectionInfoResponse = deps
.querier
.query_wasm_smart(sender, &QueryMsg::CollectionInfo {})?;
let royalty_info = collection_info.royalty_info.map(|r| RoyaltyInfo {
payment_address: Addr::unchecked(r.payment_address),
share: r.share,
});
let extension = Some(CollectionExtension {
description: collection_info.description,
image: collection_info.image,
external_link: collection_info.external_link,
explicit_content: collection_info.explicit_content,
start_trading_time: collection_info.start_trading_time,
royalty_info,
});

Comment on lines +29 to +40
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here sg-ics721 overrides default implementation and gets collection data from sg721-base via CollectionInfo query.

Ok(Some(SgCollectionData {
Ok(Some(CollectionData {
owner,
contract_info,
name,
symbol,
num_tokens,
collection_info: Some(collection_info),
extension,
}))
}

fn init_msg(&self, deps: Deps, env: &Env, class: &Class) -> StdResult<Binary> {
fn init_msg(
&self,
deps: Deps,
env: &Env,
class: &Class,
cw721_admin: Option<String>,
) -> StdResult<Binary> {
// ics721 creator is used, in case no source owner in class data is provided (e.g. due to nft-transfer module).
let ics721_contract_info = deps
let ContractInfoResponse { creator, admin, .. } = deps
.querier
.query_wasm_contract_info(env.contract.address.to_string())?;
// use by default ClassId, in case there's no class data with name and symbol
let cw721_admin_or_ics721_admin_or_ics721_creator = cw721_admin
.clone()
.or_else(|| admin.clone())
.or_else(|| Some(creator.clone()))
.unwrap();
let mut instantiate_msg = sg721::InstantiateMsg {
name: class.id.clone().into(),
symbol: class.id.clone().into(),
Expand All @@ -46,10 +74,10 @@ impl Ics721Execute for SgIcs721Contract {
// source owner could be: 1. regular wallet, 2. contract, or 3. multisig
// bech32 calculation for 2. and 3. leads to unknown address
// therefore, we use ics721 creator as owner
creator: ics721_contract_info.creator,
creator: cw721_admin_or_ics721_admin_or_ics721_creator.clone(),
description: "".to_string(),
// use Stargaze icon as placeholder
image: STARGAZE_ICON_PLACEHOLDER.to_string(),
// remaining props is set below, in case there's collection data
image: STARGAZE_ICON_PLACEHOLDER.to_string(), // use Stargaze icon as placeholder
external_link: None,
explicit_content: None,
start_trading_time: None,
Expand All @@ -65,6 +93,22 @@ impl Ics721Execute for SgIcs721Contract {
if let Some(collection_data) = collection_data {
instantiate_msg.name = collection_data.name;
instantiate_msg.symbol = collection_data.symbol;
if let Some(collection_info_extension_msg) =
collection_data.extension.map(|ext| sg721::CollectionInfo {
creator: cw721_admin_or_ics721_admin_or_ics721_creator.clone(),
description: ext.description,
image: ext.image,
external_link: ext.external_link,
explicit_content: ext.explicit_content,
start_trading_time: ext.start_trading_time,
royalty_info: ext.royalty_info.map(|r| RoyaltyInfoResponse {
payment_address: cw721_admin_or_ics721_admin_or_ics721_creator, // r.payment_address cant be used, since it is from another chain
share: r.share,
}),
})
{
instantiate_msg.collection_info = collection_info_extension_msg;
}
}

to_json_binary(&instantiate_msg)
Expand Down
18 changes: 0 additions & 18 deletions contracts/sg-ics721/src/state.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,5 @@
use cosmwasm_schema::cw_serde;
use cosmwasm_std::ContractInfoResponse;
use sg721_base::msg::CollectionInfoResponse;

pub const STARGAZE_ICON_PLACEHOLDER: &str =
"ipfs://bafkreie5vwrm5zts4wiq6ebtopmztgl5qzyl4uszyllgwpaizyc5w2uycm";

/// Collection data provided by the (source) cw721 contract. This is pass as optional class data during interchain transfer to target chain.
/// ICS721 on target chain is free to use this data or not. Lik in case of `sg721-base` it uses owner for defining creator in collection info.
#[cw_serde]
pub struct SgCollectionData {
// CW721 specific props, copied from ics721::state::CollectionData
pub owner: Option<String>,
pub contract_info: Option<ContractInfoResponse>,
pub name: String,
pub symbol: String,
pub num_tokens: Option<u64>,
/// SG721 specific collection info
pub collection_info: Option<CollectionInfoResponse>,
}

#[derive(Default)]
pub struct SgIcs721Contract {}
Loading
Loading