diff --git a/contracts/sg-ics721/src/testing/integration_tests.rs b/contracts/sg-ics721/src/testing/integration_tests.rs index f5af98a7..a6bdc3f7 100644 --- a/contracts/sg-ics721/src/testing/integration_tests.rs +++ b/contracts/sg-ics721/src/testing/integration_tests.rs @@ -354,6 +354,7 @@ impl Test { outgoing_proxy, pauser: admin.clone(), cw721_admin: admin, + contract_addr_length: None, }, &[], "sg-ics721", @@ -464,6 +465,13 @@ impl Test { .unwrap() } + fn query_contract_addr_length(&mut self) -> Option { + self.app + .wrap() + .query_wasm_smart(self.ics721.clone(), &QueryMsg::ContractAddrLength {}) + .unwrap() + } + fn query_nft_contracts(&mut self) -> Vec<(String, Addr)> { self.app .wrap() @@ -2350,6 +2358,7 @@ fn test_pause() { outgoing_proxy: None, cw721_base_code_id: None, cw721_admin: None, + contract_addr_length: None, }) .unwrap(), } @@ -2404,6 +2413,7 @@ fn test_migration() { outgoing_proxy: None, cw721_base_code_id: Some(12345678), cw721_admin: Some(admin.to_string()), + contract_addr_length: Some(20), // injective have 20 bytes addresses }) .unwrap(), } @@ -2417,23 +2427,25 @@ fn test_migration() { assert!(proxy.is_none()); let cw721_code_id = test.query_cw721_id(); assert_eq!(cw721_code_id, 12345678); - assert_eq!(test.query_cw721_admin(), Some(admin),); + assert_eq!(test.query_cw721_admin(), Some(admin)); + assert_eq!(test.query_contract_addr_length(), Some(20),); // migrate without changing code id + let msg = MigrateMsg::WithUpdate { + pauser: None, + incoming_proxy: None, + outgoing_proxy: None, + cw721_base_code_id: None, + cw721_admin: Some("".to_string()), + contract_addr_length: None, + }; test.app .execute( test.app.api().addr_make(ICS721_ADMIN_AND_PAUSER), WasmMsg::Migrate { contract_addr: test.ics721.to_string(), new_code_id: test.ics721_id, - msg: to_json_binary(&MigrateMsg::WithUpdate { - pauser: None, - incoming_proxy: None, - outgoing_proxy: None, - cw721_base_code_id: None, - cw721_admin: Some("".to_string()), - }) - .unwrap(), + msg: to_json_binary(&msg).unwrap(), } .into(), ) @@ -2445,5 +2457,6 @@ fn test_migration() { assert!(proxy.is_none()); let cw721_code_id = test.query_cw721_id(); assert_eq!(cw721_code_id, 12345678); - assert_eq!(test.query_cw721_admin(), None,); + assert_eq!(test.query_cw721_admin(), None); + assert_eq!(test.query_contract_addr_length(), None,); } diff --git a/packages/ics721/schema/ics721.json b/packages/ics721/schema/ics721.json index 56202f8d..b59a2e48 100644 --- a/packages/ics721/schema/ics721.json +++ b/packages/ics721/schema/ics721.json @@ -10,6 +10,15 @@ "cw721_base_code_id" ], "properties": { + "contract_addr_length": { + "description": "The optional contract address length being used for instantiate2. In case of None, default length is 32 (standard in cosmwasm).", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, "cw721_admin": { "description": "The admin address for instantiating new cw721 contracts. In case of None, contract is immutable.", "type": [ @@ -1047,6 +1056,20 @@ }, "additionalProperties": false }, + { + "description": "Gets the contract address length being used for instantiate2. In case of None, default length is 32 (standard in cosmwasm).", + "type": "object", + "required": [ + "contract_addr_length" + ], + "properties": { + "contract_addr_length": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Gets a list of classID as key (from NonFungibleTokenPacketData) and cw721 contract as value (instantiated for that classID).", "type": "object", @@ -1255,6 +1278,16 @@ } } }, + "contract_addr_length": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Nullable_uint32", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, "cw721_admin": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Nullable_Nullable_Addr", diff --git a/packages/ics721/schema/raw/instantiate.json b/packages/ics721/schema/raw/instantiate.json index 52fd2a18..68486ce8 100644 --- a/packages/ics721/schema/raw/instantiate.json +++ b/packages/ics721/schema/raw/instantiate.json @@ -6,6 +6,15 @@ "cw721_base_code_id" ], "properties": { + "contract_addr_length": { + "description": "The optional contract address length being used for instantiate2. In case of None, default length is 32 (standard in cosmwasm).", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, "cw721_admin": { "description": "The admin address for instantiating new cw721 contracts. In case of None, contract is immutable.", "type": [ diff --git a/packages/ics721/schema/raw/query.json b/packages/ics721/schema/raw/query.json index 0bcdce7e..b6c82f81 100644 --- a/packages/ics721/schema/raw/query.json +++ b/packages/ics721/schema/raw/query.json @@ -203,6 +203,20 @@ }, "additionalProperties": false }, + { + "description": "Gets the contract address length being used for instantiate2. In case of None, default length is 32 (standard in cosmwasm).", + "type": "object", + "required": [ + "contract_addr_length" + ], + "properties": { + "contract_addr_length": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Gets a list of classID as key (from NonFungibleTokenPacketData) and cw721 contract as value (instantiated for that classID).", "type": "object", diff --git a/packages/ics721/schema/raw/response_to_contract_addr_length.json b/packages/ics721/schema/raw/response_to_contract_addr_length.json new file mode 100644 index 00000000..8c78001d --- /dev/null +++ b/packages/ics721/schema/raw/response_to_contract_addr_length.json @@ -0,0 +1,10 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Nullable_uint32", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 +} diff --git a/packages/ics721/src/execute.rs b/packages/ics721/src/execute.rs index 3616e6ac..5e604303 100644 --- a/packages/ics721/src/execute.rs +++ b/packages/ics721/src/execute.rs @@ -25,7 +25,7 @@ use crate::{ }, state::{ ClassIdInfo, CollectionData, UniversalAllNftInfoResponse, ADMIN_USED_FOR_CW721, - CLASS_ID_AND_NFT_CONTRACT_INFO, CLASS_ID_TO_CLASS, CW721_CODE_ID, + CLASS_ID_AND_NFT_CONTRACT_INFO, CLASS_ID_TO_CLASS, CONTRACT_ADDR_LENGTH, CW721_CODE_ID, INCOMING_CLASS_TOKEN_TO_CHANNEL, INCOMING_PROXY, OUTGOING_CLASS_TOKEN_TO_CHANNEL, OUTGOING_PROXY, PO, TOKEN_METADATA, }, @@ -76,6 +76,13 @@ where .transpose()?, )?; + let contract_addr_length = msg.contract_addr_length; + if let Some(contract_addr_length) = contract_addr_length { + CONTRACT_ADDR_LENGTH.save(deps.storage, &contract_addr_length)?; + } else { + CONTRACT_ADDR_LENGTH.remove(deps.storage); + } + Ok(Response::default() .add_submessages(proxies_instantiate) .add_attribute("method", "instantiate") @@ -84,6 +91,10 @@ where "cw721_admin", msg.cw721_admin .map_or_else(|| "immutable".to_string(), |or| or), + ) + .add_attribute( + "contract_addr_length", + contract_addr_length.map_or_else(|| "none".to_string(), |or| or.to_string()), )) } @@ -711,6 +722,7 @@ where outgoing_proxy, cw721_base_code_id, cw721_admin, + contract_addr_length, } => { // disables incoming proxy if none is provided! INCOMING_PROXY.save( @@ -741,6 +753,12 @@ where } } + if let Some(contract_addr_length) = contract_addr_length { + CONTRACT_ADDR_LENGTH.save(deps.storage, &contract_addr_length)?; + } else { + CONTRACT_ADDR_LENGTH.remove(deps.storage); + } + let response = Response::default() .add_attribute("method", "migrate") .add_attribute("pauser", pauser.map_or_else(|| "none".to_string(), |or| or)) @@ -768,6 +786,11 @@ where } }, ), + ) + .add_attribute( + "contract_addr_length", + contract_addr_length + .map_or_else(|| "none".to_string(), |or| or.to_string()), ); self.migrate_legacy(deps, response) diff --git a/packages/ics721/src/helpers.rs b/packages/ics721/src/helpers.rs index f2d83586..91c5a571 100644 --- a/packages/ics721/src/helpers.rs +++ b/packages/ics721/src/helpers.rs @@ -4,7 +4,11 @@ use cosmwasm_std::{ }; use serde::Deserialize; -use crate::{ibc::ACK_CALLBACK_REPLY_ID, state::INCOMING_PROXY, ContractError}; +use crate::{ + ibc::ACK_CALLBACK_REPLY_ID, + state::{CONTRACT_ADDR_LENGTH, INCOMING_PROXY}, + ContractError, +}; use ics721_types::{ ibc_types::NonFungibleTokenPacketData, types::{ @@ -147,8 +151,14 @@ pub fn get_instantiate2_address( let CodeInfoResponse { checksum, .. } = deps.querier.query_wasm_code_info(code_id)?; let canonical_cw721_addr = instantiate2_address(&checksum, &canonical_creator, salt)?; - - Ok(deps.api.addr_humanize(&canonical_cw721_addr)?) + if let Some(contract_addr_length) = CONTRACT_ADDR_LENGTH.may_load(deps.storage)? { + let contract_addr_length = contract_addr_length as usize; + Ok(deps + .api + .addr_humanize(&canonical_cw721_addr[..contract_addr_length].into())?) + } else { + Ok(deps.api.addr_humanize(&canonical_cw721_addr)?) + } } mod test { diff --git a/packages/ics721/src/msg.rs b/packages/ics721/src/msg.rs index ccc9ab75..e2f58ebf 100644 --- a/packages/ics721/src/msg.rs +++ b/packages/ics721/src/msg.rs @@ -31,6 +31,8 @@ pub struct InstantiateMsg { pub pauser: Option, /// The admin address for instantiating new cw721 contracts. In case of None, contract is immutable. pub cw721_admin: Option, + /// The optional contract address length being used for instantiate2. In case of None, default length is 32 (standard in cosmwasm). + pub contract_addr_length: Option, } #[cw_serde] @@ -162,6 +164,10 @@ pub enum QueryMsg { #[returns(Option>)] Cw721Admin {}, + /// Gets the contract address length being used for instantiate2. In case of None, default length is 32 (standard in cosmwasm). + #[returns(Option)] + ContractAddrLength {}, + /// Gets a list of classID as key (from /// NonFungibleTokenPacketData) and cw721 contract as value /// (instantiated for that classID). @@ -212,5 +218,7 @@ pub enum MigrateMsg { cw721_base_code_id: Option, /// The admin address for instantiating new cw721 contracts. In case of "", contract is immutable. cw721_admin: Option, + /// The optional contract address length being used for instantiate2. In case of None, default length is 32 (standard in cosmwasm). + contract_addr_length: Option, }, } diff --git a/packages/ics721/src/query.rs b/packages/ics721/src/query.rs index b4a55de6..afafc25a 100644 --- a/packages/ics721/src/query.rs +++ b/packages/ics721/src/query.rs @@ -5,8 +5,8 @@ use crate::{ msg::QueryMsg, state::{ UniversalAllNftInfoResponse, ADMIN_USED_FOR_CW721, CLASS_ID_AND_NFT_CONTRACT_INFO, - CLASS_ID_TO_CLASS, CW721_CODE_ID, INCOMING_CLASS_TOKEN_TO_CHANNEL, INCOMING_PROXY, - OUTGOING_CLASS_TOKEN_TO_CHANNEL, OUTGOING_PROXY, PO, TOKEN_METADATA, + CLASS_ID_TO_CLASS, CONTRACT_ADDR_LENGTH, CW721_CODE_ID, INCOMING_CLASS_TOKEN_TO_CHANNEL, + INCOMING_PROXY, OUTGOING_CLASS_TOKEN_TO_CHANNEL, OUTGOING_PROXY, PO, TOKEN_METADATA, }, }; use ics721_types::token_types::{Class, ClassId, ClassToken, Token, TokenId}; @@ -35,6 +35,9 @@ pub trait Ics721Query { QueryMsg::IncomingProxy {} => to_json_binary(&INCOMING_PROXY.load(deps.storage)?), QueryMsg::Cw721CodeId {} => to_json_binary(&query_cw721_code_id(deps)?), QueryMsg::Cw721Admin {} => to_json_binary(&ADMIN_USED_FOR_CW721.load(deps.storage)?), + QueryMsg::ContractAddrLength {} => { + to_json_binary(&CONTRACT_ADDR_LENGTH.may_load(deps.storage)?) + } QueryMsg::NftContracts { start_after, limit } => { to_json_binary(&query_nft_contracts(deps, start_after, limit)?) } diff --git a/packages/ics721/src/state.rs b/packages/ics721/src/state.rs index 5f69287f..0c43b0bc 100644 --- a/packages/ics721/src/state.rs +++ b/packages/ics721/src/state.rs @@ -38,15 +38,22 @@ pub const CLASS_ID_TO_CLASS: Map = Map::new("g"); 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"); + /// Maps (class ID, token ID) -> token metadata. Used to store /// on-chain metadata for tokens that have arrived from other /// chains. When a token arrives, it's metadata (regardless of if it /// is `None`) is stored in this map. When the token is returned to /// it's source chain, the metadata is removed from the map. pub const TOKEN_METADATA: Map<(ClassId, TokenId), Option> = Map::new("j"); + /// The admin address for instantiating new cw721 contracts. In case of None, contract is immutable. pub const ADMIN_USED_FOR_CW721: Item> = Item::new("l"); +/// The optional contract address length being used for instantiate2. In case of None, default length is 32 (standard in cosmwasm). +/// So length must be shorter than 32. For example, Injective has 20 length address. +/// Bug: https://github.com/CosmWasm/cosmwasm/issues/2155 +pub const CONTRACT_ADDR_LENGTH: Item = Item::new("n"); + #[derive(Deserialize)] pub struct UniversalAllNftInfoResponse { pub access: UniversalOwnerOfResponse, diff --git a/packages/ics721/src/testing/contract.rs b/packages/ics721/src/testing/contract.rs index 409fe612..8f057ea7 100644 --- a/packages/ics721/src/testing/contract.rs +++ b/packages/ics721/src/testing/contract.rs @@ -20,8 +20,8 @@ use crate::{ Ics721Query, }, state::{ - CollectionData, ADMIN_USED_FOR_CW721, CLASS_ID_TO_CLASS, CW721_CODE_ID, INCOMING_PROXY, - OUTGOING_CLASS_TOKEN_TO_CHANNEL, OUTGOING_PROXY, PO, + CollectionData, ADMIN_USED_FOR_CW721, CLASS_ID_TO_CLASS, CONTRACT_ADDR_LENGTH, + CW721_CODE_ID, INCOMING_PROXY, OUTGOING_CLASS_TOKEN_TO_CHANNEL, OUTGOING_PROXY, PO, }, utils::get_collection_data, }; @@ -504,6 +504,7 @@ fn instantiate_msg( outgoing_proxy, pauser: Some(PAUSER_ADDR.to_string()), cw721_admin: Some(ADMIN_ADDR.to_string()), + contract_addr_length: None, } } @@ -528,10 +529,11 @@ fn test_instantiate() { }), label: "outgoing".to_string(), }; - let msg = instantiate_msg( + let mut msg = instantiate_msg( Some(incoming_proxy_init_msg.clone()), Some(outgoing_proxy_init_msg.clone()), ); + msg.contract_addr_length = Some(20); let response = Ics721Contract {} .instantiate(deps.as_mut(), env.clone(), info, msg.clone()) .unwrap(); @@ -550,7 +552,8 @@ fn test_instantiate() { )) .add_attribute("method", "instantiate") .add_attribute("cw721_code_id", msg.cw721_base_code_id.to_string()) - .add_attribute("cw721_admin", ADMIN_ADDR); + .add_attribute("cw721_admin", ADMIN_ADDR) + .add_attribute("contract_addr_length", "20"); assert_eq!(response, expected_response); assert_eq!(CW721_CODE_ID.load(&deps.storage).unwrap(), 0); // incoming and outgoing proxy initially set to None and set later in sub msg @@ -565,6 +568,7 @@ fn test_instantiate() { ADMIN_USED_FOR_CW721.load(&deps.storage).unwrap(), Some(Addr::unchecked(ADMIN_ADDR.to_string())) ); + assert_eq!(CONTRACT_ADDR_LENGTH.load(&deps.storage).unwrap(), 20); } #[test] @@ -582,6 +586,7 @@ fn test_migrate() { incoming_proxy: Some("incoming".to_string()), cw721_base_code_id: Some(1), cw721_admin: Some("some_other_admin".to_string()), + contract_addr_length: Some(20), }; // before migrate, populate legacy @@ -638,6 +643,7 @@ fn test_migrate() { ADMIN_USED_FOR_CW721.load(&deps.storage).unwrap(), Some(Addr::unchecked("some_other_admin")) ); + assert_eq!(CONTRACT_ADDR_LENGTH.load(&deps.storage).unwrap(), 20); let nft_contract_and_class_id_list = query_nft_contracts(deps.as_ref(), None, None).unwrap(); assert_eq!(nft_contract_and_class_id_list.len(), 2); assert_eq!(nft_contract_and_class_id_list[0].0, CLASS_ID_1); diff --git a/packages/ics721/src/testing/ibc_tests.rs b/packages/ics721/src/testing/ibc_tests.rs index 09bc90f1..d1ecb44b 100644 --- a/packages/ics721/src/testing/ibc_tests.rs +++ b/packages/ics721/src/testing/ibc_tests.rs @@ -111,6 +111,7 @@ fn do_instantiate(deps: DepsMut, env: Env, sender: &str) -> StdResult outgoing_proxy: None, pauser: None, cw721_admin: None, + contract_addr_length: None, }; Ics721Contract::default().instantiate(deps, env, mock_info(sender, &[]), msg) } diff --git a/packages/ics721/src/testing/integration_tests.rs b/packages/ics721/src/testing/integration_tests.rs index cf3dcf30..09e4e904 100644 --- a/packages/ics721/src/testing/integration_tests.rs +++ b/packages/ics721/src/testing/integration_tests.rs @@ -354,6 +354,7 @@ impl Test { outgoing_proxy, pauser: admin.clone(), cw721_admin: admin.clone(), + contract_addr_length: None, }, &[], "ics721-base", @@ -471,6 +472,13 @@ impl Test { .unwrap() } + fn query_contract_addr_length(&mut self) -> Option { + self.app + .wrap() + .query_wasm_smart(self.ics721.clone(), &QueryMsg::ContractAddrLength {}) + .unwrap() + } + fn query_nft_contracts(&mut self) -> Vec<(String, Addr)> { self.app .wrap() @@ -2251,6 +2259,7 @@ fn test_pause() { outgoing_proxy: None, cw721_base_code_id: None, cw721_admin: None, + contract_addr_length: None, }) .unwrap(), } @@ -2306,6 +2315,7 @@ fn test_migration() { outgoing_proxy: None, cw721_base_code_id: Some(12345678), cw721_admin: Some(admin.to_string()), + contract_addr_length: Some(20), }) .unwrap(), } @@ -2319,23 +2329,25 @@ fn test_migration() { assert!(proxy.is_none()); let cw721_code_id = test.query_cw721_id(); assert_eq!(cw721_code_id, 12345678); - assert_eq!(test.query_cw721_admin(), Some(admin),); + assert_eq!(test.query_cw721_admin(), Some(admin)); + assert_eq!(test.query_contract_addr_length(), Some(20),); // migrate without changing code id + let msg = MigrateMsg::WithUpdate { + pauser: None, + incoming_proxy: None, + outgoing_proxy: None, + cw721_base_code_id: None, + cw721_admin: Some("".to_string()), + contract_addr_length: None, + }; test.app .execute( test.app.api().addr_make(ICS721_ADMIN_AND_PAUSER), WasmMsg::Migrate { contract_addr: test.ics721.to_string(), new_code_id: test.ics721_id, - msg: to_json_binary(&MigrateMsg::WithUpdate { - pauser: None, - incoming_proxy: None, - outgoing_proxy: None, - cw721_base_code_id: None, - cw721_admin: Some("".to_string()), - }) - .unwrap(), + msg: to_json_binary(&msg).unwrap(), } .into(), ) @@ -2347,5 +2359,6 @@ fn test_migration() { assert!(proxy.is_none()); let cw721_code_id = test.query_cw721_id(); assert_eq!(cw721_code_id, 12345678); - assert_eq!(test.query_cw721_admin(), None,); + assert_eq!(test.query_cw721_admin(), None); + assert_eq!(test.query_contract_addr_length(), None); }