Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Audit final revision compared to initial audit (based on branch is v0.1.5, commit f1dfefc71c3ace567a5b79e98100ee17d9cfcc5d) #88

Closed
Closed
Changes from 1 commit
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
04fd6a8
new CW721_ADMIN store
taitruong Jan 21, 2024
5016c3a
use latest optimizer version
taitruong Jan 21, 2024
b5c4acb
rename CW721_ADMIN > ADMIN_USED_FOR_CW721
taitruong Jan 22, 2024
09da354
Merge pull request #81 from arkprotocol/cw721_admin
taitruong Jan 23, 2024
fb52da8
test backtransfer to banned recipient, but also to to regular recpient
taitruong Jan 25, 2024
be897b8
e2e ts relayer test:
taitruong Jan 26, 2024
ae6c7d3
fix back transfer, remove entries in outgoing channel only in case al…
taitruong Jan 26, 2024
2afbce7
rename
taitruong Jan 26, 2024
6357673
simplify receive_ibc_packet(), create sub message directly, remove ac…
taitruong Jan 27, 2024
f903de4
fix move to sub message for saving incoming channel entries
taitruong Jan 27, 2024
f43f9c0
move to dedicated functions
taitruong Jan 27, 2024
31ab7b5
docs
taitruong Jan 27, 2024
dec10c1
cleanup
taitruong Jan 27, 2024
94557a5
cargo schema
taitruong Jan 28, 2024
c5a6548
2 new admin msgs for fixing forked NFTs
taitruong Jan 29, 2024
80995ed
docs
taitruong Jan 29, 2024
dc32c5f
test admin msgs
taitruong Jan 29, 2024
545909f
cargo schema
taitruong Jan 29, 2024
1d96023
Merge pull request #83 from arkprotocol/fix_back_transfer
taitruong Jan 31, 2024
2da4752
rename
taitruong Feb 1, 2024
5059fc0
pass nft contract, instead of overriding info.sender
taitruong Jan 22, 2024
d6d6383
remove NFT_CONTRACT_TO_CLASS_ID and CLASS_ID_TO_NFT_CONTRACT and merg…
taitruong Jan 23, 2024
36e0567
test migration
taitruong Jan 23, 2024
86537cb
migrate only in case CLASS_ID_AND_NFT_CONTRACT_INFO is not populated …
taitruong Feb 1, 2024
f7b1b90
fix optional minter
taitruong Feb 9, 2024
7ecfb36
typo
taitruong Feb 12, 2024
2d19b4d
use Stargaze icon as placeholder
taitruong Feb 14, 2024
e8e3c1f
move to constant
taitruong Feb 15, 2024
bbc19e8
move to dedicated function
taitruong Feb 15, 2024
9ef9bd7
docs
taitruong Feb 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix move to sub message for saving incoming channel entries
taitruong committed Jan 27, 2024
commit f903de4aa81621a0f7590af2cfae6dc7cba67cb8
77 changes: 77 additions & 0 deletions ics721_bug.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
Bug in ICS721 when an NFT gets transferred back from chain B to A.

Preliminary: NFT (forward) transferred from chain A to B
- NFT outcoume: NFT escrowed by ICS721 on chain A, NFT minted on newly, instantiated collection on chain B
- state changes:
- entry added on chain A: `OUTGOING_CLASS_TOKEN_TO_CHANNEL` (marker, check for identifying next time, NFT gets transferred back)
- entry added on chain B: `INCOMING_CLASS_TOKEN_TO_CHANNEL`

Expected result on back transfer
- NFT burned on chain B
- "source/OG" NFT escrowed by ICS721 transferred to given recipent
- state changes:
- removed entry in `INCOMING_CLASS_TOKEN_TO_CHANNEL` on chain A
- removed entry in `OUTGOING_CLASS_TOKEN_TO_CHANNEL` on chain B
Actual result:
- NFT burned on chain B
- NFT minted on newly, instantiated collection
- NFT escrowed still escrowed by ICS721 on source collection



