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

scripts for ics721 setup #94

Merged
merged 8 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
103 changes: 102 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ To gain a better understanding of how ICS721 (interchain) workflows function, co
- Mint an NFT.
- Transfer the NFT from one chain to another.

For testing interchain transfers please check [gist code snippet](https://gist.github.com/taitruong/c561fbebc46a99b3723d37a1dc3ff0af).

## From a thousand feet up

This contract deals in debt-vouchers.
Expand Down Expand Up @@ -78,11 +80,27 @@ These sorts of issues can cause trouble with relayer implementations. The inabil

## Callbacks

There are 2 types of callbacks users can use when transfering an NFT:
cw-ics721 supports [callbacks](./packages/ics721-types/src/types.rs#L67-L70) for Ics721ReceiveCallback and Ics721AckCallback.

1. Receive callback - Callback that is being called on the receiving chain when the NFT was succesfully transferred.
2. Ack callback - Callback that is being called on the sending chain notifying about the status of the transfer.

Workflow:

1. `send_nft` from cw721 -> cw-ics721.
2. `send_nft` holds `IbcOutgoingMsg` msg.
3. `IbcOutgoingMsg` holds `Ics721Memo` with optional receive (request) and ack (response) callbacks.
4. `cw-ics721` on target chain executes optional receive callback.
5. `cw-ics721` sends ack success or ack error to `cw-ics721` on source chain.
6. `cw-ics721` on source chain executes optional ack callback.

NOTES:

In case of 4. if any error occurs on target chain, NFT gets rolled back and return to sender on source chain.
In case of 6. ack callback also holds `Ics721Status::Success` or `Ics721Status::Failed(String)`

### Callback Execution

Callbacks are optional and can be added in the memo field of the transfer message:

```json
Expand All @@ -96,8 +114,91 @@ Callbacks are optional and can be added in the memo field of the transfer messag
}
```

An [Ics721Memo](./packages/ics721-types/src/types.rs#L11-L30) may be provided as part of [IbcOutgoingMsg](./packages/ics721-types/src/ibc_types.rs#L99):

```rust
// -- ibc_types.rs
#[cw_serde]
pub struct IbcOutgoingMsg {
/// The address that should receive the NFT being sent on the
/// *receiving chain*.
pub receiver: String,
/// The *local* channel ID this ought to be sent away on. This
/// contract must have a connection on this channel.
pub channel_id: String,
/// Timeout for the IBC message.
pub timeout: IbcTimeout,
/// Memo to add custom string to the msg
pub memo: Option<String>,
}

// -- types.rs
pub struct Ics721Memo {
pub callbacks: Option<Ics721Callbacks>,
}

/// The format we expect for the memo field on a send
#[cw_serde]
pub struct Ics721Callbacks {
/// Data to pass with a callback on source side (status update)
/// Note - If this field is empty, no callback will be sent
pub ack_callback_data: Option<Binary>,
/// The address that will receive the callback message
/// Defaults to the sender address
pub ack_callback_addr: Option<String>,
/// Data to pass with a callback on the destination side (ReceiveNftIcs721)
/// Note - If this field is empty, no callback will be sent
pub receive_callback_data: Option<Binary>,
/// The address that will receive the callback message
/// Defaults to the receiver address
pub receive_callback_addr: Option<String>,
}

```

In order to execute an ack callback, `ack_callback_data` must not be empty. In order to execute a receive callback, `receive_callback_data` must not be empty.

A contract sending an NFT with callback may look like this:

```rust
let callback_msg = MyAckCallbackMsgData {
// ... any arbitrary data contract wants to
};
let mut callbacks = Ics721Callbacks {
ack_callback_data: Some(to_json_binary(&callback_msg)?),
ack_callback_addr: None, // in case of none ics721 uses recipient (default) as callback addr
receive_callback_data: None,
receive_callback_addr: None,
};
if let Some(counterparty_contract) = COUNTERPARTY_CONTRACT.may_load(deps.storage)? {
callbacks.receive_callback_data = Some(to_json_binary(&callback_msg)?);
callbacks.receive_callback_addr = Some(counterparty_contract); // here we need to set contract addr, since receiver is NFT receiver
}
let memo = Ics721Memo {
callbacks: Some(callbacks),
};
let ibc_msg = IbcOutgoingMsg {
receiver,
channel_id,
timeout: IbcTimeout::with_timestamp(env.block.time.plus_minutes(30)),
memo: Some(Binary::to_base64(&to_json_binary(&memo)?)),
};
// send nft to ics721 (or outgoing proxy if set by ics721)
let send_nft_msg = Cw721ExecuteMsg::SendNft {
contract: 'ADDR_ICS721_OUTGOING_PROXY'.to_string(),
token_id: token_id.to_string(),
msg: to_json_binary(&ibc_msg)?,
};
let send_nft_sub_msg = SubMsg::<Empty>::reply_on_success(
WasmMsg::Execute {
contract_addr: CW721_ADDR.load(storage)?.to_string(),
msg: to_json_binary(&send_nft_msg)?,
funds: vec![],
},
REPLY_NOOP,
);
```

### Contract to accept callbacks

In order for a contract to accept callbacks, it must implement the next messages:
Expand Down
45 changes: 45 additions & 0 deletions scripts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Setup ICS721

For ICS721 it requires these contracts:

- ICS721: the bridge itself
- Incoming Proxy: optional contract for filtering incoming packets
- Outgoing Proxy: optional contract for filtering incoming packets

NOTE:
Below scripts use [select-chain.sh](./select-chain.sh). For each selected chain there is an `.env` file like `stargaze.env` and `osmosis.env`.

## Scripts

### Initial Setup

Scripts for setup must be executed in this order:

1. ICS721 without proxies: [instantiate-ics721.sh](./instantiate-ics721.sh)
2. Incoming Proxy: [instantiate-incoming-proxy.sh](./instantiate-incoming-proxy.sh)
3. Outgoing Proxy: [instantiate-outgoing-proxy.sh](.instantiate-outgoing-proxy.sh)

After instantiation:

- update `ADDR_ICS721`, `ADDR_INCOMING_PROXY`, `ADDR_OUTGOING_PROXY` in env file
- Note: ICS721 is instantiated without(!) proxies, proxies are added via migration (velow)

### Migration

1. ICS721 : [migrate-ics721.sh](./migrate-ics721.sh)
2. Incoming Proxy: [migrate-incoming-proxy.sh](./migrate-incoming-proxy.sh)
3. Outgoing Proxy: [migrate-outgoing-proxy.sh](.migrate-outgoing-proxy.sh)

### Outgoing Proxy Messages

Usage:

```sh
$ ./scripts/whitelist-outgoing-proxy.sh
Usage: ./scripts/whitelist-outgoing-proxy.sh stargaze|osmosis [--add WHITELIST|--remove WHITELIST|--enable true_or_false] --type collection|channel|checksum|fees
Example:
./scripts/whitelist-outgoing-proxy.sh stargaze|osmosis --add channel-1 --type channel
./scripts/whitelist-outgoing-proxy.sh stargaze|osmosis --enable false --type collection
```

The owner of the outgoing proxy contract can add, remove and enable whitelists for collections, channels, collection checksums, and collection fees.
24 changes: 24 additions & 0 deletions scripts/instantiate-ics721.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/bash
# ----------------------------------------------------
# Instantiates the ICS721 contract with cw721_base_code_id and pauser
# ----------------------------------------------------

SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)

# select chain
if [[ -z "$CHAIN" ]]; then
source "$SCRIPT_DIR"/select-chain.sh
CHAIN=$(select_chain)
export CHAIN
fi
echo "reading $SCRIPT_DIR/$CHAIN.env"
source "$SCRIPT_DIR"/"$CHAIN".env

printf -v MSG '{"cw721_base_code_id": %s, "pauser": "%s"}' $CODE_ID_CW721 $WALLET_OWNER
CMD="$CLI tx wasm instantiate $CODE_ID_ICS721 '$MSG' --label 'ICS721 with rate limiter outgoing proxy'"
CMD+=" --from $WALLET --admin $WALLET_ADMIN"
CMD+=" --gas $CLI_GAS --gas-prices $CLI_GAS_PRICES --gas-adjustment $CLI_GAS_ADJUSTMENT"
CMD+=" --chain-id $CHAIN_ID --node $CHAIN_NODE -y"

echo "executing: $CMD" >&2
eval $CMD
27 changes: 27 additions & 0 deletions scripts/instantiate-incoming-proxy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/bin/bash
# ----------------------------------------------------
# Instantiates the Incoming Whitelist Channel Proxy contract
# with the whitelist channels and reference to the ICS721 contract
# ----------------------------------------------------

SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)

# select chain
if [[ -z "$CHAIN" ]]; then
source "$SCRIPT_DIR"/select-chain.sh
CHAIN=$(select_chain)
export CHAIN
fi
echo "reading $SCRIPT_DIR/$CHAIN.env"
source "$SCRIPT_DIR"/"$CHAIN".env

printf -v MSG '{"origin": "%s", "channels": %s}' $ADDR_ICS721 $CHANNELS
LABEL="ICS721 Incoming Whitelist Channel Proxy, Managed by Ark Protocol"
CMD="$CLI tx wasm instantiate $CODE_ID_INCOMING_PROXY '$MSG'"
CMD+=" --label '$LABEL'"
CMD+=" --from $WALLET --admin $WALLET_ADMIN"
CMD+=" --gas $CLI_GAS --gas-prices $CLI_GAS_PRICES --gas-adjustment $CLI_GAS_ADJUSTMENT"
CMD+=" --chain-id $CHAIN_ID --node $CHAIN_NODE -y"

echo "executing: $CMD" >&2
eval $CMD
41 changes: 41 additions & 0 deletions scripts/instantiate-outgoing-proxy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/bin/bash
# ----------------------------------------------------
# Instantiates the ICS721 Outgoing Whitelist Channel Proxy contract
# with the (channels, collections, checksums, collection fees) whitelist and reference to the ICS721 contract
# ----------------------------------------------------

SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)

