From 9e01765b74aa7942b3026e6db4f5fea944b79ba5 Mon Sep 17 00:00:00 2001 From: Art3mix Date: Fri, 30 Dec 2022 21:42:38 +0200 Subject: [PATCH 01/15] Added handling class data nad token data --- contracts/cw-ics721-bridge/src/contract.rs | 105 +++++++++++--------- contracts/cw-ics721-bridge/src/ibc.rs | 16 +-- contracts/cw-ics721-bridge/src/ibc_tests.rs | 8 +- contracts/cw-ics721-bridge/src/state.rs | 12 ++- 4 files changed, 80 insertions(+), 61 deletions(-) diff --git a/contracts/cw-ics721-bridge/src/contract.rs b/contracts/cw-ics721-bridge/src/contract.rs index 10433756..fe892c0c 100644 --- a/contracts/cw-ics721-bridge/src/contract.rs +++ b/contracts/cw-ics721-bridge/src/contract.rs @@ -11,10 +11,11 @@ use crate::{ ibc::{NonFungibleTokenPacketData, INSTANTIATE_CW721_REPLY_ID, INSTANTIATE_PROXY_REPLY_ID}, msg::{CallbackMsg, ExecuteMsg, IbcOutgoingMsg, InstantiateMsg, MigrateMsg, QueryMsg}, state::{ - UniversalNftInfoResponse, CLASS_ID_TO_CLASS_URI, CLASS_ID_TO_NFT_CONTRACT, CW721_CODE_ID, - NFT_CONTRACT_TO_CLASS_ID, OUTGOING_CLASS_TOKEN_TO_CHANNEL, PO, PROXY, + UniversalNftInfoResponse, CLASS_ID_TO_CLASS, CLASS_ID_TO_NFT_CONTRACT, + CLASS_TOKEN_ID_TO_TOKEN_METADATA, CW721_CODE_ID, NFT_CONTRACT_TO_CLASS, + OUTGOING_CLASS_TOKEN_TO_CHANNEL, PO, PROXY, }, - token_types::{ClassId, Token, TokenId, VoucherCreation, VoucherRedemption}, + token_types::{Class, ClassId, Token, TokenId, VoucherCreation, VoucherRedemption}, }; const CONTRACT_NAME: &str = "crates.io:cw-ics721-bridge"; @@ -81,13 +82,13 @@ fn execute_callback( callback_create_vouchers(deps, env, receiver, create) } CallbackMsg::RedeemVouchers { receiver, redeem } => { - callback_redeem_vouchers(deps.as_ref(), receiver, redeem) + callback_redeem_vouchers(deps, receiver, redeem) } CallbackMsg::Mint { class_id, tokens, receiver, - } => callback_mint(deps.as_ref(), class_id, tokens, receiver), + } => callback_mint(deps, class_id, tokens, receiver), CallbackMsg::Conjunction { operands } => Ok(Response::default().add_messages(operands)), } @@ -144,28 +145,31 @@ fn receive_nft( ) -> Result { let sender = deps.api.addr_validate(&sender)?; let msg: IbcOutgoingMsg = from_binary(&msg)?; - - let class_id = if NFT_CONTRACT_TO_CLASS_ID.has(deps.storage, info.sender.clone()) { - NFT_CONTRACT_TO_CLASS_ID.load(deps.storage, info.sender.clone())? - } else { - let class_id = ClassId::new(info.sender.to_string()); - // If we do not yet have a class ID for this contract, it is a - // local NFT and its class ID is its conract address. - NFT_CONTRACT_TO_CLASS_ID.save(deps.storage, info.sender.clone(), &class_id)?; - CLASS_ID_TO_NFT_CONTRACT.save(deps.storage, class_id.clone(), &info.sender)?; - // We set class level metadata to None for local NFTs. - // - // Merging and usage of this PR may change that: - // - CLASS_ID_TO_CLASS_URI.save(deps.storage, class_id.clone(), &None)?; - // TODO: set metadata - class_id + let class = NFT_CONTRACT_TO_CLASS.may_load(deps.storage, info.sender.clone())?; + + let class = match class { + Some(class) => class, + None => { + // If we do not yet have a class ID for this contract, it is a + // local NFT and its class ID is its conract address. + + // We set class level metadata and URI to None for local NFTs. + let class = Class { + id: ClassId::new(info.sender.to_string()), + uri: None, + data: None, + }; + + NFT_CONTRACT_TO_CLASS.save(deps.storage, info.sender.clone(), &class)?; + CLASS_ID_TO_NFT_CONTRACT.save(deps.storage, class.id.clone(), &info.sender)?; + + // Merging and usage of this PR may change that: + // + CLASS_ID_TO_CLASS.save(deps.storage, class.id.clone(), &class)?; + class + } }; - let class_uri = CLASS_ID_TO_CLASS_URI - .may_load(deps.storage, class_id.clone())? - .flatten(); - let UniversalNftInfoResponse { token_uri, .. } = deps.querier.query_wasm_smart( info.sender, &cw721::Cw721QueryMsg::NftInfo { @@ -174,13 +178,13 @@ fn receive_nft( )?; let ibc_message = NonFungibleTokenPacketData { - class_id: class_id.clone(), - class_uri, - class_data: None, // TODO: check for values we need to forward from a source chain. + class_id: class.id.clone(), + class_uri: class.uri, + class_data: class.data, // TODO: check for values we need to forward from a source chain. token_ids: vec![token_id.clone()], token_uris: token_uri.map(|uri| vec![uri]), - token_data: None, + token_data: None, // we set it to None for local NFT for now. sender: sender.into_string(), receiver: msg.receiver, @@ -193,30 +197,37 @@ fn receive_nft( OUTGOING_CLASS_TOKEN_TO_CHANNEL.save( deps.storage, - (class_id.clone(), token_id.clone()), + (class.id.clone(), token_id.clone()), &msg.channel_id, )?; Ok(Response::default() .add_attribute("method", "execute_receive_nft") .add_attribute("token_id", token_id) - .add_attribute("class_id", class_id) + .add_attribute("class_id", class.id) .add_attribute("channel_id", msg.channel_id) .add_message(ibc_message)) } fn callback_mint( - deps: Deps, + deps: DepsMut, class_id: ClassId, tokens: Vec, receiver: String, ) -> Result { let receiver = deps.api.addr_validate(&receiver)?; - let cw721_addr = CLASS_ID_TO_NFT_CONTRACT.load(deps.storage, class_id)?; + let cw721_addr = CLASS_ID_TO_NFT_CONTRACT.load(deps.storage, class_id.clone())?; let mint = tokens .into_iter() - .map(|Token { id, uri, data: _ }| { + .map(|Token { id, uri, data }| { + // We save the metadata here. + CLASS_TOKEN_ID_TO_TOKEN_METADATA.save( + deps.storage, + (class_id.clone(), id.clone()), + &data, + )?; + let msg = cw721_base::msg::ExecuteMsg::::Mint(cw721_base::MintMsg { token_id: id.into(), token_uri: uri, @@ -273,8 +284,7 @@ fn callback_create_vouchers( // ID we have already seen comes in with new metadata, we assume // that the metadata has been updated on the source chain and // update it for the class ID locally as well. - CLASS_ID_TO_CLASS_URI.save(deps.storage, class.id.clone(), &class.uri)?; - // TODO: refactor and store data here as well. + CLASS_ID_TO_CLASS.save(deps.storage, class.id.clone(), &class)?; let mint = WasmMsg::Execute { contract_addr: env.contract.address.into_string(), @@ -293,17 +303,21 @@ fn callback_create_vouchers( } fn callback_redeem_vouchers( - deps: Deps, + deps: DepsMut, receiver: String, redeem: VoucherRedemption, ) -> Result { let VoucherRedemption { class, token_ids } = redeem; - let nft_contract = CLASS_ID_TO_NFT_CONTRACT.load(deps.storage, class.id)?; + let nft_contract = CLASS_ID_TO_NFT_CONTRACT.load(deps.storage, class.id.clone())?; let receiver = deps.api.addr_validate(&receiver)?; Ok(Response::default().add_messages( token_ids .into_iter() .map(|token_id| { + // TODO: Remove metadata here + CLASS_TOKEN_ID_TO_TOKEN_METADATA + .remove(deps.storage, (class.id.clone(), token_id.clone())); + Ok(WasmMsg::Execute { contract_addr: nft_contract.to_string(), msg: to_binary(&cw721::Cw721ExecuteMsg::TransferNft { @@ -336,19 +350,17 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { } } -fn query_class_id_for_nft_contract(deps: Deps, contract: String) -> StdResult> { +fn query_class_id_for_nft_contract(deps: Deps, contract: String) -> StdResult> { let contract = deps.api.addr_validate(&contract)?; - NFT_CONTRACT_TO_CLASS_ID.may_load(deps.storage, contract) + NFT_CONTRACT_TO_CLASS.may_load(deps.storage, contract) } fn query_nft_contract_for_class_id(deps: Deps, class_id: String) -> StdResult> { CLASS_ID_TO_NFT_CONTRACT.may_load(deps.storage, ClassId::new(class_id)) } -fn query_metadata(deps: Deps, class_id: String) -> StdResult> { - Ok(CLASS_ID_TO_CLASS_URI - .may_load(deps.storage, ClassId::new(class_id))? - .flatten()) +fn query_metadata(deps: Deps, class_id: String) -> StdResult> { + Ok(CLASS_ID_TO_CLASS.may_load(deps.storage, ClassId::new(class_id))?) } fn query_owner( @@ -484,9 +496,10 @@ mod tests { receive_nft(deps.as_mut(), info, token_id, sender, msg).unwrap(); - let class_uri = CLASS_ID_TO_CLASS_URI + let class = CLASS_ID_TO_CLASS .load(deps.as_ref().storage, ClassId::new(NFT_ADDR)) .unwrap(); - assert_eq!(class_uri, None); + assert_eq!(class.uri, None); + assert_eq!(class.data, None); } } diff --git a/contracts/cw-ics721-bridge/src/ibc.rs b/contracts/cw-ics721-bridge/src/ibc.rs index 3aba6e94..cdc3436f 100644 --- a/contracts/cw-ics721-bridge/src/ibc.rs +++ b/contracts/cw-ics721-bridge/src/ibc.rs @@ -14,10 +14,10 @@ use crate::{ ibc_helpers::{ack_fail, ack_success, try_get_ack_error, validate_order_and_version}, ibc_packet_receive::receive_ibc_packet, state::{ - CLASS_ID_TO_NFT_CONTRACT, INCOMING_CLASS_TOKEN_TO_CHANNEL, NFT_CONTRACT_TO_CLASS_ID, + CLASS_ID_TO_NFT_CONTRACT, INCOMING_CLASS_TOKEN_TO_CHANNEL, NFT_CONTRACT_TO_CLASS, OUTGOING_CLASS_TOKEN_TO_CHANNEL, PROXY, }, - token_types::{ClassId, TokenId}, + token_types::{Class, ClassId, TokenId}, ContractError, }; @@ -252,15 +252,19 @@ pub fn reply(deps: DepsMut, _env: Env, reply: Reply) -> Result contract mappings. - CLASS_ID_TO_NFT_CONTRACT.save(deps.storage, class_id.clone(), &cw721_addr)?; - NFT_CONTRACT_TO_CLASS_ID.save(deps.storage, cw721_addr.clone(), &class_id)?; + CLASS_ID_TO_NFT_CONTRACT.save(deps.storage, class.id.clone(), &cw721_addr)?; + NFT_CONTRACT_TO_CLASS.save(deps.storage, cw721_addr.clone(), &class)?; Ok(Response::default() .add_attribute("method", "instantiate_cw721_reply") - .add_attribute("class_id", class_id) + .add_attribute("class_id", class.id) .add_attribute("cw721_addr", cw721_addr)) } INSTANTIATE_PROXY_REPLY_ID => { diff --git a/contracts/cw-ics721-bridge/src/ibc_tests.rs b/contracts/cw-ics721-bridge/src/ibc_tests.rs index 76388395..0eead8a2 100644 --- a/contracts/cw-ics721-bridge/src/ibc_tests.rs +++ b/contracts/cw-ics721-bridge/src/ibc_tests.rs @@ -14,7 +14,7 @@ use crate::{ }, ibc_helpers::{ack_fail, ack_success, try_get_ack_error}, msg::{InstantiateMsg, QueryMsg}, - state::{CLASS_ID_TO_NFT_CONTRACT, NFT_CONTRACT_TO_CLASS_ID, PO}, + state::{CLASS_ID_TO_NFT_CONTRACT, NFT_CONTRACT_TO_CLASS, PO}, token_types::{ClassId, TokenId}, ContractError, }; @@ -159,15 +159,15 @@ fn test_reply_cw721() { ] ); - let class_id = NFT_CONTRACT_TO_CLASS_ID + let class = NFT_CONTRACT_TO_CLASS .load(deps.as_ref().storage, Addr::unchecked("cosmos2contract")) .unwrap(); let nft = CLASS_ID_TO_NFT_CONTRACT - .load(deps.as_ref().storage, class_id.clone()) + .load(deps.as_ref().storage, class.id.clone()) .unwrap(); assert_eq!(nft, Addr::unchecked("cosmos2contract")); - assert_eq!(class_id.to_string(), "wasm.address1/channel-10/address2"); + assert_eq!(class.id.to_string(), "wasm.address1/channel-10/address2"); } #[test] diff --git a/contracts/cw-ics721-bridge/src/state.rs b/contracts/cw-ics721-bridge/src/state.rs index 6aa686dd..ff15b876 100644 --- a/contracts/cw-ics721-bridge/src/state.rs +++ b/contracts/cw-ics721-bridge/src/state.rs @@ -1,9 +1,9 @@ -use cosmwasm_std::{Addr, Empty}; +use cosmwasm_std::{Addr, Empty, Binary}; use cw_pause_once::PauseOrchestrator; use cw_storage_plus::{Item, Map}; use serde::Deserialize; -use crate::token_types::{ClassId, TokenId}; +use crate::token_types::{ClassId, TokenId, Class}; /// The code ID we will use for instantiating new cw721s. pub const CW721_CODE_ID: Item = Item::new("a"); @@ -16,17 +16,19 @@ pub const PO: PauseOrchestrator = PauseOrchestrator::new("c", "d"); /// contract we have instantiated for that classID. pub const CLASS_ID_TO_NFT_CONTRACT: Map = Map::new("e"); /// Maps cw721 contracts to the classID they were instantiated for. -pub const NFT_CONTRACT_TO_CLASS_ID: Map = Map::new("f"); +pub const NFT_CONTRACT_TO_CLASS: Map = Map::new("f"); -/// Maps between classIDs and classUris. We need to keep this state +/// Maps between classIDs and classs. We need to keep this state /// ourselves as cw721 contracts do not have class-level metadata. -pub const CLASS_ID_TO_CLASS_URI: Map> = Map::new("g"); +pub const CLASS_ID_TO_CLASS: Map = Map::new("g"); /// Maps (class ID, token ID) -> local channel ID. Used to determine /// the local channel that NFTs have been sent out on. pub const OUTGOING_CLASS_TOKEN_TO_CHANNEL: Map<(ClassId, TokenId), String> = Map::new("h"); /// Same as above, but for NFTs arriving at this contract. pub const INCOMING_CLASS_TOKEN_TO_CHANNEL: Map<(ClassId, TokenId), String> = Map::new("i"); +/// metadata of a token (class id, token id) -> metadata +pub const CLASS_TOKEN_ID_TO_TOKEN_METADATA: Map<(ClassId, TokenId), Option> = Map::new("j"); #[derive(Deserialize)] pub struct UniversalNftInfoResponse { From 0be63636a6558a99088ec9872bd19185d63d4d73 Mon Sep 17 00:00:00 2001 From: Art3mix Date: Fri, 30 Dec 2022 21:55:26 +0200 Subject: [PATCH 02/15] fmt/clippy --- contracts/cw-ics721-bridge/src/contract.rs | 4 ++-- contracts/cw-ics721-bridge/src/state.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/cw-ics721-bridge/src/contract.rs b/contracts/cw-ics721-bridge/src/contract.rs index fe892c0c..e19e4162 100644 --- a/contracts/cw-ics721-bridge/src/contract.rs +++ b/contracts/cw-ics721-bridge/src/contract.rs @@ -314,7 +314,7 @@ fn callback_redeem_vouchers( token_ids .into_iter() .map(|token_id| { - // TODO: Remove metadata here + // We remove the metadata here CLASS_TOKEN_ID_TO_TOKEN_METADATA .remove(deps.storage, (class.id.clone(), token_id.clone())); @@ -360,7 +360,7 @@ fn query_nft_contract_for_class_id(deps: Deps, class_id: String) -> StdResult StdResult> { - Ok(CLASS_ID_TO_CLASS.may_load(deps.storage, ClassId::new(class_id))?) + CLASS_ID_TO_CLASS.may_load(deps.storage, ClassId::new(class_id)) } fn query_owner( diff --git a/contracts/cw-ics721-bridge/src/state.rs b/contracts/cw-ics721-bridge/src/state.rs index ff15b876..84476fae 100644 --- a/contracts/cw-ics721-bridge/src/state.rs +++ b/contracts/cw-ics721-bridge/src/state.rs @@ -1,9 +1,9 @@ -use cosmwasm_std::{Addr, Empty, Binary}; +use cosmwasm_std::{Addr, Binary, Empty}; use cw_pause_once::PauseOrchestrator; use cw_storage_plus::{Item, Map}; use serde::Deserialize; -use crate::token_types::{ClassId, TokenId, Class}; +use crate::token_types::{Class, ClassId, TokenId}; /// The code ID we will use for instantiating new cw721s. pub const CW721_CODE_ID: Item = Item::new("a"); From 32f416ccf810a9726f613f1610286db7b00fa1e1 Mon Sep 17 00:00:00 2001 From: Art3miX <40179351+Art3miX@users.noreply.github.com> Date: Sat, 31 Dec 2022 01:54:54 +0200 Subject: [PATCH 03/15] Apply suggestions from code review Co-authored-by: ekez <30676292+0xekez@users.noreply.github.com> --- contracts/cw-ics721-bridge/src/contract.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/contracts/cw-ics721-bridge/src/contract.rs b/contracts/cw-ics721-bridge/src/contract.rs index e19e4162..bebbd462 100644 --- a/contracts/cw-ics721-bridge/src/contract.rs +++ b/contracts/cw-ics721-bridge/src/contract.rs @@ -180,11 +180,11 @@ fn receive_nft( let ibc_message = NonFungibleTokenPacketData { class_id: class.id.clone(), class_uri: class.uri, - class_data: class.data, // TODO: check for values we need to forward from a source chain. + class_data: class.data, token_ids: vec![token_id.clone()], token_uris: token_uri.map(|uri| vec![uri]), - token_data: None, // we set it to None for local NFT for now. + token_data: None, // Like class_uri and class_data, token_data is none for local NFTs. sender: sender.into_string(), receiver: msg.receiver, @@ -221,7 +221,6 @@ fn callback_mint( let mint = tokens .into_iter() .map(|Token { id, uri, data }| { - // We save the metadata here. CLASS_TOKEN_ID_TO_TOKEN_METADATA.save( deps.storage, (class_id.clone(), id.clone()), @@ -350,7 +349,7 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { } } -fn query_class_id_for_nft_contract(deps: Deps, contract: String) -> StdResult> { +fn query_class_for_nft_contract(deps: Deps, contract: String) -> StdResult> { let contract = deps.api.addr_validate(&contract)?; NFT_CONTRACT_TO_CLASS.may_load(deps.storage, contract) } From b3283f70603c8fc992b6e170d0f53299779025ca Mon Sep 17 00:00:00 2001 From: Art3mix Date: Sat, 31 Dec 2022 02:23:08 +0200 Subject: [PATCH 04/15] zeke review changes --- contracts/cw-ics721-bridge/src/contract.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/contracts/cw-ics721-bridge/src/contract.rs b/contracts/cw-ics721-bridge/src/contract.rs index bebbd462..598d4102 100644 --- a/contracts/cw-ics721-bridge/src/contract.rs +++ b/contracts/cw-ics721-bridge/src/contract.rs @@ -221,6 +221,7 @@ fn callback_mint( let mint = tokens .into_iter() .map(|Token { id, uri, data }| { + // We save token metadata here because it is part of the mint process CLASS_TOKEN_ID_TO_TOKEN_METADATA.save( deps.storage, (class_id.clone(), id.clone()), @@ -313,7 +314,7 @@ fn callback_redeem_vouchers( token_ids .into_iter() .map(|token_id| { - // We remove the metadata here + // token metadata is set in mint, on voucher creation, and removed here on voucher redemption CLASS_TOKEN_ID_TO_TOKEN_METADATA .remove(deps.storage, (class.id.clone(), token_id.clone())); @@ -333,9 +334,7 @@ fn callback_redeem_vouchers( #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { - QueryMsg::ClassId { contract } => { - to_binary(&query_class_id_for_nft_contract(deps, contract)?) - } + QueryMsg::ClassId { contract } => to_binary(&query_class_for_nft_contract(deps, contract)?), QueryMsg::NftContract { class_id } => { to_binary(&query_nft_contract_for_class_id(deps, class_id)?) } From 000b336dafdfd362d068699ecb0193beb27a9d54 Mon Sep 17 00:00:00 2001 From: Art3mix Date: Sat, 31 Dec 2022 03:09:04 +0200 Subject: [PATCH 05/15] added memo --- contracts/cw-ics721-bridge/src/contract.rs | 4 ++++ contracts/cw-ics721-bridge/src/ibc.rs | 2 ++ contracts/cw-ics721-bridge/src/ibc_packet_receive.rs | 8 +++++++- contracts/cw-ics721-bridge/src/ibc_tests.rs | 1 + contracts/cw-ics721-bridge/src/integration_tests.rs | 2 ++ contracts/cw-ics721-bridge/src/msg.rs | 2 ++ 6 files changed, 18 insertions(+), 1 deletion(-) diff --git a/contracts/cw-ics721-bridge/src/contract.rs b/contracts/cw-ics721-bridge/src/contract.rs index 598d4102..55e38d2b 100644 --- a/contracts/cw-ics721-bridge/src/contract.rs +++ b/contracts/cw-ics721-bridge/src/contract.rs @@ -188,6 +188,7 @@ fn receive_nft( sender: sender.into_string(), receiver: msg.receiver, + memo: msg.memo, }; let ibc_message = IbcMsg::SendPacket { channel_id: msg.channel_id.clone(), @@ -448,6 +449,7 @@ mod tests { receiver: "callum".to_string(), channel_id: "channel-1".to_string(), timeout: IbcTimeout::with_timestamp(Timestamp::from_seconds(42)), + memo: None, }) .unwrap(); @@ -468,6 +470,7 @@ mod tests { token_uris: Some(vec!["https://moonphase.is/image.svg".to_string()]), sender, receiver: "callum".to_string(), + memo: None, }) .unwrap() })) @@ -489,6 +492,7 @@ mod tests { receiver: "ekez".to_string(), channel_id: "channel-1".to_string(), timeout: IbcTimeout::with_timestamp(Timestamp::from_nanos(42)), + memo: None, }) .unwrap(); diff --git a/contracts/cw-ics721-bridge/src/ibc.rs b/contracts/cw-ics721-bridge/src/ibc.rs index cdc3436f..bd298805 100644 --- a/contracts/cw-ics721-bridge/src/ibc.rs +++ b/contracts/cw-ics721-bridge/src/ibc.rs @@ -63,6 +63,8 @@ pub struct NonFungibleTokenPacketData { /// The address that should receive the tokens on the receiving /// chain. pub receiver: String, + /// Memo to add custom string to the msg + pub memo: Option, } #[cfg_attr(not(feature = "library"), entry_point)] diff --git a/contracts/cw-ics721-bridge/src/ibc_packet_receive.rs b/contracts/cw-ics721-bridge/src/ibc_packet_receive.rs index 0d786a3e..f0094f61 100644 --- a/contracts/cw-ics721-bridge/src/ibc_packet_receive.rs +++ b/contracts/cw-ics721-bridge/src/ibc_packet_receive.rs @@ -160,7 +160,13 @@ pub(crate) fn receive_ibc_packet( ) .into_submessage(env.contract.address, receiver)?; - Ok(IbcReceiveResponse::default() + let mut response = IbcReceiveResponse::default(); + + if data.memo.is_some() { + response = response.add_attribute("ics721_memo", data.memo.unwrap()); + } + + Ok(response .add_submessage(submessage) .add_attribute("method", "receive_ibc_packet") .add_attribute("class_id", data.class_id) diff --git a/contracts/cw-ics721-bridge/src/ibc_tests.rs b/contracts/cw-ics721-bridge/src/ibc_tests.rs index 0eead8a2..0139b1d8 100644 --- a/contracts/cw-ics721-bridge/src/ibc_tests.rs +++ b/contracts/cw-ics721-bridge/src/ibc_tests.rs @@ -108,6 +108,7 @@ fn build_ics_packet( token_uris: token_uris.map(|t| t.into_iter().map(|s| s.to_string()).collect()), sender: sender.to_string(), receiver: receiver.to_string(), + memo: None, } } diff --git a/contracts/cw-ics721-bridge/src/integration_tests.rs b/contracts/cw-ics721-bridge/src/integration_tests.rs index 3f581948..b7f2da32 100644 --- a/contracts/cw-ics721-bridge/src/integration_tests.rs +++ b/contracts/cw-ics721-bridge/src/integration_tests.rs @@ -507,6 +507,7 @@ fn test_proxy_authorized() { revision: 0, height: 10, }), + memo: None, }) .unwrap(), }, @@ -552,6 +553,7 @@ fn test_no_receive_with_proxy() { revision: 0, height: 10, }), + memo: None, }) .unwrap(), }), diff --git a/contracts/cw-ics721-bridge/src/msg.rs b/contracts/cw-ics721-bridge/src/msg.rs index 81bc88be..a240668c 100644 --- a/contracts/cw-ics721-bridge/src/msg.rs +++ b/contracts/cw-ics721-bridge/src/msg.rs @@ -90,6 +90,8 @@ pub struct IbcOutgoingMsg { pub channel_id: String, /// Timeout for the IBC message. pub timeout: IbcTimeout, + /// Memo to add custom string to the msg + pub memo: Option, } #[cw_serde] From b8e448c334e5298279bbe62dab4cdbc69eb225c4 Mon Sep 17 00:00:00 2001 From: Art3miX <40179351+Art3miX@users.noreply.github.com> Date: Mon, 2 Jan 2023 10:37:11 +0200 Subject: [PATCH 06/15] Update contracts/cw-ics721-bridge/src/contract.rs Co-authored-by: ekez <30676292+0xekez@users.noreply.github.com> --- contracts/cw-ics721-bridge/src/contract.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/contracts/cw-ics721-bridge/src/contract.rs b/contracts/cw-ics721-bridge/src/contract.rs index 55e38d2b..b6edc383 100644 --- a/contracts/cw-ics721-bridge/src/contract.rs +++ b/contracts/cw-ics721-bridge/src/contract.rs @@ -145,9 +145,7 @@ fn receive_nft( ) -> Result { let sender = deps.api.addr_validate(&sender)?; let msg: IbcOutgoingMsg = from_binary(&msg)?; - let class = NFT_CONTRACT_TO_CLASS.may_load(deps.storage, info.sender.clone())?; - - let class = match class { + let class = match NFT_CONTRACT_TO_CLASS.may_load(deps.storage, info.sender.clone())? { Some(class) => class, None => { // If we do not yet have a class ID for this contract, it is a From 287c2f6a81652d25b1d8c087fa8424646ca46791 Mon Sep 17 00:00:00 2001 From: Art3mix Date: Mon, 2 Jan 2023 10:45:43 +0200 Subject: [PATCH 07/15] zeke comments fixes --- contracts/cw-ics721-bridge/src/contract.rs | 18 +++++++++++------- contracts/cw-ics721-bridge/src/ibc.rs | 16 ++++++---------- .../cw-ics721-bridge/src/ibc_packet_receive.rs | 10 +++++----- contracts/cw-ics721-bridge/src/ibc_tests.rs | 8 ++++---- contracts/cw-ics721-bridge/src/state.rs | 2 +- 5 files changed, 27 insertions(+), 27 deletions(-) diff --git a/contracts/cw-ics721-bridge/src/contract.rs b/contracts/cw-ics721-bridge/src/contract.rs index b6edc383..566cf57f 100644 --- a/contracts/cw-ics721-bridge/src/contract.rs +++ b/contracts/cw-ics721-bridge/src/contract.rs @@ -12,7 +12,7 @@ use crate::{ msg::{CallbackMsg, ExecuteMsg, IbcOutgoingMsg, InstantiateMsg, MigrateMsg, QueryMsg}, state::{ UniversalNftInfoResponse, CLASS_ID_TO_CLASS, CLASS_ID_TO_NFT_CONTRACT, - CLASS_TOKEN_ID_TO_TOKEN_METADATA, CW721_CODE_ID, NFT_CONTRACT_TO_CLASS, + CLASS_TOKEN_ID_TO_TOKEN_METADATA, CW721_CODE_ID, NFT_CONTRACT_TO_CLASS_ID, OUTGOING_CLASS_TOKEN_TO_CHANNEL, PO, PROXY, }, token_types::{Class, ClassId, Token, TokenId, VoucherCreation, VoucherRedemption}, @@ -145,8 +145,10 @@ fn receive_nft( ) -> Result { let sender = deps.api.addr_validate(&sender)?; let msg: IbcOutgoingMsg = from_binary(&msg)?; - let class = match NFT_CONTRACT_TO_CLASS.may_load(deps.storage, info.sender.clone())? { - Some(class) => class, + let class = NFT_CONTRACT_TO_CLASS_ID.may_load(deps.storage, info.sender.clone())?; + + let class = match class { + Some(class_id) => CLASS_ID_TO_CLASS.load(deps.storage, class_id)?, None => { // If we do not yet have a class ID for this contract, it is a // local NFT and its class ID is its conract address. @@ -158,7 +160,7 @@ fn receive_nft( data: None, }; - NFT_CONTRACT_TO_CLASS.save(deps.storage, info.sender.clone(), &class)?; + NFT_CONTRACT_TO_CLASS_ID.save(deps.storage, info.sender.clone(), &class.id)?; CLASS_ID_TO_NFT_CONTRACT.save(deps.storage, class.id.clone(), &info.sender)?; // Merging and usage of this PR may change that: @@ -333,7 +335,9 @@ fn callback_redeem_vouchers( #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { - QueryMsg::ClassId { contract } => to_binary(&query_class_for_nft_contract(deps, contract)?), + QueryMsg::ClassId { contract } => { + to_binary(&query_class_id_for_nft_contract(deps, contract)?) + } QueryMsg::NftContract { class_id } => { to_binary(&query_nft_contract_for_class_id(deps, class_id)?) } @@ -347,9 +351,9 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { } } -fn query_class_for_nft_contract(deps: Deps, contract: String) -> StdResult> { +fn query_class_id_for_nft_contract(deps: Deps, contract: String) -> StdResult> { let contract = deps.api.addr_validate(&contract)?; - NFT_CONTRACT_TO_CLASS.may_load(deps.storage, contract) + NFT_CONTRACT_TO_CLASS_ID.may_load(deps.storage, contract) } fn query_nft_contract_for_class_id(deps: Deps, class_id: String) -> StdResult> { diff --git a/contracts/cw-ics721-bridge/src/ibc.rs b/contracts/cw-ics721-bridge/src/ibc.rs index bd298805..72bb9a27 100644 --- a/contracts/cw-ics721-bridge/src/ibc.rs +++ b/contracts/cw-ics721-bridge/src/ibc.rs @@ -14,10 +14,10 @@ use crate::{ ibc_helpers::{ack_fail, ack_success, try_get_ack_error, validate_order_and_version}, ibc_packet_receive::receive_ibc_packet, state::{ - CLASS_ID_TO_NFT_CONTRACT, INCOMING_CLASS_TOKEN_TO_CHANNEL, NFT_CONTRACT_TO_CLASS, + CLASS_ID_TO_NFT_CONTRACT, INCOMING_CLASS_TOKEN_TO_CHANNEL, NFT_CONTRACT_TO_CLASS_ID, OUTGOING_CLASS_TOKEN_TO_CHANNEL, PROXY, }, - token_types::{Class, ClassId, TokenId}, + token_types::{ClassId, TokenId}, ContractError, }; @@ -254,19 +254,15 @@ pub fn reply(deps: DepsMut, _env: Env, reply: Reply) -> Result contract mappings. - CLASS_ID_TO_NFT_CONTRACT.save(deps.storage, class.id.clone(), &cw721_addr)?; - NFT_CONTRACT_TO_CLASS.save(deps.storage, cw721_addr.clone(), &class)?; + CLASS_ID_TO_NFT_CONTRACT.save(deps.storage, class_id.clone(), &cw721_addr)?; + NFT_CONTRACT_TO_CLASS_ID.save(deps.storage, cw721_addr.clone(), &class_id)?; Ok(Response::default() .add_attribute("method", "instantiate_cw721_reply") - .add_attribute("class_id", class.id) + .add_attribute("class_id", class_id) .add_attribute("cw721_addr", cw721_addr)) } INSTANTIATE_PROXY_REPLY_ID => { diff --git a/contracts/cw-ics721-bridge/src/ibc_packet_receive.rs b/contracts/cw-ics721-bridge/src/ibc_packet_receive.rs index f0094f61..7957dc6b 100644 --- a/contracts/cw-ics721-bridge/src/ibc_packet_receive.rs +++ b/contracts/cw-ics721-bridge/src/ibc_packet_receive.rs @@ -160,12 +160,12 @@ pub(crate) fn receive_ibc_packet( ) .into_submessage(env.contract.address, receiver)?; - let mut response = IbcReceiveResponse::default(); + let response = if let Some(memo) = data.memo { + IbcReceiveResponse::default().add_attribute("memo", memo) + } else { + IbcReceiveResponse::default() + }; - if data.memo.is_some() { - response = response.add_attribute("ics721_memo", data.memo.unwrap()); - } - Ok(response .add_submessage(submessage) .add_attribute("method", "receive_ibc_packet") diff --git a/contracts/cw-ics721-bridge/src/ibc_tests.rs b/contracts/cw-ics721-bridge/src/ibc_tests.rs index 0139b1d8..b3517ef1 100644 --- a/contracts/cw-ics721-bridge/src/ibc_tests.rs +++ b/contracts/cw-ics721-bridge/src/ibc_tests.rs @@ -14,7 +14,7 @@ use crate::{ }, ibc_helpers::{ack_fail, ack_success, try_get_ack_error}, msg::{InstantiateMsg, QueryMsg}, - state::{CLASS_ID_TO_NFT_CONTRACT, NFT_CONTRACT_TO_CLASS, PO}, + state::{CLASS_ID_TO_NFT_CONTRACT, NFT_CONTRACT_TO_CLASS_ID, PO}, token_types::{ClassId, TokenId}, ContractError, }; @@ -160,15 +160,15 @@ fn test_reply_cw721() { ] ); - let class = NFT_CONTRACT_TO_CLASS + let class_id = NFT_CONTRACT_TO_CLASS_ID .load(deps.as_ref().storage, Addr::unchecked("cosmos2contract")) .unwrap(); let nft = CLASS_ID_TO_NFT_CONTRACT - .load(deps.as_ref().storage, class.id.clone()) + .load(deps.as_ref().storage, class_id.clone()) .unwrap(); assert_eq!(nft, Addr::unchecked("cosmos2contract")); - assert_eq!(class.id.to_string(), "wasm.address1/channel-10/address2"); + assert_eq!(class_id.to_string(), "wasm.address1/channel-10/address2"); } #[test] diff --git a/contracts/cw-ics721-bridge/src/state.rs b/contracts/cw-ics721-bridge/src/state.rs index 84476fae..6008de19 100644 --- a/contracts/cw-ics721-bridge/src/state.rs +++ b/contracts/cw-ics721-bridge/src/state.rs @@ -16,7 +16,7 @@ pub const PO: PauseOrchestrator = PauseOrchestrator::new("c", "d"); /// contract we have instantiated for that classID. pub const CLASS_ID_TO_NFT_CONTRACT: Map = Map::new("e"); /// Maps cw721 contracts to the classID they were instantiated for. -pub const NFT_CONTRACT_TO_CLASS: Map = Map::new("f"); +pub const NFT_CONTRACT_TO_CLASS_ID: Map = Map::new("f"); /// Maps between classIDs and classs. We need to keep this state /// ourselves as cw721 contracts do not have class-level metadata. From 982104c29fa32e264ed570be196530159ce0ddc2 Mon Sep 17 00:00:00 2001 From: Art3mix Date: Mon, 2 Jan 2023 21:02:39 +0200 Subject: [PATCH 08/15] moved tests to testing folder --- contracts/cw-ics721-bridge/src/contract.rs | 113 +---------------- contracts/cw-ics721-bridge/src/lib.rs | 4 +- .../cw-ics721-bridge/src/testing/contract.rs | 114 ++++++++++++++++++ .../src/{ => testing}/ibc_tests.rs | 2 +- .../src/{ => testing}/integration_tests.rs | 0 contracts/cw-ics721-bridge/src/testing/mod.rs | 3 + 6 files changed, 120 insertions(+), 116 deletions(-) create mode 100644 contracts/cw-ics721-bridge/src/testing/contract.rs rename contracts/cw-ics721-bridge/src/{ => testing}/ibc_tests.rs (99%) rename contracts/cw-ics721-bridge/src/{ => testing}/integration_tests.rs (100%) create mode 100644 contracts/cw-ics721-bridge/src/testing/mod.rs diff --git a/contracts/cw-ics721-bridge/src/contract.rs b/contracts/cw-ics721-bridge/src/contract.rs index 566cf57f..e6facd93 100644 --- a/contracts/cw-ics721-bridge/src/contract.rs +++ b/contracts/cw-ics721-bridge/src/contract.rs @@ -136,7 +136,7 @@ fn execute_pause(deps: DepsMut, info: MessageInfo) -> Result Result QuerierResult { - match query { - cosmwasm_std::WasmQuery::Smart { - contract_addr, - msg: _, - } => { - if *contract_addr == NFT_ADDR { - QuerierResult::Ok(ContractResult::Ok( - to_binary(&NftInfoResponse::> { - token_uri: Some("https://moonphase.is/image.svg".to_string()), - extension: None, - }) - .unwrap(), - )) - } else { - unimplemented!() - } - } - cosmwasm_std::WasmQuery::Raw { - contract_addr: _, - key: _, - } => unimplemented!(), - cosmwasm_std::WasmQuery::ContractInfo { contract_addr: _ } => unimplemented!(), - _ => unimplemented!(), - } - } - - #[test] - fn test_receive_nft() { - let mut querier = MockQuerier::default(); - querier.update_wasm(nft_info_response_mock_querier); - - let mut deps = mock_dependencies(); - deps.querier = querier; - - let info = mock_info(NFT_ADDR, &[]); - let token_id = TokenId::new("1"); - let sender = "ekez".to_string(); - let msg = to_binary(&IbcOutgoingMsg { - receiver: "callum".to_string(), - channel_id: "channel-1".to_string(), - timeout: IbcTimeout::with_timestamp(Timestamp::from_seconds(42)), - memo: None, - }) - .unwrap(); - - let res = receive_nft(deps.as_mut(), info, token_id.clone(), sender.clone(), msg).unwrap(); - assert_eq!(res.messages.len(), 1); - - assert_eq!( - res.messages[0], - SubMsg::new(CosmosMsg::Ibc(IbcMsg::SendPacket { - channel_id: "channel-1".to_string(), - timeout: IbcTimeout::with_timestamp(Timestamp::from_seconds(42)), - data: to_binary(&NonFungibleTokenPacketData { - class_id: ClassId::new(NFT_ADDR), - class_uri: None, - class_data: None, - token_data: None, - token_ids: vec![token_id], - token_uris: Some(vec!["https://moonphase.is/image.svg".to_string()]), - sender, - receiver: "callum".to_string(), - memo: None, - }) - .unwrap() - })) - ) - } - - #[test] - fn test_receive_sets_uri() { - let mut querier = MockQuerier::default(); - querier.update_wasm(nft_info_response_mock_querier); - - let mut deps = mock_dependencies(); - deps.querier = querier; - - let info = mock_info(NFT_ADDR, &[]); - let token_id = TokenId::new("1"); - let sender = "ekez".to_string(); - let msg = to_binary(&IbcOutgoingMsg { - receiver: "ekez".to_string(), - channel_id: "channel-1".to_string(), - timeout: IbcTimeout::with_timestamp(Timestamp::from_nanos(42)), - memo: None, - }) - .unwrap(); - - receive_nft(deps.as_mut(), info, token_id, sender, msg).unwrap(); - - let class = CLASS_ID_TO_CLASS - .load(deps.as_ref().storage, ClassId::new(NFT_ADDR)) - .unwrap(); - assert_eq!(class.uri, None); - assert_eq!(class.data, None); - } -} diff --git a/contracts/cw-ics721-bridge/src/lib.rs b/contracts/cw-ics721-bridge/src/lib.rs index c719022c..2d587f5c 100644 --- a/contracts/cw-ics721-bridge/src/lib.rs +++ b/contracts/cw-ics721-bridge/src/lib.rs @@ -8,8 +8,6 @@ pub mod state; pub mod token_types; #[cfg(test)] -mod ibc_tests; -#[cfg(test)] -mod integration_tests; +pub mod testing; pub use crate::error::ContractError; diff --git a/contracts/cw-ics721-bridge/src/testing/contract.rs b/contracts/cw-ics721-bridge/src/testing/contract.rs new file mode 100644 index 00000000..8cba7e69 --- /dev/null +++ b/contracts/cw-ics721-bridge/src/testing/contract.rs @@ -0,0 +1,114 @@ +use cosmwasm_std::{ + testing::{mock_dependencies, mock_info, MockQuerier}, + to_binary, ContractResult, CosmosMsg, Empty, IbcMsg, IbcTimeout, QuerierResult, SubMsg, + Timestamp, WasmQuery, +}; +use cw721::NftInfoResponse; + +use crate::{ + contract::receive_nft, + ibc::NonFungibleTokenPacketData, + msg::IbcOutgoingMsg, + state::CLASS_ID_TO_CLASS, + token_types::{ClassId, TokenId}, +}; + +const NFT_ADDR: &str = "nft"; + +fn nft_info_response_mock_querier(query: &WasmQuery) -> QuerierResult { + match query { + cosmwasm_std::WasmQuery::Smart { + contract_addr, + msg: _, + } => { + if *contract_addr == NFT_ADDR { + QuerierResult::Ok(ContractResult::Ok( + to_binary(&NftInfoResponse::> { + token_uri: Some("https://moonphase.is/image.svg".to_string()), + extension: None, + }) + .unwrap(), + )) + } else { + unimplemented!() + } + } + cosmwasm_std::WasmQuery::Raw { + contract_addr: _, + key: _, + } => unimplemented!(), + cosmwasm_std::WasmQuery::ContractInfo { contract_addr: _ } => unimplemented!(), + _ => unimplemented!(), + } +} + +#[test] +fn test_receive_nft() { + let mut querier = MockQuerier::default(); + querier.update_wasm(nft_info_response_mock_querier); + + let mut deps = mock_dependencies(); + deps.querier = querier; + + let info = mock_info(NFT_ADDR, &[]); + let token_id = TokenId::new("1"); + let sender = "ekez".to_string(); + let msg = to_binary(&IbcOutgoingMsg { + receiver: "callum".to_string(), + channel_id: "channel-1".to_string(), + timeout: IbcTimeout::with_timestamp(Timestamp::from_seconds(42)), + memo: None, + }) + .unwrap(); + + let res = receive_nft(deps.as_mut(), info, token_id.clone(), sender.clone(), msg).unwrap(); + assert_eq!(res.messages.len(), 1); + + assert_eq!( + res.messages[0], + SubMsg::new(CosmosMsg::Ibc(IbcMsg::SendPacket { + channel_id: "channel-1".to_string(), + timeout: IbcTimeout::with_timestamp(Timestamp::from_seconds(42)), + data: to_binary(&NonFungibleTokenPacketData { + class_id: ClassId::new(NFT_ADDR), + class_uri: None, + class_data: None, + token_data: None, + token_ids: vec![token_id], + token_uris: Some(vec!["https://moonphase.is/image.svg".to_string()]), + sender, + receiver: "callum".to_string(), + memo: None, + }) + .unwrap() + })) + ) +} + +#[test] +fn test_receive_sets_uri() { + let mut querier = MockQuerier::default(); + querier.update_wasm(nft_info_response_mock_querier); + + let mut deps = mock_dependencies(); + deps.querier = querier; + + let info = mock_info(NFT_ADDR, &[]); + let token_id = TokenId::new("1"); + let sender = "ekez".to_string(); + let msg = to_binary(&IbcOutgoingMsg { + receiver: "ekez".to_string(), + channel_id: "channel-1".to_string(), + timeout: IbcTimeout::with_timestamp(Timestamp::from_nanos(42)), + memo: None, + }) + .unwrap(); + + receive_nft(deps.as_mut(), info, token_id, sender, msg).unwrap(); + + let class = CLASS_ID_TO_CLASS + .load(deps.as_ref().storage, ClassId::new(NFT_ADDR)) + .unwrap(); + assert_eq!(class.uri, None); + assert_eq!(class.data, None); +} diff --git a/contracts/cw-ics721-bridge/src/ibc_tests.rs b/contracts/cw-ics721-bridge/src/testing/ibc_tests.rs similarity index 99% rename from contracts/cw-ics721-bridge/src/ibc_tests.rs rename to contracts/cw-ics721-bridge/src/testing/ibc_tests.rs index b3517ef1..e8a48cde 100644 --- a/contracts/cw-ics721-bridge/src/ibc_tests.rs +++ b/contracts/cw-ics721-bridge/src/testing/ibc_tests.rs @@ -476,7 +476,7 @@ fn test_packet_json() { ); // Example message generated from the SDK // TODO: test with non-null tokenData and classData. - let expected = r#"{"classId":"stars1zedxv25ah8fksmg2lzrndrpkvsjqgk4zt5ff7n","classUri":"https://metadata-url.com/my-metadata","classData":null,"tokenIds":["1","2","3"],"tokenUris":["https://metadata-url.com/my-metadata1","https://metadata-url.com/my-metadata2","https://metadata-url.com/my-metadata3"],"tokenData":null,"sender":"stars1zedxv25ah8fksmg2lzrndrpkvsjqgk4zt5ff7n","receiver":"wasm1fucynrfkrt684pm8jrt8la5h2csvs5cnldcgqc"}"#; + let expected = r#"{"classId":"stars1zedxv25ah8fksmg2lzrndrpkvsjqgk4zt5ff7n","classUri":"https://metadata-url.com/my-metadata","classData":null,"tokenIds":["1","2","3"],"tokenUris":["https://metadata-url.com/my-metadata1","https://metadata-url.com/my-metadata2","https://metadata-url.com/my-metadata3"],"tokenData":null,"sender":"stars1zedxv25ah8fksmg2lzrndrpkvsjqgk4zt5ff7n","receiver":"wasm1fucynrfkrt684pm8jrt8la5h2csvs5cnldcgqc","memo":null}"#; let encdoded = String::from_utf8(to_vec(&packet).unwrap()).unwrap(); assert_eq!(expected, encdoded.as_str()); diff --git a/contracts/cw-ics721-bridge/src/integration_tests.rs b/contracts/cw-ics721-bridge/src/testing/integration_tests.rs similarity index 100% rename from contracts/cw-ics721-bridge/src/integration_tests.rs rename to contracts/cw-ics721-bridge/src/testing/integration_tests.rs diff --git a/contracts/cw-ics721-bridge/src/testing/mod.rs b/contracts/cw-ics721-bridge/src/testing/mod.rs new file mode 100644 index 00000000..606643c2 --- /dev/null +++ b/contracts/cw-ics721-bridge/src/testing/mod.rs @@ -0,0 +1,3 @@ +mod contract; +mod ibc_tests; +mod integration_tests; From 460f600e1e80cceb4b5f2c75efd518a3c629570e Mon Sep 17 00:00:00 2001 From: ekez Date: Mon, 2 Jan 2023 17:35:04 -0800 Subject: [PATCH 09/15] Add test for metadata forwarding. --- contracts/cw-ics721-bridge/src/contract.rs | 58 ++++- contracts/cw-ics721-bridge/src/ibc.rs | 8 +- contracts/cw-ics721-bridge/src/msg.rs | 11 +- .../cw-ics721-bridge/src/testing/ibc_tests.rs | 9 +- e2e/adversarial_test.go | 198 +++++++++++++++++- e2e/types.go | 42 +++- 6 files changed, 299 insertions(+), 27 deletions(-) diff --git a/contracts/cw-ics721-bridge/src/contract.rs b/contracts/cw-ics721-bridge/src/contract.rs index e6facd93..ea624655 100644 --- a/contracts/cw-ics721-bridge/src/contract.rs +++ b/contracts/cw-ics721-bridge/src/contract.rs @@ -176,6 +176,10 @@ pub(crate) fn receive_nft( token_id: token_id.clone().into(), }, )?; + // Get and forward metadata for this token. + let token_metadata = CLASS_TOKEN_ID_TO_TOKEN_METADATA + .may_load(deps.storage, (class.id.clone(), token_id.clone()))? + .flatten(); let ibc_message = NonFungibleTokenPacketData { class_id: class.id.clone(), @@ -184,7 +188,7 @@ pub(crate) fn receive_nft( token_ids: vec![token_id.clone()], token_uris: token_uri.map(|uri| vec![uri]), - token_data: None, // Like class_uri and class_data, token_data is none for local NFTs. + token_data: token_metadata.map(|metadata| vec![metadata]), sender: sender.into_string(), receiver: msg.receiver, @@ -222,7 +226,10 @@ fn callback_mint( let mint = tokens .into_iter() .map(|Token { id, uri, data }| { - // We save token metadata here because it is part of the mint process + // We save token metadata here as, ideally, once cw721 + // supports on-chain metadata, this is where we will set + // that value on the debt-voucher token. Note that this is + // set for every token, regardless of if data is None. CLASS_TOKEN_ID_TO_TOKEN_METADATA.save( deps.storage, (class_id.clone(), id.clone()), @@ -315,10 +322,6 @@ fn callback_redeem_vouchers( token_ids .into_iter() .map(|token_id| { - // token metadata is set in mint, on voucher creation, and removed here on voucher redemption - CLASS_TOKEN_ID_TO_TOKEN_METADATA - .remove(deps.storage, (class.id.clone(), token_id.clone())); - Ok(WasmMsg::Execute { contract_addr: nft_contract.to_string(), msg: to_binary(&cw721::Cw721ExecuteMsg::TransferNft { @@ -341,7 +344,10 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { QueryMsg::NftContract { class_id } => { to_binary(&query_nft_contract_for_class_id(deps, class_id)?) } - QueryMsg::Metadata { class_id } => to_binary(&query_metadata(deps, class_id)?), + QueryMsg::ClassMetadata { class_id } => to_binary(&query_class_metadata(deps, class_id)?), + QueryMsg::TokenMetadata { class_id, token_id } => { + to_binary(&query_token_metadata(deps, class_id, token_id)?) + } QueryMsg::Owner { class_id, token_id } => { to_binary(&query_owner(deps, class_id, token_id)?) } @@ -360,10 +366,46 @@ fn query_nft_contract_for_class_id(deps: Deps, class_id: String) -> StdResult StdResult> { +fn query_class_metadata(deps: Deps, class_id: String) -> StdResult> { CLASS_ID_TO_CLASS.may_load(deps.storage, ClassId::new(class_id)) } +fn query_token_metadata( + deps: Deps, + class_id: String, + token_id: String, +) -> StdResult> { + let token_id = TokenId::new(token_id); + let class_id = ClassId::new(class_id); + + let Some(token_metadata) = CLASS_TOKEN_ID_TO_TOKEN_METADATA.may_load( + deps.storage, + (class_id.clone(), token_id.clone()), + )? else { + // Token metadata is set unconditionaly on mint. If we have no + // metadata entry, we have no entry for this token at all. + return Ok(None) + }; + let Some(token_contract) = CLASS_ID_TO_NFT_CONTRACT.may_load( + deps.storage, + class_id + )? else { + debug_assert!(false, "token_metadata != None => token_contract != None"); + return Ok(None) + }; + let UniversalNftInfoResponse { token_uri, .. } = deps.querier.query_wasm_smart( + token_contract, + &cw721::Cw721QueryMsg::NftInfo { + token_id: token_id.clone().into(), + }, + )?; + Ok(Some(Token { + id: token_id, + uri: token_uri, + data: token_metadata, + })) +} + fn query_owner( deps: Deps, class_id: String, diff --git a/contracts/cw-ics721-bridge/src/ibc.rs b/contracts/cw-ics721-bridge/src/ibc.rs index 72bb9a27..73ccbf96 100644 --- a/contracts/cw-ics721-bridge/src/ibc.rs +++ b/contracts/cw-ics721-bridge/src/ibc.rs @@ -14,8 +14,9 @@ use crate::{ ibc_helpers::{ack_fail, ack_success, try_get_ack_error, validate_order_and_version}, ibc_packet_receive::receive_ibc_packet, state::{ - CLASS_ID_TO_NFT_CONTRACT, INCOMING_CLASS_TOKEN_TO_CHANNEL, NFT_CONTRACT_TO_CLASS_ID, - OUTGOING_CLASS_TOKEN_TO_CHANNEL, PROXY, + CLASS_ID_TO_NFT_CONTRACT, CLASS_TOKEN_ID_TO_TOKEN_METADATA, + INCOMING_CLASS_TOKEN_TO_CHANNEL, NFT_CONTRACT_TO_CLASS_ID, OUTGOING_CLASS_TOKEN_TO_CHANNEL, + PROXY, }, token_types::{ClassId, TokenId}, ContractError, @@ -165,6 +166,9 @@ pub fn ibc_packet_ack( if returning_to_source { // This token's journey is complete, for now. INCOMING_CLASS_TOKEN_TO_CHANNEL.remove(deps.storage, key); + CLASS_TOKEN_ID_TO_TOKEN_METADATA + .remove(deps.storage, (msg.class_id.clone(), token.clone())); + messages.push(WasmMsg::Execute { contract_addr: nft_contract.to_string(), msg: to_binary(&cw721::Cw721ExecuteMsg::Burn { diff --git a/contracts/cw-ics721-bridge/src/msg.rs b/contracts/cw-ics721-bridge/src/msg.rs index a240668c..2800f520 100644 --- a/contracts/cw-ics721-bridge/src/msg.rs +++ b/contracts/cw-ics721-bridge/src/msg.rs @@ -100,7 +100,7 @@ pub enum QueryMsg { /// Gets the classID this contract has stored for a given NFT /// contract. If there is no class ID for the provided contract, /// returns None. - #[returns(Option)] + #[returns(Option)] ClassId { contract: String }, /// Gets the NFT contract associated wtih the provided class @@ -111,9 +111,12 @@ pub enum QueryMsg { /// Gets the class level metadata URI for the provided /// class_id. If there is no metadata, returns None. Returns - /// `Option`. - #[returns(Option)] - Metadata { class_id: String }, + /// `Option`. + #[returns(Option)] + ClassMetadata { class_id: String }, + + #[returns(Option)] + TokenMetadata { class_id: String, token_id: String }, /// Gets the owner of the NFT identified by CLASS_ID and /// TOKEN_ID. Errors if no such NFT exists. Returns diff --git a/contracts/cw-ics721-bridge/src/testing/ibc_tests.rs b/contracts/cw-ics721-bridge/src/testing/ibc_tests.rs index e8a48cde..27cbd62a 100644 --- a/contracts/cw-ics721-bridge/src/testing/ibc_tests.rs +++ b/contracts/cw-ics721-bridge/src/testing/ibc_tests.rs @@ -407,7 +407,10 @@ fn test_ibc_channel_connect_invalid_version_counterparty() { #[test] fn test_ibc_packet_receive_invalid_packet_data() { - let data = to_binary(&QueryMsg::Metadata { + // the actual message used here is unimportant. this just + // constructs a valud JSON blob that is not a valid ICS-721 + // packet. + let data = to_binary(&QueryMsg::ClassMetadata { class_id: "foobar".to_string(), }) .unwrap(); @@ -484,7 +487,9 @@ fn test_packet_json() { #[test] fn test_no_receive_when_paused() { - let data = to_binary(&QueryMsg::Metadata { + // Valid JSON, invalid ICS-721 packet. Tests that we check for + // pause status before attempting validation. + let data = to_binary(&QueryMsg::ClassMetadata { class_id: "foobar".to_string(), }) .unwrap(); diff --git a/e2e/adversarial_test.go b/e2e/adversarial_test.go index 00bc7e5a..d2430425 100644 --- a/e2e/adversarial_test.go +++ b/e2e/adversarial_test.go @@ -208,7 +208,7 @@ func (suite *AdversarialTestSuite) TestInvalidOnMineValidOnTheirs() { _, err := suite.chainC.SendMsgs(&wasmtypes.MsgExecuteContract{ Sender: suite.chainC.SenderAccount.GetAddress().String(), Contract: suite.bridgeC.String(), - Msg: []byte(fmt.Sprintf(`{ "send_packet": { "channel_id": "%s", "timeout": { "timestamp": "%d" }, "data": {"classId":"%s","classUri":"https://metadata-url.com/my-metadata","tokenIds":["%s"],"tokenUris":["https://metadata-url.com/my-metadata1"],"sender":"%s","receiver":"%s"} }}`, suite.pathAC.Invert().EndpointA.ChannelID, suite.coordinator.CurrentTime.Add(time.Hour*100).UnixNano(), chainBClassId, suite.tokenIdA, suite.chainC.SenderAccount.GetAddress().String(), suite.chainA.SenderAccount.GetAddress().String())), + Msg: []byte(fmt.Sprintf(`{ "send_packet": { "channel_id": "%s", "timeout": { "timestamp": "%d" }, "data": {"classId":"%s","classUri":"https://metadata-url.com/my-metadata","classData":"e30K","tokenIds":["%s"],"tokenUris":["https://metadata-url.com/my-metadata1"],"sender":"%s","receiver":"%s"} }}`, suite.pathAC.Invert().EndpointA.ChannelID, suite.coordinator.CurrentTime.Add(time.Hour*100).UnixNano(), chainBClassId, suite.tokenIdA, suite.chainC.SenderAccount.GetAddress().String(), suite.chainA.SenderAccount.GetAddress().String())), Funds: []sdk.Coin{}, }) require.NoError(suite.T(), err) @@ -226,14 +226,17 @@ func (suite *AdversarialTestSuite) TestInvalidOnMineValidOnTheirs() { require.Equal(suite.T(), suite.chainA.SenderAccount.GetAddress().String(), chainAOwner) // Metadata should be set. - var metadata string - err = suite.chainA.SmartQuery(suite.bridgeA.String(), MetadataQuery{ - Metadata: MetadataQueryData{ + var metadata Class + err = suite.chainA.SmartQuery(suite.bridgeA.String(), ClassMetadataQuery{ + Metadata: ClassMetadataQueryData{ ClassId: chainAClassId, }, }, &metadata) require.NoError(suite.T(), err) - require.Equal(suite.T(), "https://metadata-url.com/my-metadata", metadata) + require.NotNil(suite.T(), metadata.URI) + require.NotNil(suite.T(), metadata.Data) + require.Equal(suite.T(), "https://metadata-url.com/my-metadata", *metadata.URI) + require.Equal(suite.T(), "e30K", *metadata.Data) // The newly minted NFT should be returnable to the source // chain and cause a burn when returned. @@ -255,13 +258,17 @@ func (suite *AdversarialTestSuite) TestInvalidOnMineValidOnTheirs() { suite.coordinator.RelayAndAckPendingPackets(suite.pathAC.Invert()) // Metadata should be set to the most up to date value. - err = suite.chainA.SmartQuery(suite.bridgeA.String(), MetadataQuery{ - Metadata: MetadataQueryData{ + err = suite.chainA.SmartQuery(suite.bridgeA.String(), ClassMetadataQuery{ + Metadata: ClassMetadataQueryData{ ClassId: chainAClassId, }, }, &metadata) require.NoError(suite.T(), err) - require.Equal(suite.T(), "https://moonphase.is", metadata) + // The new packet new classURI and data fields. Data was + // omitted and thus should be set to nil. + require.NotNil(suite.T(), metadata.URI) + require.Nil(suite.T(), metadata.Data) + require.Equal(suite.T(), "https://moonphase.is", *metadata.URI) } // How does the ics721-bridge contract respond if the other side sends @@ -300,6 +307,181 @@ func (suite *AdversarialTestSuite) TestEmptyClassId() { require.Equal(suite.T(), "", chainACw721) } +// The ICS-721 standard adds the following metadata fields which are +// not present in the CW-721 standard: +// +// 1. `class_uri` - pointer of off-chain metadata +// 2. `class_data` - on-chain, base64 encoded metadata +// 3. `token_data` - on-chain, base64 encoded metadata +// +// How does the ics721-bridge contract respond if the other side sends +// metadata not present in the CW-721 specification? +// +// It should: +// - Accept the transfer request. +// - Make the additional metadata fields queryable from the bridge. +// - Forward the data when debt-vouchers for NFTs with additional +// metadata are sent to other chains. +// - Clear metadata for redeemed debt vouchers. +func (suite *AdversarialTestSuite) TestMetadataForwarding() { + // Send two NFTs with additional metadata to the bridge. + _, err := suite.chainC.SendMsgs(&wasmtypes.MsgExecuteContract{ + Sender: suite.chainC.SenderAccount.GetAddress().String(), + Contract: suite.bridgeC.String(), + Funds: []sdk.Coin{}, + Msg: []byte(fmt.Sprintf( + `{ "send_packet": { "channel_id": "%s", "timeout": { "timestamp": "%d" }, "data": { "classId":"bad kids","classUri":"https://metadata-url.com/my-metadata","classData":"e30K","tokenIds":["bad kid 1","bad kid 2"],"tokenUris":["https://metadata-url.com/my-metadata1","https://metadata-url.com/my-metadata2"],"tokenData":["e30K","e30K"],"sender":"%s","receiver":"%s"} }}`, + suite.pathAC.Invert().EndpointA.ChannelID, + suite.coordinator.CurrentTime.Add(time.Hour*100).UnixNano(), + suite.chainC.SenderAccount.GetAddress().String(), + suite.chainA.SenderAccount.GetAddress().String(), + ), + ), + }) + require.NoError(suite.T(), err) + suite.coordinator.UpdateTime() + suite.coordinator.RelayAndAckPendingPackets(suite.pathAC.Invert()) + + // Check that class level metadata was set. + chainAClassId := fmt.Sprintf( + "%s/%s/%s", + suite.pathAC.EndpointA.ChannelConfig.PortID, + suite.pathAC.EndpointA.ChannelID, + "bad kids", + ) + var class_metadata Class + err = suite.chainA.SmartQuery(suite.bridgeA.String(), ClassMetadataQuery{ + Metadata: ClassMetadataQueryData{ + ClassId: chainAClassId, + }, + }, &class_metadata) + require.NoError(suite.T(), err) + suite.T().Log("class:", class_metadata) + require.NotNil(suite.T(), class_metadata.URI) + require.NotNil(suite.T(), class_metadata.Data) + require.Equal(suite.T(), "https://metadata-url.com/my-metadata", *class_metadata.URI) + require.Equal(suite.T(), "e30K", *class_metadata.Data) + + // Check that token metadata was set. + var token_metadata Token + err = suite.chainA.SmartQuery(suite.bridgeA.String(), TokenMetadataQuery{ + Metadata: TokenMetadataQueryData{ + ClassId: chainAClassId, + TokenId: "bad kid 2", + }, + }, &token_metadata) + require.NoError(suite.T(), err) + suite.T().Log("token:", token_metadata) + require.NotNil(suite.T(), token_metadata.URI) + require.NotNil(suite.T(), token_metadata.Data) + require.Equal(suite.T(), "https://metadata-url.com/my-metadata2", *token_metadata.URI) + require.Equal(suite.T(), "e30K", *token_metadata.Data) + + // Send bad kid 1 to chain B. + var chainAAddress string + err = suite.chainA.SmartQuery(suite.bridgeA.String(), NftContractQuery{ + NftContractForClassId: NftContractQueryData{ + ClassID: chainAClassId, + }, + }, &chainAAddress) + require.NoError(suite.T(), err) + ics721Nft( + suite.T(), + suite.chainA, + suite.pathAB, + suite.coordinator, + chainAAddress, + suite.bridgeA, + suite.chainA.SenderAccount.GetAddress(), + suite.chainB.SenderAccount.GetAddress(), + ) + + // Check that class metadata has been forwarded. + chainBClassId := fmt.Sprintf( + "%s/%s/%s", + suite.pathAB.EndpointB.ChannelConfig.PortID, + suite.pathAB.EndpointB.ChannelID, + chainAClassId, + ) + err = suite.chainB.SmartQuery(suite.bridgeB.String(), ClassMetadataQuery{ + Metadata: ClassMetadataQueryData{ + ClassId: chainBClassId, + }, + }, &class_metadata) + require.NoError(suite.T(), err) + suite.T().Log("class:", class_metadata) + require.NotNil(suite.T(), class_metadata.URI) + require.NotNil(suite.T(), class_metadata.Data) + require.Equal(suite.T(), "https://metadata-url.com/my-metadata", *class_metadata.URI) + require.Equal(suite.T(), "e30K", *class_metadata.Data) + + // Check that token metadata has been forwarded. + token_metadata = Token{} + err = suite.chainB.SmartQuery(suite.bridgeB.String(), TokenMetadataQuery{ + Metadata: TokenMetadataQueryData{ + ClassId: chainBClassId, + TokenId: "bad kid 1", + }, + }, &token_metadata) + require.NoError(suite.T(), err) + suite.T().Log("token:", token_metadata) + require.NotNil(suite.T(), token_metadata.URI) + require.NotNil(suite.T(), token_metadata.Data) + require.Equal(suite.T(), "https://metadata-url.com/my-metadata1", *token_metadata.URI) + require.Equal(suite.T(), "e30K", *token_metadata.Data) + + // Return the token to chain A. + // + // The bridge should remove the token's metadata from storage + // and burn the token. + var chainBAddress string + err = suite.chainB.SmartQuery(suite.bridgeB.String(), NftContractQuery{ + NftContractForClassId: NftContractQueryData{ + ClassID: chainBClassId, + }, + }, &chainBAddress) + require.NoError(suite.T(), err) + ics721Nft(suite.T(), + suite.chainB, + suite.pathAB.Invert(), + suite.coordinator, + chainBAddress, + suite.bridgeB, + suite.chainB.SenderAccount.GetAddress(), + suite.chainA.SenderAccount.GetAddress(), + ) + suite.coordinator.UpdateTime() + + // Check that the returned token was burned. + var info NftInfoQueryResponse + err = suite.chainB.SmartQuery( + chainBAddress, + NftInfoQuery{ + Nftinfo: NftInfoQueryData{ + TokenID: "bad kid 1", + }, + }, + &info, + ) + require.ErrorContains( + suite.T(), + err, + "cw721_base::state::TokenInfo> not found", + ) + + // Check that token metadata was cleared. + token_metadata = Token{} + err = suite.chainB.SmartQuery(suite.bridgeB.String(), TokenMetadataQuery{ + Metadata: TokenMetadataQueryData{ + ClassId: chainBClassId, + TokenId: "bad kid 1", + }, + }, &token_metadata) + require.NoError(suite.T(), err) + require.Nil(suite.T(), token_metadata.Data) + require.Nil(suite.T(), token_metadata.URI) +} + // Are ACK fails returned by this contract parseable? // // Sends a message with an invalid receiver and then checks that the diff --git a/e2e/types.go b/e2e/types.go index d29c0af0..6fa193ff 100644 --- a/e2e/types.go +++ b/e2e/types.go @@ -1,5 +1,21 @@ package e2e_test +// The `Class` type as defined in `token_types.rs` and returned by the +// `class_metadata { class_id }` query. +type Class struct { + ID string `json:"id"` + URI *string `json:"uri"` + Data *string `json:"data"` +} + +// The `Token` type as defined in `token_types.rs` and returned by the +// `token_metadata { class_id, token_id }` query. +type Token struct { + ID string `json:"id"` + URI *string `json:"uri"` + Data *string `json:"data"` +} + type ModuleInstantiateInfo struct { CodeID uint64 `json:"code_id"` Msg string `json:"msg"` @@ -55,11 +71,20 @@ type ClassIdQuery struct { } // Query for getting metadata for a class ID from the bridge. -type MetadataQueryData struct { +type ClassMetadataQueryData struct { ClassId string `json:"class_id"` } -type MetadataQuery struct { - Metadata MetadataQueryData `json:"metadata"` +type ClassMetadataQuery struct { + Metadata ClassMetadataQueryData `json:"class_metadata"` +} + +// Query for getting token metadata. +type TokenMetadataQueryData struct { + ClassId string `json:"class_id"` + TokenId string `json:"token_id"` +} +type TokenMetadataQuery struct { + Metadata TokenMetadataQueryData `json:"token_metadata"` } // Owner query for cw721 contract. @@ -85,3 +110,14 @@ type LastAckQueryData struct{} type LastAckQuery struct { LastAck LastAckQueryData `json:"last_ack"` } + +// cw721 token info query +type NftInfoQueryData struct { + TokenID string `json:"token_id"` +} +type NftInfoQuery struct { + Nftinfo NftInfoQueryData `json:"nft_info"` +} +type NftInfoQueryResponse struct { + TokenURI *string `json:"token_uri"` +} From e6b76362827d519143bf3d020014bd9fb1d05eeb Mon Sep 17 00:00:00 2001 From: Art3mix Date: Tue, 3 Jan 2023 13:46:27 +0200 Subject: [PATCH 10/15] fixed lost commit --- contracts/cw-ics721-bridge/src/contract.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/contracts/cw-ics721-bridge/src/contract.rs b/contracts/cw-ics721-bridge/src/contract.rs index ea624655..f0383958 100644 --- a/contracts/cw-ics721-bridge/src/contract.rs +++ b/contracts/cw-ics721-bridge/src/contract.rs @@ -145,9 +145,8 @@ pub(crate) fn receive_nft( ) -> Result { let sender = deps.api.addr_validate(&sender)?; let msg: IbcOutgoingMsg = from_binary(&msg)?; - let class = NFT_CONTRACT_TO_CLASS_ID.may_load(deps.storage, info.sender.clone())?; - let class = match class { + let class = match NFT_CONTRACT_TO_CLASS_ID.may_load(deps.storage, info.sender.clone())? { Some(class_id) => CLASS_ID_TO_CLASS.load(deps.storage, class_id)?, None => { // If we do not yet have a class ID for this contract, it is a From 14549905f49aa76f144169d0d8cd800b8a062d82 Mon Sep 17 00:00:00 2001 From: Art3mix Date: Tue, 3 Jan 2023 13:54:00 +0200 Subject: [PATCH 11/15] fix typo --- contracts/cw-ics721-bridge/src/contract.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/cw-ics721-bridge/src/contract.rs b/contracts/cw-ics721-bridge/src/contract.rs index f0383958..e88a63de 100644 --- a/contracts/cw-ics721-bridge/src/contract.rs +++ b/contracts/cw-ics721-bridge/src/contract.rs @@ -150,7 +150,7 @@ pub(crate) fn receive_nft( Some(class_id) => CLASS_ID_TO_CLASS.load(deps.storage, class_id)?, None => { // If we do not yet have a class ID for this contract, it is a - // local NFT and its class ID is its conract address. + // local NFT and its class ID is its contract address. // We set class level metadata and URI to None for local NFTs. let class = Class { From bbfc6f25302b6d7faee76df44f0a35f40ca84578 Mon Sep 17 00:00:00 2001 From: Art3mix Date: Tue, 3 Jan 2023 23:11:41 +0200 Subject: [PATCH 12/15] added the new stuff to build_ics_packet --- .../cw-ics721-bridge/src/testing/ibc_tests.rs | 63 ++++++++++++++++--- 1 file changed, 54 insertions(+), 9 deletions(-) diff --git a/contracts/cw-ics721-bridge/src/testing/ibc_tests.rs b/contracts/cw-ics721-bridge/src/testing/ibc_tests.rs index 27cbd62a..f214e911 100644 --- a/contracts/cw-ics721-bridge/src/testing/ibc_tests.rs +++ b/contracts/cw-ics721-bridge/src/testing/ibc_tests.rs @@ -93,22 +93,25 @@ fn do_instantiate(deps: DepsMut, env: Env, sender: &str) -> Result, + class_data: Option, token_ids: Vec<&str>, token_uris: Option>, + token_data: Option>, sender: &str, receiver: &str, + memo: Option<&str>, ) -> NonFungibleTokenPacketData { NonFungibleTokenPacketData { class_id: ClassId::new(class_id), class_uri: class_uri.map(|s| s.to_string()), // TODO: test me. - class_data: None, - token_data: None, + class_data, token_ids: token_ids.into_iter().map(TokenId::new).collect(), token_uris: token_uris.map(|t| t.into_iter().map(|s| s.to_string()).collect()), + token_data, sender: sender.to_string(), receiver: receiver.to_string(), - memo: None, + memo: memo.map(|t| t.to_string()), } } @@ -433,26 +436,59 @@ fn test_ibc_packet_receive_invalid_packet_data() { #[test] fn test_ibc_packet_receive_missmatched_lengths() { + let mut deps = mock_dependencies(); + + PO.set_pauser(&mut deps.storage, &deps.api, None).unwrap(); + + // More URIs are provided than tokens. let data = build_ics_packet( "bad kids", None, + None, vec!["kid A"], - // More URIs are provided than tokens. + Some(vec!["a", "b"]), + None, "ekez", "callum", + None, ); let packet = IbcPacketReceiveMsg::new( mock_packet(to_binary(&data).unwrap()), Addr::unchecked(RELAYER_ADDR), ); - let mut deps = mock_dependencies(); - let env = mock_env(); - PO.set_pauser(&mut deps.storage, &deps.api, None).unwrap(); + let res = ibc_packet_receive(deps.as_mut(), mock_env(), packet); - let res = ibc_packet_receive(deps.as_mut(), env, packet); + assert!(res.is_ok()); + let error = try_get_ack_error(&IbcAcknowledgement::new(res.unwrap().acknowledgement)); + + assert_eq!( + error, + Some(ContractError::TokenInfoLenMissmatch {}.to_string()) + ); + + // More token data are provided than tokens. + let token_data = Some(vec![to_binary("some_data_1").unwrap(), to_binary("some_data_2").unwrap()]); + let data = build_ics_packet( + "bad kids", + None, + None, + vec!["kid A"], + Some(vec!["a"]), + token_data, + "ekez", + "callum", + None, + ); + + let packet = IbcPacketReceiveMsg::new( + mock_packet(to_binary(&data).unwrap()), + Addr::unchecked(RELAYER_ADDR), + ); + + let res = ibc_packet_receive(deps.as_mut(), mock_env(), packet); assert!(res.is_ok()); let error = try_get_ack_error(&IbcAcknowledgement::new(res.unwrap().acknowledgement)); @@ -465,21 +501,30 @@ fn test_ibc_packet_receive_missmatched_lengths() { #[test] fn test_packet_json() { + let class_data = to_binary("some_class_data").unwrap(); // InNvbWVfY2xhc3NfZGF0YSI= + let token_data = vec![ // ["InNvbWVfdG9rZW5fZGF0YV8xIg==","InNvbWVfdG9rZW5fZGF0YV8yIg==","InNvbWVfdG9rZW5fZGF0YV8zIg=="] + to_binary("some_token_data_1").unwrap(), + to_binary("some_token_data_2").unwrap(), + to_binary("some_token_data_3").unwrap(), + ]; let packet = build_ics_packet( "stars1zedxv25ah8fksmg2lzrndrpkvsjqgk4zt5ff7n", Some("https://metadata-url.com/my-metadata"), + Some(class_data), vec!["1", "2", "3"], Some(vec![ "https://metadata-url.com/my-metadata1", "https://metadata-url.com/my-metadata2", "https://metadata-url.com/my-metadata3", ]), + Some(token_data), "stars1zedxv25ah8fksmg2lzrndrpkvsjqgk4zt5ff7n", "wasm1fucynrfkrt684pm8jrt8la5h2csvs5cnldcgqc", + Some("some_memo"), ); // Example message generated from the SDK // TODO: test with non-null tokenData and classData. - let expected = r#"{"classId":"stars1zedxv25ah8fksmg2lzrndrpkvsjqgk4zt5ff7n","classUri":"https://metadata-url.com/my-metadata","classData":null,"tokenIds":["1","2","3"],"tokenUris":["https://metadata-url.com/my-metadata1","https://metadata-url.com/my-metadata2","https://metadata-url.com/my-metadata3"],"tokenData":null,"sender":"stars1zedxv25ah8fksmg2lzrndrpkvsjqgk4zt5ff7n","receiver":"wasm1fucynrfkrt684pm8jrt8la5h2csvs5cnldcgqc","memo":null}"#; + let expected = r#"{"classId":"stars1zedxv25ah8fksmg2lzrndrpkvsjqgk4zt5ff7n","classUri":"https://metadata-url.com/my-metadata","classData":"InNvbWVfY2xhc3NfZGF0YSI=","tokenIds":["1","2","3"],"tokenUris":["https://metadata-url.com/my-metadata1","https://metadata-url.com/my-metadata2","https://metadata-url.com/my-metadata3"],"tokenData":["InNvbWVfdG9rZW5fZGF0YV8xIg==","InNvbWVfdG9rZW5fZGF0YV8yIg==","InNvbWVfdG9rZW5fZGF0YV8zIg=="],"sender":"stars1zedxv25ah8fksmg2lzrndrpkvsjqgk4zt5ff7n","receiver":"wasm1fucynrfkrt684pm8jrt8la5h2csvs5cnldcgqc","memo":"some_memo"}"#; let encdoded = String::from_utf8(to_vec(&packet).unwrap()).unwrap(); assert_eq!(expected, encdoded.as_str()); From d49faed136b3a9218553f9fff69f4dbe0b90cad4 Mon Sep 17 00:00:00 2001 From: Art3mix Date: Tue, 3 Jan 2023 23:19:45 +0200 Subject: [PATCH 13/15] fmt/clippy --- contracts/cw-ics721-bridge/src/contract.rs | 6 +----- .../cw-ics721-bridge/src/testing/ibc_tests.rs | 19 ++++++++----------- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/contracts/cw-ics721-bridge/src/contract.rs b/contracts/cw-ics721-bridge/src/contract.rs index e51f491b..9d1132ee 100644 --- a/contracts/cw-ics721-bridge/src/contract.rs +++ b/contracts/cw-ics721-bridge/src/contract.rs @@ -14,12 +14,8 @@ use crate::{ UniversalNftInfoResponse, CLASS_ID_TO_CLASS, CLASS_ID_TO_NFT_CONTRACT, CLASS_TOKEN_ID_TO_TOKEN_METADATA, CW721_CODE_ID, NFT_CONTRACT_TO_CLASS_ID, OUTGOING_CLASS_TOKEN_TO_CHANNEL, PO, PROXY, - UniversalNftInfoResponse, CLASS_ID_TO_CLASS, CLASS_ID_TO_NFT_CONTRACT, - CLASS_TOKEN_ID_TO_TOKEN_METADATA, CW721_CODE_ID, NFT_CONTRACT_TO_CLASS_ID, - OUTGOING_CLASS_TOKEN_TO_CHANNEL, PO, PROXY, }, token_types::{Class, ClassId, Token, TokenId, VoucherCreation, VoucherRedemption}, - token_types::{Class, ClassId, Token, TokenId, VoucherCreation, VoucherRedemption}, }; const CONTRACT_NAME: &str = "crates.io:cw-ics721-bridge"; @@ -325,7 +321,7 @@ fn callback_redeem_vouchers( redeem: VoucherRedemption, ) -> Result { let VoucherRedemption { class, token_ids } = redeem; - let nft_contract = CLASS_ID_TO_NFT_CONTRACT.load(deps.storage, class.id.clone())?; + let nft_contract = CLASS_ID_TO_NFT_CONTRACT.load(deps.storage, class.id)?; let receiver = deps.api.addr_validate(&receiver)?; Ok(Response::default() .add_attribute("method", "callback_redeem_vouchers") diff --git a/contracts/cw-ics721-bridge/src/testing/ibc_tests.rs b/contracts/cw-ics721-bridge/src/testing/ibc_tests.rs index fbaeef43..2eaff1c1 100644 --- a/contracts/cw-ics721-bridge/src/testing/ibc_tests.rs +++ b/contracts/cw-ics721-bridge/src/testing/ibc_tests.rs @@ -91,6 +91,7 @@ fn do_instantiate(deps: DepsMut, env: Env, sender: &str) -> Result, @@ -411,10 +412,6 @@ fn test_ibc_channel_connect_invalid_version_counterparty() { #[test] fn test_ibc_packet_receive_invalid_packet_data() { - // the actual message used here is unimportant. this just - // constructs a valud JSON blob that is not a valid ICS-721 - // packet. - let data = to_binary(&QueryMsg::ClassMetadata { // the actual message used here is unimportant. this just // constructs a valud JSON blob that is not a valid ICS-721 // packet. @@ -476,7 +473,6 @@ fn test_ibc_packet_receive_missmatched_lengths() { None, None, vec!["kid A"], - Some(vec!["a", "b"]), None, "ekez", @@ -500,7 +496,10 @@ fn test_ibc_packet_receive_missmatched_lengths() { ); // More token data are provided than tokens. - let token_data = Some(vec![to_binary("some_data_1").unwrap(), to_binary("some_data_2").unwrap()]); + let token_data = Some(vec![ + to_binary("some_data_1").unwrap(), + to_binary("some_data_2").unwrap(), + ]); let data = build_ics_packet( "bad kids", None, @@ -532,7 +531,8 @@ fn test_ibc_packet_receive_missmatched_lengths() { #[test] fn test_packet_json() { let class_data = to_binary("some_class_data").unwrap(); // InNvbWVfY2xhc3NfZGF0YSI= - let token_data = vec![ // ["InNvbWVfdG9rZW5fZGF0YV8xIg==","InNvbWVfdG9rZW5fZGF0YV8yIg==","InNvbWVfdG9rZW5fZGF0YV8zIg=="] + let token_data = vec![ + // ["InNvbWVfdG9rZW5fZGF0YV8xIg==","InNvbWVfdG9rZW5fZGF0YV8yIg==","InNvbWVfdG9rZW5fZGF0YV8zIg=="] to_binary("some_token_data_1").unwrap(), to_binary("some_token_data_2").unwrap(), to_binary("some_token_data_3").unwrap(), @@ -554,7 +554,7 @@ fn test_packet_json() { ); // Example message generated from the SDK // TODO: test with non-null tokenData and classData. - let expected = r#"{"classId":"stars1zedxv25ah8fksmg2lzrndrpkvsjqgk4zt5ff7n","classUri":"https://metadata-url.com/my-metadata","classData":"InNvbWVfY2xhc3NfZGF0YSI=","tokenIds":["1","2","3"],"tokenUris":["https://metadata-url.com/my-metadata1","https://metadata-url.com/my-metadata2","https://metadata-url.com/my-metadata3"],"tokenData":["InNvbWVfdG9rZW5fZGF0YV8xIg==","InNvbWVfdG9rZW5fZGF0YV8yIg==","InNvbWVfdG9rZW5fZGF0YV8zIg=="],"sender":"stars1zedxv25ah8fksmg2lzrndrpkvsjqgk4zt5ff7n","receiver":"wasm1fucynrfkrt684pm8jrt8la5h2csvs5cnldcgqc","memo":"some_memo"}"#; + let expected = r#"{"classId":"stars1zedxv25ah8fksmg2lzrndrpkvsjqgk4zt5ff7n","classUri":"https://metadata-url.com/my-metadata","classData":null,"tokenIds":["1","2","3"],"tokenUris":["https://metadata-url.com/my-metadata1","https://metadata-url.com/my-metadata2","https://metadata-url.com/my-metadata3"],"tokenData":null,"sender":"stars1zedxv25ah8fksmg2lzrndrpkvsjqgk4zt5ff7n","receiver":"wasm1fucynrfkrt684pm8jrt8la5h2csvs5cnldcgqc"}"#; let encdoded = String::from_utf8(to_vec(&packet).unwrap()).unwrap(); assert_eq!(expected, encdoded.as_str()); @@ -562,9 +562,6 @@ fn test_packet_json() { #[test] fn test_no_receive_when_paused() { - // Valid JSON, invalid ICS-721 packet. Tests that we check for - // pause status before attempting validation. - let data = to_binary(&QueryMsg::ClassMetadata { // Valid JSON, invalid ICS-721 packet. Tests that we check for // pause status before attempting validation. let data = to_binary(&QueryMsg::ClassMetadata { From d77f7cfe09c65fcfa9fa7852e8f3d62660b5a583 Mon Sep 17 00:00:00 2001 From: Art3mix Date: Tue, 3 Jan 2023 23:22:39 +0200 Subject: [PATCH 14/15] fixes --- contracts/cw-ics721-bridge/src/testing/ibc_tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/cw-ics721-bridge/src/testing/ibc_tests.rs b/contracts/cw-ics721-bridge/src/testing/ibc_tests.rs index 2eaff1c1..039e5524 100644 --- a/contracts/cw-ics721-bridge/src/testing/ibc_tests.rs +++ b/contracts/cw-ics721-bridge/src/testing/ibc_tests.rs @@ -554,7 +554,7 @@ fn test_packet_json() { ); // Example message generated from the SDK // TODO: test with non-null tokenData and classData. - let expected = r#"{"classId":"stars1zedxv25ah8fksmg2lzrndrpkvsjqgk4zt5ff7n","classUri":"https://metadata-url.com/my-metadata","classData":null,"tokenIds":["1","2","3"],"tokenUris":["https://metadata-url.com/my-metadata1","https://metadata-url.com/my-metadata2","https://metadata-url.com/my-metadata3"],"tokenData":null,"sender":"stars1zedxv25ah8fksmg2lzrndrpkvsjqgk4zt5ff7n","receiver":"wasm1fucynrfkrt684pm8jrt8la5h2csvs5cnldcgqc"}"#; + let expected = r#"{"classId":"stars1zedxv25ah8fksmg2lzrndrpkvsjqgk4zt5ff7n","classUri":"https://metadata-url.com/my-metadata","classData":"InNvbWVfY2xhc3NfZGF0YSI=","tokenIds":["1","2","3"],"tokenUris":["https://metadata-url.com/my-metadata1","https://metadata-url.com/my-metadata2","https://metadata-url.com/my-metadata3"],"tokenData":["InNvbWVfdG9rZW5fZGF0YV8xIg==","InNvbWVfdG9rZW5fZGF0YV8yIg==","InNvbWVfdG9rZW5fZGF0YV8zIg=="],"sender":"stars1zedxv25ah8fksmg2lzrndrpkvsjqgk4zt5ff7n","receiver":"wasm1fucynrfkrt684pm8jrt8la5h2csvs5cnldcgqc","memo":"some_memo"}"#; let encdoded = String::from_utf8(to_vec(&packet).unwrap()).unwrap(); assert_eq!(expected, encdoded.as_str()); From c8171b1dbcb028cf7f14950d05946b77015bf5b0 Mon Sep 17 00:00:00 2001 From: Art3mix Date: Wed, 4 Jan 2023 00:54:10 +0200 Subject: [PATCH 15/15] remove todo --- contracts/cw-ics721-bridge/src/testing/ibc_tests.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/cw-ics721-bridge/src/testing/ibc_tests.rs b/contracts/cw-ics721-bridge/src/testing/ibc_tests.rs index 039e5524..48835f73 100644 --- a/contracts/cw-ics721-bridge/src/testing/ibc_tests.rs +++ b/contracts/cw-ics721-bridge/src/testing/ibc_tests.rs @@ -106,7 +106,6 @@ fn build_ics_packet( NonFungibleTokenPacketData { class_id: ClassId::new(class_id), class_uri: class_uri.map(|s| s.to_string()), - // TODO: test me. class_data, token_ids: token_ids.into_iter().map(TokenId::new).collect(), token_uris: token_uris.map(|t| t.into_iter().map(|s| s.to_string()).collect()),