By the spec of ics721, it is possible doing bulk transfers (whilst cw721 doesnt support this yet).
I believe this was the main objection of `ActionAggregator`'s design:
1. for each NFT either an `Action::Redemption` or `Action::Creation` is created:
- `Action::Redemption`: unescrow nft on forward transfer or
- `Action::Creation`: mint NFT and instantiate collection
- in addition for redemption/back transfer, entries in `OUTGOING_CLASS_TOKEN_TO_CHANNEL` are removed. That's the bug in contract's tx, we've identified
2. Each NFT action added to `ActionAggregator`
- interestingly `Action` enums are converted to either `VoucherRedemption` or `VoucherCreation` structs
- then converted structs are added to aggregator
- imo conversion not needed here, better approach here:
3. Finally all gets wrapped into a single sub message:
- create message list for each recreation or creation struct in aggregator:
- convert to WasmMsg with:
- `ExecuteMsg::Callback(CallbackMsg::CreateVouchers)` or
- `ExecuteMsg::Callback(CallbackMsg::RedeemVouchers)`
- message list represent a list of nft `operands`
- optional incoming proxy is added on top of operands list
- optional callback is added at the end of operands list
- merge message list into into single, final sub message
- final sub message is of type `reply all` for making sure TX always succeeds
- if there's only one message in list, this will be used for final sub message
- if message list contains more than one entries, they are merged into `ExecuteMsg::Callback(CallbackMsg::Conjunction {operands})`

As a result a single `CallbackMsg` sub msg is created, which is either a
- `CallbackMsg::CreateVouchers`
- appends optional instantiate sub msg
- appends `CallbackMsg::Mint` msg
- `CallbackMsg::RedeemVouchers`
- transfer NFT to recipient
- `CallbackMsg::Conjunction`
- appends all to messages (operands callbacks (`CreateVouchers` or `RedeemVouchers` + optional incoming proxy + optional callback))

This guarantees:
- ics721 contract always succeeds
- each sub message handled serateley
- in case of sub msg failure
- it's partial state is reverted, but not its parent
- read more here: https://github.com/CosmWasm/cosmwasm/blob/main/SEMANTICS.md#submessages)
"... On an error, the subcall will revert any partial state changes due to this message, but not revert any state changes in the calling contract. The error may then be intercepted by the calling contract (for ReplyOn::Always and ReplyOn::Error). In this case, the messages error doesn't abort the whole transaction ..."
- remaining sub messages are not executed
- since operands in conjunction sub msg are added as messages (not sub msgs):
- sub msg is parent of operand messages
- ics721 is root parent of sub msg
- if any msg fails (eg. msg1: tranfer NFT1, msg2: tranfer NFT2)
- all messages and its parent/sub message is reverted
- root parent/ics721 is not reverted

tl;dr:
- `receive_ibc_packet` response contains single callback sub msg, which:
- in case of failure, revert its own partial state, but wont revert contract's TX
- contains one or more messages

The more I think about it, we can create this result response:
- create directly call back msgs
- hence, no intermediate step of Action enums and Action is required

20 changes: 17 additions & 3 deletions packages/ics721/src/execute.rs
Original file line number Diff line number Diff line change
@@ -20,8 +20,9 @@ use crate::{
msg::{CallbackMsg, ExecuteMsg, InstantiateMsg, MigrateMsg},
state::{
CollectionData, UniversalAllNftInfoResponse, ADMIN_USED_FOR_CW721, CLASS_ID_TO_CLASS,
CLASS_ID_TO_NFT_CONTRACT, CW721_CODE_ID, INCOMING_PROXY, NFT_CONTRACT_TO_CLASS_ID,
OUTGOING_CLASS_TOKEN_TO_CHANNEL, OUTGOING_PROXY, PO, TOKEN_METADATA,
CLASS_ID_TO_NFT_CONTRACT, CW721_CODE_ID, INCOMING_CLASS_TOKEN_TO_CHANNEL, INCOMING_PROXY,
NFT_CONTRACT_TO_CLASS_ID, OUTGOING_CLASS_TOKEN_TO_CHANNEL, OUTGOING_PROXY, PO,
TOKEN_METADATA,
},
token_types::{VoucherCreation, VoucherRedemption},
ContractError,
@@ -297,7 +298,9 @@ where
CallbackMsg::RedeemOutgoingChannelEntries(entries) => {
self.callback_redeem_outgoing_channel_entries(deps, entries)
}

CallbackMsg::AddIncomingChannelEntries(entries) => {
self.callback_save_incoming_channel_entries(deps, entries)
}
CallbackMsg::Conjunction { operands } => {
Ok(Response::default().add_messages(operands))
}
@@ -489,6 +492,17 @@ where
Ok(Response::default().add_attribute("method", "callback_redeem_outgoing_channel_entries"))
}

