-
Notifications
You must be signed in to change notification settings - Fork 30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Final audit revision #90
Final audit revision #90
Conversation
…e into CLASS_ID_AND_NFT_CONTRACT_INFO
…yet, also keep legacy store
packages/ics721/src/execute.rs
Outdated
// we migrate only in case CLASS_ID_AND_NFT_CONTRACT_INFO is not populated yet | ||
// TODO once migrated: | ||
// - this complete block can be deleted | ||
// - legacy map 'e' and 'f' can be deleted | ||
let is_empty = query_nft_contracts(deps.as_ref(), None, None) | ||
.map(|nft_contracts| nft_contracts.is_empty())?; | ||
if is_empty { | ||
// - get legacy map and migrate it to new indexed map | ||
let legacy_nft_contract_to_class_id: Map<Addr, ClassId> = Map::new("f"); | ||
match cw_paginate_storage::paginate_map( | ||
deps.as_ref(), | ||
&legacy_nft_contract_to_class_id, | ||
None, | ||
None, | ||
Order::Ascending, | ||
) { | ||
Ok(nft_contract_and_class_id) => { | ||
let response = response.add_attribute( | ||
"migrated nft contracts", | ||
nft_contract_and_class_id.len().to_string(), | ||
); | ||
for (nft_contract, class_id) in nft_contract_and_class_id { | ||
let class_id_info = ClassIdInfo { | ||
class_id: class_id.clone(), | ||
address: nft_contract.clone(), | ||
}; | ||
CLASS_ID_AND_NFT_CONTRACT_INFO.save( | ||
deps.storage, | ||
&class_id, | ||
&class_id_info, | ||
)?; | ||
} | ||
Ok(response) | ||
} | ||
Err(err) => Err(ContractError::Std(err)), | ||
} | ||
} else { | ||
Ok(response) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the migration logic for CLASS_ID_AND_NFT_CONTRACT_INFO
fn test_migrate() { | ||
let mut deps = mock_dependencies(); | ||
let env = mock_env(); | ||
let info = mock_info(OWNER_ADDR, &[]); | ||
let msg = instantiate_msg(None, None); | ||
Ics721Contract {} | ||
.instantiate(deps.as_mut(), env.clone(), info, msg.clone()) | ||
.unwrap(); | ||
let msg = MigrateMsg::WithUpdate { | ||
pauser: Some("some_other_pauser".to_string()), | ||
outgoing_proxy: Some("outgoing".to_string()), | ||
incoming_proxy: Some("incoming".to_string()), | ||
cw721_base_code_id: Some(1), | ||
cw721_admin: Some("some_other_admin".to_string()), | ||
}; | ||
|
||
// before migrate, populate legacy | ||
let class_id_to_nft_contract: Map<ClassId, Addr> = Map::new("e"); | ||
class_id_to_nft_contract | ||
.save( | ||
deps.as_mut().storage, | ||
ClassId::new(CLASS_ID_1), | ||
&Addr::unchecked(NFT_CONTRACT_1), | ||
) | ||
.unwrap(); | ||
class_id_to_nft_contract | ||
.save( | ||
deps.as_mut().storage, | ||
ClassId::new(CLASS_ID_2), | ||
&Addr::unchecked(NFT_CONTRACT_2), | ||
) | ||
.unwrap(); | ||
let nft_contract_to_class_id: Map<Addr, ClassId> = Map::new("f"); | ||
nft_contract_to_class_id | ||
.save( | ||
deps.as_mut().storage, | ||
Addr::unchecked(NFT_CONTRACT_1), | ||
&ClassId::new(CLASS_ID_1), | ||
) | ||
.unwrap(); | ||
nft_contract_to_class_id | ||
.save( | ||
deps.as_mut().storage, | ||
Addr::unchecked(NFT_CONTRACT_2), | ||
&ClassId::new(CLASS_ID_2), | ||
) | ||
.unwrap(); | ||
|
||
// migrate | ||
Ics721Contract {} | ||
.migrate(deps.as_mut(), env.clone(), msg) | ||
.unwrap(); | ||
|
||
assert_eq!( | ||
PO.pauser.load(&deps.storage).unwrap(), | ||
Some(Addr::unchecked("some_other_pauser")) | ||
); | ||
assert_eq!( | ||
OUTGOING_PROXY.load(&deps.storage).unwrap(), | ||
Some(Addr::unchecked("outgoing")) | ||
); | ||
assert_eq!( | ||
INCOMING_PROXY.load(&deps.storage).unwrap(), | ||
Some(Addr::unchecked("incoming")) | ||
); | ||
assert_eq!(CW721_CODE_ID.load(&deps.storage).unwrap(), 1); | ||
assert_eq!( | ||
ADMIN_USED_FOR_CW721.load(&deps.storage).unwrap(), | ||
Some(Addr::unchecked("some_other_admin")) | ||
); | ||
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); | ||
assert_eq!(nft_contract_and_class_id_list[0].1, NFT_CONTRACT_1); | ||
assert_eq!(nft_contract_and_class_id_list[1].0, CLASS_ID_2); | ||
assert_eq!(nft_contract_and_class_id_list[1].1, NFT_CONTRACT_2); | ||
// test query and indexers for class id and addr are working | ||
let nft_contract_1 = | ||
query_nft_contract_for_class_id(&deps.storage, CLASS_ID_1.to_string()).unwrap(); | ||
assert_eq!(nft_contract_1, Some(Addr::unchecked(NFT_CONTRACT_1))); | ||
let nft_contract_2 = | ||
query_nft_contract_for_class_id(&deps.storage, CLASS_ID_2.to_string()).unwrap(); | ||
assert_eq!(nft_contract_2, Some(Addr::unchecked(NFT_CONTRACT_2))); | ||
let class_id_1 = | ||
query_class_id_for_nft_contract(deps.as_ref(), NFT_CONTRACT_1.to_string()).unwrap(); | ||
assert_eq!(class_id_1, Some(ClassId::new(CLASS_ID_1))); | ||
let class_id_2 = | ||
query_class_id_for_nft_contract(deps.as_ref(), NFT_CONTRACT_2.to_string()).unwrap(); | ||
assert_eq!(class_id_2, Some(ClassId::new(CLASS_ID_2))); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
test migration by populating new CLASS_ID_AND_NFT_CONTRACT_INFO
with legacy data from NFT_CONTRACT_TO_CLASS_ID
. Note: CLASS_ID_TO_NFT_CONTRACT
is not used, since it is redundant to NFT_CONTRACT_TO_CLASS_ID
pub const CLASS_ID_TO_NFT_CONTRACT: Map<ClassId, Addr> = Map::new("e"); | ||
/// Maps cw721 contracts to the classID they were instantiated for. | ||
pub const NFT_CONTRACT_TO_CLASS_ID: Map<Addr, ClassId> = Map::new("f"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removing redundant states...
pub const CLASS_ID_AND_NFT_CONTRACT_INFO: IndexedMap<&str, ClassIdInfo, ClassIdInfoIndexes> = | ||
IndexedMap::new( | ||
"m", | ||
ClassIdInfoIndexes { | ||
class_id: UniqueIndex::new(|d| d.class_id.clone(), "class_id_info__class_id"), | ||
address: UniqueIndex::new(|d| d.address.clone(), "class_id_info__address"), | ||
}, | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... and replaced by new state using IndexedMap
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why indexed map instead of the other two maps? What is benefit of putting it into new IndexedMap?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See answer below
&self, | ||
deps: DepsMut, | ||
env: Env, | ||
info: MessageInfo, | ||
nft_contract: &Addr, | ||
token_id: TokenId, | ||
nft_owner: String, | ||
msg: Binary, | ||
) -> Result<Response<T>, ContractError> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
explicit arg for nft_contract
instead of using info
...
match deps.api.addr_validate(&msg.collection) { | ||
Ok(collection_addr) => { | ||
// set collection address as (initial) sender | ||
info.sender = collection_addr; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
before info.sender
was overridden using collection...
.map(|msg| match deps.api.addr_validate(&msg.collection) { | ||
Ok(nft_contract) => self.receive_nft( | ||
deps, | ||
env, | ||
&nft_contract, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
...after change, collection is directly passed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah much better
contracts/sg-ics721/src/execute.rs
Outdated
@@ -48,7 +48,9 @@ impl Ics721Execute for SgIcs721Contract { | |||
// therefore, we use ics721 creator as owner | |||
creator: ics721_contract_info.creator, | |||
description: "".to_string(), | |||
image: "https://arkprotocol.io".to_string(), | |||
// use Stargaze icon as placeholder | |||
image: "ipfs://bafkreie5vwrm5zts4wiq6ebtopmztgl5qzyl4uszyllgwpaizyc5w2uycm" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe this constant should be in a variable somewhere or in an external file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
moved to STARGAZE_ICON_PLACEHOLDER
packages/ics721/src/execute.rs
Outdated
let instantiate = | ||
if CLASS_ID_AND_NFT_CONTRACT_INFO.has(deps.storage, class.id.to_string().as_str()) { | ||
vec![] | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a huge block doing a lot of calculations. Can we put this in a function somewhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
moved to create_instantiate_msg()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome
packages/ics721/src/execute.rs
Outdated
)) | ||
); | ||
|
||
// we migrate only in case CLASS_ID_AND_NFT_CONTRACT_INFO is not populated yet |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can put this in a function so its clear that its legacy code to be deleted? It would make it more clear which code specifically is to be removed after migration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
moved to migrate_legacy()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you
} | ||
pub fn query_class_id_for_nft_contract(deps: Deps, contract: String) -> StdResult<Option<ClassId>> { | ||
let contract = deps.api.addr_validate(&contract)?; | ||
load_class_id_for_nft_contract(deps.storage, &contract) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice reuse
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🙏
@@ -2,6 +2,9 @@ use cosmwasm_schema::cw_serde; | |||
use cosmwasm_std::ContractInfoResponse; | |||
use sg721_base::msg::CollectionInfoResponse; | |||
|
|||
pub const STARGAZE_ICON_PLACEHOLDER: &str = | |||
"ipfs://bafkreie5vwrm5zts4wiq6ebtopmztgl5qzyl4uszyllgwpaizyc5w2uycm"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
waiting for @jhernandezb providing another link
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)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@humanalgorithm as noted in audit:
In the cw-ics721 package, specifically in state.rs at lines 22 and 25, there are two cw-storage-plus::Maps
named CLASS_ID_TO_NFT_CONTRACT and NFT_CONTRACT_TO_CLASS_ID. These maps are responsible for tracking the class_id and collection_addr in a reciprocal manner. However, this setup leads to redundant operations since any modification to these values necessitates updates in both maps.
So both maps hold redundant data - which requires redundant operations like here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's funny, I originally proposed the IndexedMap in the original implementation but my suggestion got shot down.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lol
class_id: UniqueIndex::new(|d| d.class_id.clone(), "class_id_info__class_id"), | ||
address: UniqueIndex::new(|d| d.address.clone(), "class_id_info__address"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@humanalgorithm IndexedMap allows multiple indices. Here we need to put an index on ClassId
and collection address.
.idx | ||
.address |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@humanalgorithm here query is done on address index
.idx | ||
.class_id |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@humanalgorithm here query is done on class id index
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where is execute_admin_clean_and_burn_nft
function being used? can't find it
This is related to #83 and not part of this PR. There was a bug on packet receive in case of failure in sub msgs and states (especially In short: this msg can be called by admin using /// Admin msg in case something goes wrong.
/// As a minimum it clean up states (incoming channel and token metadata), and burn NFT if exists.
AdminCleanAndBurnNft {
owner: String,
token_id: String,
class_id: String,
collection: String,
},
/// Admin msg in case something goes wrong.
/// As a minimum it clean up state (outgoing channel), and transfer NFT if exists.
/// - transfer NFT if exists
AdminCleanAndUnescrowNft {
recipient: String,
token_id: String,
class_id: String,
collection: String,
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LFG
Changes: