Skip to content

Commit

Permalink
Merge pull request #103 from arkprotocol/onchain_metadata_v19
Browse files Browse the repository at this point in the history
ICS721 v2: cw721 v19 with creator and onchain extensioms (aka metadata)
  • Loading branch information
taitruong authored Aug 17, 2024
2 parents 8e8de23 + 2d08afe commit d182e22
Show file tree
Hide file tree
Showing 38 changed files with 3,089 additions and 1,259 deletions.
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,
});

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

0 comments on commit d182e22

Please sign in to comment.