fn callback_save_incoming_channel_entries(
&self,
deps: DepsMut,
entries: Vec<((ClassId, TokenId), String)>,
) -> Result<Response<T>, ContractError> {
for (key, channel) in entries {
INCOMING_CLASS_TOKEN_TO_CHANNEL.save(deps.storage, key, &channel)?;
}
Ok(Response::default().add_attribute("method", "callback_redeem_outgoing_channel_entries"))
}

fn migrate(
&self,
deps: DepsMut,
99 changes: 49 additions & 50 deletions packages/ics721/src/ibc_packet_receive.rs
Original file line number Diff line number Diff line change
@@ -13,10 +13,7 @@ use crate::{
ibc::ACK_AND_DO_NOTHING_REPLY_ID,
ibc_helpers::{get_endpoint_prefix, try_pop_source_prefix},
msg::{CallbackMsg, ExecuteMsg},
state::{
CLASS_ID_TO_NFT_CONTRACT, CW721_CODE_ID, INCOMING_CLASS_TOKEN_TO_CHANNEL,
OUTGOING_CLASS_TOKEN_TO_CHANNEL, PO,
},
state::{CLASS_ID_TO_NFT_CONTRACT, CW721_CODE_ID, OUTGOING_CLASS_TOKEN_TO_CHANNEL, PO},
token_types::{VoucherCreation, VoucherRedemption},
ContractError,
};
@@ -81,14 +78,6 @@ pub(crate) fn receive_ibc_packet(
}
// It's not something we've sent out before => make a
// new NFT.
let local_prefix = get_endpoint_prefix(&packet.dest);
let local_class_id = ClassId::new(format!("{}{}", local_prefix, data.class_id));

INCOMING_CLASS_TOKEN_TO_CHANNEL.save(
deps.storage,
(local_class_id.clone(), token_id.clone()),
&packet.dest.channel_id,
)?;
redemption_or_create.1.push(Token {
id: token_id,
uri: token_uri,
@@ -155,7 +144,7 @@ pub(crate) fn receive_ibc_packet(

let incoming_proxy_msg =
get_incoming_proxy_msg(deps.storage, packet.clone(), cloned_data.clone())?;
let voucher_message = match is_redemption {
let voucher_and_channel_messages = match is_redemption {
true => {
let redemption = VoucherRedemption {
class: Class {
@@ -165,17 +154,22 @@ pub(crate) fn receive_ibc_packet(
},
token_ids: redemption_or_create.0,
};
let redeem_outgoing_class_tokens: Option<Vec<(ClassId, TokenId)>> = Some(
redemption
.token_ids
.clone()
.into_iter()
.map(|token_id| (local_class_id.clone(), token_id))
.collect(),
);
let redeem_outgoing_channels: Vec<(ClassId, TokenId)> = redemption
.token_ids
.clone()
.into_iter()
.map(|token_id| (local_class_id.clone(), token_id))
.collect();
let redeem_outgoing_channels_msg = WasmMsg::Execute {
contract_addr: env.contract.address.to_string(),
msg: to_json_binary(&ExecuteMsg::Callback(
CallbackMsg::RedeemOutgoingChannelEntries(redeem_outgoing_channels),
))?,
funds: vec![],
};
(
redemption.into_wasm_msg(env.contract.address.clone(), receiver.to_string())?,
redeem_outgoing_class_tokens,
redeem_outgoing_channels_msg,
)
}
false => {
@@ -187,18 +181,36 @@ pub(crate) fn receive_ibc_packet(
},
tokens: redemption_or_create.1,
};
let add_incoming_channels: Vec<((ClassId, TokenId), String)> = creation
.tokens
.clone()
.into_iter()
.map(|token| {
(
(local_class_id.clone(), token.id),
packet.dest.channel_id.clone(),
)
})
.collect();
let add_incoming_channels_msg = WasmMsg::Execute {
contract_addr: env.contract.address.to_string(),
msg: to_json_binary(&ExecuteMsg::Callback(
CallbackMsg::AddIncomingChannelEntries(add_incoming_channels),
))?,
funds: vec![],
};
(
creation.into_wasm_msg(env.contract.address.clone(), receiver.to_string())?,
None,
add_incoming_channels_msg,
)
}
};

let submessage = into_submessage(
env.contract.address,
voucher_message.0,
voucher_and_channel_messages.0,
voucher_and_channel_messages.1,
callback_msg,
voucher_message.1,
incoming_proxy_msg,
)?;

@@ -219,41 +231,28 @@ pub(crate) fn receive_ibc_packet(
pub fn into_submessage(
contract: Addr,
voucher_message: WasmMsg,
channel_message: WasmMsg,
callback_msg: Option<WasmMsg>,
redeem_outgoing_class_tokens: Option<Vec<(ClassId, TokenId)>>,
incoming_proxy_msg: Option<WasmMsg>,
) -> StdResult<SubMsg<Empty>> {
let mut m = Vec::with_capacity(3); // 3 is the max number of submessages we can have
let mut operands = Vec::with_capacity(3); // 3 is the max number of submessages we can have
if let Some(incoming_proxy_msg) = incoming_proxy_msg {
m.push(incoming_proxy_msg)
operands.push(incoming_proxy_msg)
}

m.push(voucher_message);
operands.push(voucher_message);

if let Some(callback_msg) = callback_msg {
m.push(callback_msg)
operands.push(callback_msg)
}

// once all other submessages are done, we can redeem entries in the outgoing channel
if let Some(outgoing_class_tokens) = redeem_outgoing_class_tokens {
m.push(WasmMsg::Execute {
contract_addr: contract.to_string(),
msg: to_json_binary(&ExecuteMsg::Callback(
CallbackMsg::RedeemOutgoingChannelEntries(outgoing_class_tokens),
))?,
funds: vec![],
});
}
let message = if m.len() == 1 {
m[0].clone()
} else {
WasmMsg::Execute {
contract_addr: contract.into_string(),
msg: to_json_binary(&ExecuteMsg::Callback(CallbackMsg::Conjunction {
operands: m,
}))?,
funds: vec![],
}
// once all other submessages are done, we can update incoming or outgoing channel
operands.push(channel_message);

let message = WasmMsg::Execute {
contract_addr: contract.into_string(),
msg: to_json_binary(&ExecuteMsg::Callback(CallbackMsg::Conjunction { operands }))?,
funds: vec![],
};
Ok(SubMsg::reply_always(message, ACK_AND_DO_NOTHING_REPLY_ID))
}
2 changes: 2 additions & 0 deletions packages/ics721/src/msg.rs
Original file line number Diff line number Diff line change
@@ -65,6 +65,8 @@ pub enum CallbackMsg {
},
/// Redeem all entries in outgoing channel.
RedeemOutgoingChannelEntries(Vec<(ClassId, TokenId)>),
/// Save all entries in incoming channel.
AddIncomingChannelEntries(Vec<((ClassId, TokenId), String)>),
/// Mints a NFT of collection class_id for receiver with the
/// provided id and metadata. Only callable by this contract.
Mint {
110 changes: 71 additions & 39 deletions packages/ics721/src/testing/ibc_tests.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
use core::panic;

use cosmwasm_schema::cw_serde;
use cosmwasm_std::{
attr, from_json,
testing::{mock_dependencies, mock_env, mock_info},
to_json_binary, to_json_vec, Addr, Attribute, Binary, DepsMut, Empty, Env, IbcAcknowledgement,
IbcChannel, IbcChannelConnectMsg, IbcChannelOpenMsg, IbcEndpoint, IbcOrder, IbcPacket,
IbcPacketReceiveMsg, IbcTimeout, Order, Reply, Response, StdResult, SubMsgResponse,
SubMsgResult, Timestamp, WasmMsg,
IbcPacketReceiveMsg, IbcTimeout, Reply, Response, StdResult, SubMsgResponse, SubMsgResult,
Timestamp, WasmMsg,
};

use crate::{
@@ -14,14 +16,14 @@ use crate::{
ibc_helpers::{ack_fail, ack_success, try_get_ack_error},
msg::{CallbackMsg, ExecuteMsg, InstantiateMsg, QueryMsg},
query::Ics721Query,
state::{CollectionData, INCOMING_CLASS_TOKEN_TO_CHANNEL, NFT_CONTRACT_TO_CLASS_ID, PO},
state::{CollectionData, NFT_CONTRACT_TO_CLASS_ID, PO},
utils::get_collection_data,
ContractError,
};
use ics721_types::{
ibc_types::NonFungibleTokenPacketData,
token_types::{ClassId, TokenId},
types::Ics721Callbacks,
types::{Ics721Callbacks, ReceiverExecuteMsg},
};

const CONTRACT_PORT: &str = "wasm.address1";
@@ -447,32 +449,61 @@ fn test_ibc_packet_receive() {
let mut deps = mock_dependencies();
let env = mock_env();
PO.set_pauser(&mut deps.storage, &deps.api, None).unwrap();
Ics721Contract::default()
let response = Ics721Contract::default()
.ibc_packet_receive(deps.as_mut(), env, packet)
.unwrap();
// assert there is only one message
assert_eq!(response.messages.len(), 1);

// check incoming classID and tokenID
let keys = INCOMING_CLASS_TOKEN_TO_CHANNEL
.keys(deps.as_mut().storage, None, None, Order::Ascending)
.collect::<StdResult<Vec<(String, String)>>>()
.unwrap();
let class_id = format!(
"{}/{}/{}",
ibc_packet.dest.port_id, ibc_packet.dest.channel_id, "id"
);
assert_eq!(keys, [(class_id, "1".to_string())]);

// check channel
let key = (
ClassId::new(keys[0].clone().0),
TokenId::new(keys[0].clone().1),
);
assert_eq!(
INCOMING_CLASS_TOKEN_TO_CHANNEL
.load(deps.as_mut().storage, key)
.unwrap(),
ibc_packet.dest.channel_id,
)
let conjunction_msg = match response.messages[0].msg.clone() {
cosmwasm_std::CosmosMsg::Wasm(WasmMsg::Execute { msg, .. }) => {
match from_json::<ExecuteMsg>(msg.clone()).unwrap() {
ExecuteMsg::Callback(callback_msg) => match callback_msg {
CallbackMsg::Conjunction { operands } => Some(operands),
_ => panic!("unexpected callback msg"),
},
_ => panic!("unexpected execute msg"),
}
}
_ => panic!("unexpected cosmos msg"),
};
assert!(conjunction_msg.is_some());

let operands = conjunction_msg.unwrap();
assert_eq!(operands.len(), 2);

let add_incoming_msg = operands[1].clone();
match add_incoming_msg {
WasmMsg::Execute { msg, .. } => {
match from_json::<ExecuteMsg>(msg).ok() {
Some(msg) => match msg {
ExecuteMsg::Callback(msg) => match msg {
CallbackMsg::AddIncomingChannelEntries(class_token_to_channel_list) => {
let class_token_to_channel_list = class_token_to_channel_list
.into_iter()
.map(|((class, token), channel)| {
((class.to_string(), token.into()), channel)
})
.collect::<Vec<((String, String), String)>>();
// assert there is only one class token to channel entry
let class_id = format!(
"{}/{}/{}",
ibc_packet.dest.port_id, ibc_packet.dest.channel_id, "id"
);
assert_eq!(
class_token_to_channel_list,
[((class_id, "1".to_string()), ibc_packet.dest.channel_id,)]
);
}
_ => panic!("unexpected callback msg"),
},
_ => panic!("unexpected execute msg"),
},
_ => panic!("no callback msg"),
}
}
_ => panic!("unexpected wasm msg"),
}
}

#[test]
@@ -705,20 +736,21 @@ fn test_different_memo_ignored() {
.ibc_packet_receive(deps.as_mut(), env, packet)
.unwrap();

let memo_callback_msg = match res.messages[0].msg.clone() {
cosmwasm_std::CosmosMsg::Wasm(WasmMsg::Execute { msg, .. }) => {
match from_json::<ExecuteMsg>(msg).unwrap() {
ExecuteMsg::Callback(callback_msg) => match callback_msg {
CallbackMsg::Conjunction { operands } => Some(operands),
_ => Some(vec![]),
},
_ => None,
}
if let cosmwasm_std::CosmosMsg::Wasm(WasmMsg::Execute { msg, .. }) = res.messages[0].msg.clone()
{
if let ExecuteMsg::Callback(CallbackMsg::Conjunction { operands }) =
from_json::<ExecuteMsg>(msg).unwrap()
{
// check each operand and make sure there is no memo callback
operands.into_iter().for_each(|operand| {
if let WasmMsg::Execute { msg, .. } = operand {
if let Ok(msg) = from_json::<ReceiverExecuteMsg>(msg) {
panic!("unexpected callback message: {:?}", msg)
}
}
})
}
_ => None,
};
assert!(memo_callback_msg.is_some());
assert!(memo_callback_msg.unwrap().is_empty());
}

#[test]
1 change: 0 additions & 1 deletion packages/ics721/src/testing/integration_tests.rs
Original file line number Diff line number Diff line change
@@ -2163,7 +2163,6 @@ fn test_migration() {
assert_eq!(test.query_cw721_admin(), Some(admin),);

// migrate without changing code id
println!(">>>>>>> migrate without changing code id");
test.app
.execute(
test.app.api().addr_make(ICS721_ADMIN_AND_PAUSER),
10 changes: 10 additions & 0 deletions ts-relayer-tests/src/ics721-utils.ts
Original file line number Diff line number Diff line change
@@ -62,3 +62,13 @@ export function outgoingChannels(
};
return client.sign.queryContractSmart(contractAddress, msg);
}

export function incomingChannels(
client: CosmWasmSigner,
contractAddress: string
): Promise<[[string, string], string][]> {
const msg = {
incoming_channels: {},
};
return client.sign.queryContractSmart(contractAddress, msg);
}
93 changes: 93 additions & 0 deletions ts-relayer-tests/src/ics721.spec.ts
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ import { Order } from "cosmjs-types/ibc/core/channel/v1/channel";
import { instantiateContract } from "./controller";
import { allTokens, mint, ownerOf, sendNft } from "./cw721-utils";
import {
incomingChannels,
migrate,
migrateIncomingProxy,
nftContracts,
@@ -418,6 +419,31 @@ test.serial("transfer NFT: wasmd -> osmo", async (t) => {
t.log(
`transfering to osmo chain via unknown ${otherChannel.channel.src.channelId}`
);
const beforeWasmOutgoingClassTokenToChannelList = await outgoingChannels(
wasmClient,
wasmIcs721
);
const beforeWasmIncomingClassTokenToChannelList = await incomingChannels(
wasmClient,
wasmIcs721
);
const beforeWasmNftContractsToClassIdList = await nftContracts(
wasmClient,
wasmIcs721
);
const beforeOsmoOutgoingClassTokenToChannelList = await outgoingChannels(
osmoClient,
osmoIcs721
);
const beforeOsmoIncomingClassTokenToChannelList = await incomingChannels(
osmoClient,
osmoIcs721
);
const beforeOsmoNftContractsToClassIdList = await nftContracts(
osmoClient,
osmoIcs721
);

ibcMsg = {
receiver: osmoAddr,
channel_id: otherChannel.channel.src.channelId,
@@ -441,6 +467,73 @@ test.serial("transfer NFT: wasmd -> osmo", async (t) => {
t.log("relaying packets");
info = await otherChannel.link.relayAll();
assertAckErrors(info.acksFromA);
// assert no change before and after relay
const afterWasmOutgoingClassTokenToChannelList = await outgoingChannels(
wasmClient,
wasmIcs721
);
const afterWasmIncomingClassTokenToChannelList = await incomingChannels(
wasmClient,
wasmIcs721
);
const afterWasmNftContractsToClassIdList = await nftContracts(
wasmClient,
wasmIcs721
);
t.deepEqual(
beforeWasmOutgoingClassTokenToChannelList,
afterWasmOutgoingClassTokenToChannelList,
`outgoing channels must be unchanged:
- wasm before: ${JSON.stringify(beforeWasmOutgoingClassTokenToChannelList)}
- wasm after: ${JSON.stringify(afterWasmOutgoingClassTokenToChannelList)}`
);
t.deepEqual(
beforeWasmIncomingClassTokenToChannelList,
afterWasmIncomingClassTokenToChannelList,
`incoming channels must be unchanged:
- wasm before: ${JSON.stringify(beforeWasmIncomingClassTokenToChannelList)}
- wasm after: ${JSON.stringify(afterWasmIncomingClassTokenToChannelList)}`
);
t.deepEqual(
beforeWasmNftContractsToClassIdList,
afterWasmNftContractsToClassIdList,
`nft contracts must be unchanged:
- wasm before: ${JSON.stringify(beforeWasmNftContractsToClassIdList)}
- wasm after: ${JSON.stringify(afterWasmNftContractsToClassIdList)}`
);
const afterOsmoOutgoingClassTokenToChannelList = await outgoingChannels(
osmoClient,
osmoIcs721
);
const afterOsmoIncomingClassTokenToChannelList = await incomingChannels(
osmoClient,
osmoIcs721
);
const afterOsmoNftContractsToClassIdList = await nftContracts(
osmoClient,
osmoIcs721
);
t.deepEqual(
beforeOsmoOutgoingClassTokenToChannelList,
afterOsmoOutgoingClassTokenToChannelList,
`outgoing channels must be unchanged:
- osmo before: ${JSON.stringify(beforeOsmoOutgoingClassTokenToChannelList)}
- osmo after: ${JSON.stringify(afterOsmoOutgoingClassTokenToChannelList)}`
);
t.deepEqual(
beforeOsmoIncomingClassTokenToChannelList,
afterOsmoIncomingClassTokenToChannelList,
`incoming channels must be unchanged:
- osmo before: ${JSON.stringify(beforeOsmoIncomingClassTokenToChannelList)}
- osmo after: ${JSON.stringify(afterOsmoIncomingClassTokenToChannelList)}`
);
t.deepEqual(
beforeOsmoNftContractsToClassIdList,
afterOsmoNftContractsToClassIdList,
`nft contracts must be unchanged:
- osmo before: ${JSON.stringify(beforeOsmoNftContractsToClassIdList)}
- osmo after: ${JSON.stringify(afterOsmoNftContractsToClassIdList)}`
);

// assert NFT on chain A is returned to owner
tokenOwner = await ownerOf(wasmClient, wasmCw721, tokenId);

Unchanged files with check annotations Beta

const osmosis = { ...oldOsmo, minFee: "0.025uosmo" };
export function bigIntReplacer(_key: string, value: any) {

Check warning on line 29 in ts-relayer-tests/src/utils.ts

GitHub Actions / build (18.x)

Unexpected any. Specify a different type

Check warning on line 29 in ts-relayer-tests/src/utils.ts

GitHub Actions / build (18.x)

Unexpected any. Specify a different type
if (typeof value === "bigint") {
return value.toString();
}