Skip to content

Commit

Permalink
Implement ICS-721 NFT transfer (#1020)
Browse files Browse the repository at this point in the history
* WIP: add types and contexts

* WIP: add events

* WIP: implement modules

* add send_transfer

* add recv and refund handlers

* add tests

* fix send and recv

* fix context and add tests

* fix fmt

* fix for CI

* fix messages and serde

* fix comments
  • Loading branch information
yito88 authored Jan 4, 2024
1 parent c609f7e commit 7e8c98f
Show file tree
Hide file tree
Showing 28 changed files with 3,205 additions and 58 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ ibc-client-tendermint-types = { version = "0.48.1", path = "./ibc-clients/ics07-
ibc-app-transfer-types = { version = "0.48.1", path = "./ibc-apps/ics20-transfer/types", default-features = false }
ibc-app-nft-transfer-types = { version = "0.48.1", path = "./ibc-apps/ics721-nft-transfer/types", default-features = false }

ibc-proto = { version = "0.39.1", default-features = false }
#ibc-proto = { version = "0.39.1", default-features = false }
ibc-proto = { git = "https://github.com/heliaxdev/ibc-proto-rs", branch = "yuji/feat/ics721-impl", default-features = false }

# cosmos dependencies
tendermint = { version = "0.34.0", default-features = false }
Expand Down
72 changes: 60 additions & 12 deletions ci/no-std-check/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions ibc-apps/ics20-transfer/types/src/msgs/transfer.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! Defines the token transfer message type
//! Defines the Non-Fungible Token Transfer message type
use ibc_core::channel::types::error::PacketError;
use ibc_core::channel::types::timeout::TimeoutHeight;
Expand All @@ -15,7 +15,7 @@ use crate::packet::PacketData;

pub(crate) const TYPE_URL: &str = "/ibc.applications.transfer.v1.MsgTransfer";

/// Message used to build an ICS20 token transfer packet.
/// Message used to build an ICS-721 Non-Fungible Token Transfer packet.
///
/// Note that this message is not a packet yet, as it lacks the proper sequence
/// number, and destination port/channel. This is by design. The sender of the
Expand Down
177 changes: 175 additions & 2 deletions ibc-apps/ics721-nft-transfer/src/context.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,181 @@
//! Defines the required context traits for ICS-721 to interact with host
//! machine.
use ibc_core::host::types::identifiers::{ChannelId, PortId};
use ibc_core::primitives::prelude::*;
use ibc_core::primitives::Signer;

use crate::types::error::NftTransferError;
use crate::types::{
ClassData, ClassId, ClassUri, Memo, PrefixedClassId, TokenData, TokenId, TokenUri,
};

pub trait NftContext {
/// Get the class ID of the token
fn get_class_id(&self) -> &ClassId;

/// Get the token ID
fn get_id(&self) -> &TokenId;

/// Get the token URI
fn get_uri(&self) -> &TokenUri;

/// Get the token Data
fn get_data(&self) -> &TokenData;
}

pub trait NftClassContext {
/// Get the class ID
fn get_id(&self) -> &ClassId;

/// Get the class URI
fn get_uri(&self) -> &ClassUri;

/// Get the class Data
fn get_data(&self) -> &ClassData;
}

/// Read-only methods required in NFT transfer validation context.
pub trait NftTransferValidationContext {}
pub trait NftTransferValidationContext {
type AccountId: TryFrom<Signer> + PartialEq;
type Nft: NftContext;
type NftClass: NftClassContext;

/// get_port returns the portID for the transfer module.
fn get_port(&self) -> Result<PortId, NftTransferError>;

/// Returns Ok() if the host chain supports sending NFTs.
fn can_send_nft(&self) -> Result<(), NftTransferError>;

/// Returns Ok() if the host chain supports receiving NFTs.
fn can_receive_nft(&self) -> Result<(), NftTransferError>;

/// Validates that the NFT can be created or updated successfully.
fn create_or_update_class_validate(
&self,
class_id: &PrefixedClassId,
class_uri: &ClassUri,
class_data: &ClassData,
) -> Result<(), NftTransferError>;

/// Validates that the tokens can be escrowed successfully.
///
/// The owner of the NFT should be checked in this validation.
/// `memo` field allows to incorporate additional contextual details in the
/// escrow validation.
fn escrow_nft_validate(
&self,
from_account: &Self::AccountId,
port_id: &PortId,
channel_id: &ChannelId,
class_id: &PrefixedClassId,
token_id: &TokenId,
memo: &Memo,
) -> Result<(), NftTransferError>;

/// Validates that the NFT can be unescrowed successfully.
fn unescrow_nft_validate(
&self,
to_account: &Self::AccountId,
port_id: &PortId,
channel_id: &ChannelId,
class_id: &PrefixedClassId,
token_id: &TokenId,
) -> Result<(), NftTransferError>;

/// Validates the receiver account and the NFT input
fn mint_nft_validate(
&self,
account: &Self::AccountId,
class_id: &PrefixedClassId,
token_id: &TokenId,
token_uri: &TokenUri,
token_data: &TokenData,
) -> Result<(), NftTransferError>;

/// Validates the sender account and the coin input before burning.
///
/// The owner of the NFT should be checked in this validation.
/// `memo` field allows to incorporate additional contextual details in the
/// burn validation.
fn burn_nft_validate(
&self,
account: &Self::AccountId,
class_id: &PrefixedClassId,
token_id: &TokenId,
memo: &Memo,
) -> Result<(), NftTransferError>;

/// Returns a hash of the prefixed class ID.
/// Implement only if the host chain supports hashed class ID.
fn class_hash_string(&self, _class_id: &PrefixedClassId) -> Option<String> {
None
}

/// Returns the NFT
fn get_nft(
&self,
class_id: &PrefixedClassId,
token_id: &TokenId,
) -> Result<Self::Nft, NftTransferError>;

/// Returns the NFT class
fn get_nft_class(&self, class_id: &PrefixedClassId)
-> Result<Self::NftClass, NftTransferError>;
}

/// Read-write methods required in NFT transfer execution context.
pub trait NftTransferExecutionContext: NftTransferValidationContext {}
pub trait NftTransferExecutionContext: NftTransferValidationContext {
/// Creates a new NFT Class identified by classId. If the class ID already exists, it updates the class metadata.
fn create_or_update_class_execute(
&self,
class_id: &PrefixedClassId,
class_uri: &ClassUri,
class_data: &ClassData,
) -> Result<(), NftTransferError>;

/// Executes the escrow of the NFT in a user account.
///
/// `memo` field allows to incorporate additional contextual details in the
/// escrow execution.
fn escrow_nft_execute(
&mut self,
from_account: &Self::AccountId,
port_id: &PortId,
channel_id: &ChannelId,
class_id: &PrefixedClassId,
token_id: &TokenId,
memo: &Memo,
) -> Result<(), NftTransferError>;

/// Executes the unescrow of the NFT in a user account.
fn unescrow_nft_execute(
&mut self,
to_account: &Self::AccountId,
port_id: &PortId,
channel_id: &ChannelId,
class_id: &PrefixedClassId,
token_id: &TokenId,
) -> Result<(), NftTransferError>;

/// Executes minting of the NFT in a user account.
fn mint_nft_execute(
&mut self,
account: &Self::AccountId,
class_id: &PrefixedClassId,
token_id: &TokenId,
token_uri: &TokenUri,
token_data: &TokenData,
) -> Result<(), NftTransferError>;

/// Executes burning of the NFT in a user account.
///
/// `memo` field allows to incorporate additional contextual details in the
/// burn execution.
fn burn_nft_execute(
&mut self,
account: &Self::AccountId,
class_id: &PrefixedClassId,
token_id: &TokenId,
memo: &Memo,
) -> Result<(), NftTransferError>;
}
Loading

0 comments on commit 7e8c98f

Please sign in to comment.