Skip to content

Commit

Permalink
Merge pull request #94 from arkprotocol/main
Browse files Browse the repository at this point in the history
scripts for ics721 setup
  • Loading branch information
jhernandezb authored Apr 16, 2024
2 parents ad8f7b4 + e59412c commit 781eeee
Show file tree
Hide file tree
Showing 13 changed files with 756 additions and 2 deletions.
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

0 comments on commit 781eeee

Please sign in to comment.