# select chain
if [[ -z "$CHAIN" ]]; then
source "$SCRIPT_DIR"/select-chain.sh
CHAIN=$(select_chain)
export CHAIN
fi
echo "reading $SCRIPT_DIR/$CHAIN.env"
source "$SCRIPT_DIR"/"$CHAIN".env

MSG=$(
cat <<EOF
{
"config": {
"origin": "$ADDR_ICS721",
"owner": "$WALLET_OWNER"
},
"proxy": {
"rate_limit": {"per_block": 100},
"channels": $CHANNELS,
"collections": $COLLECTIONS
}
}
EOF
)
LABEL="ICS721 Outgoing Whitelist Channel Proxy"
CMD="$CLI tx wasm instantiate $CODE_ID_OUTGOING_PROXY '$MSG'"
CMD+=" --label '$LABEL'"
CMD+=" --from $WALLET --admin $WALLET_ADMIN"
CMD+=" --gas $CLI_GAS --gas-prices $CLI_GAS_PRICES --gas-adjustment $CLI_GAS_ADJUSTMENT"
CMD+=" --chain-id $CHAIN_ID --node $CHAIN_NODE -y"

echo "executing: $CMD" >&2
eval $CMD
64 changes: 64 additions & 0 deletions scripts/migrate-ics721.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#!/bin/bash
# ----------------------------------------------------
# Migrates the ICS721 contract, and sets optional:
# - incoming proxy
# - outgoing proxy
# - cw721 code id
# - pauser
# - cw721 admin
# ----------------------------------------------------

SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)

# select chain
if [[ -z "$CHAIN" ]]; then
source "$SCRIPT_DIR"/select-chain.sh
CHAIN=$(select_chain)
export CHAIN
fi
echo "reading $SCRIPT_DIR/$CHAIN.env"
source "$SCRIPT_DIR"/"$CHAIN".env

function query_config() {
echo "cw721 code id: $($CLI query wasm contract-state smart $ADDR_ICS721 '{"cw721_code_id": {}}' --chain-id $CHAIN_ID --node $CHAIN_NODE | jq)" >&2
echo "cw721 admin: $($CLI query wasm contract-state smart $ADDR_ICS721 '{"cw721_admin": {}}' --chain-id $CHAIN_ID --node $CHAIN_NODE | jq)" >&2
echo "outgoing proxy: $($CLI query wasm contract-state smart $ADDR_ICS721 '{"outgoing_proxy": {}}' --chain-id $CHAIN_ID --node $CHAIN_NODE | jq)" >&2
echo "incoming proxy: $($CLI query wasm contract-state smart $ADDR_ICS721 '{"incoming_proxy": {}}' --chain-id $CHAIN_ID --node $CHAIN_NODE | jq)" >&2
echo "pauser: $($CLI query wasm contract-state smart $ADDR_ICS721 '{"pauser": {}}' --chain-id $CHAIN_ID --node $CHAIN_NODE | jq)" >&2
echo "contract: $($CLI query wasm contract $ADDR_ICS721 --chain-id $CHAIN_ID --node $CHAIN_NODE | jq)" >&2
}

echo "==========================================================================================" >&2
echo "configs before migration $ADDR_ICS721:" >&2
query_config

echo "==========================================================================================" >&2
echo "!!! migrating $ADDR_ICS721 data: proxy: $ADDR_OUTGOING_PROXY, cw721 code id: $CODE_ID_CW721 !!!" >&2 # use CW721 if not set
MSG=$(
cat <<EOF
{"with_update":{
"incoming_proxy": "$ADDR_INCOMING_PROXY",
"outgoing_proxy": "$ADDR_OUTGOING_PROXY",
"cw721_base_code_id": $CODE_ID_CW721,
"pauser": "$WALLET_OWNER",
"cw721_admin": "$WALLET_ADMIN"
}
}
EOF
)
CMD="$CLI tx wasm migrate $ADDR_ICS721 $CODE_ID_ICS721 '$MSG'"
CMD+=" --from $WALLET_ADMIN"
CMD+=" --gas $CLI_GAS --gas-prices $CLI_GAS_PRICES --gas-adjustment $CLI_GAS_ADJUSTMENT"
CMD+=" --chain-id $CHAIN_ID --node $CHAIN_NODE -y"

echo "executing: $CMD" >&2
eval $CMD
if [ $? -ne 0 ]; then
echo "failed to migrate $ADDR_ICS721" >&2
exit 1
fi

echo "==========================================================================================" >&2
echo "configs after migration $ADDR_ICS721:" >&2
sleep 10
query_config
Loading
Loading