Skip to content

Commit

Permalink
Merge pull request #5 from Callum-A/spec-funcs
Browse files Browse the repository at this point in the history
Spec funcs
  • Loading branch information
0xekez authored Jul 20, 2022
2 parents 4281d76 + 323cb57 commit 6b42f1b
Show file tree
Hide file tree
Showing 10 changed files with 262 additions and 79 deletions.
68 changes: 0 additions & 68 deletions .github/workflows/basic.yml

This file was deleted.

2 changes: 2 additions & 0 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions contracts/cw-ics721-bridge/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ cosmwasm-std = { version = "1.0.0", features = ["stargate"] }
cosmwasm-storage = "1.0.0"
cw-storage-plus = "0.13.2"
cw2 = "0.13.2"
cw721 = "0.13.2"
cw721-base = { version = "0.13.2", features = ["library"] }
schemars = "0.8.8"
serde = { version = "1.0.137", default-features = false, features = ["derive"] }
thiserror = { version = "1.0.31" }
Expand Down
74 changes: 67 additions & 7 deletions contracts/cw-ics721-bridge/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
#[cfg(not(feature = "library"))]
use cosmwasm_std::entry_point;
use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult};
use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult};
use cw2::set_contract_version;

use crate::error::ContractError;
use crate::helpers::{burn, get_class, get_nft, get_owner, has_class, transfer};
use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};

const CONTRACT_NAME: &str = "crates.io:cw-ics721-bridge";
Expand All @@ -22,15 +23,74 @@ pub fn instantiate(

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
_deps: DepsMut,
_env: Env,
_info: MessageInfo,
deps: DepsMut,
env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
match msg {}
match msg {
ExecuteMsg::Transfer {
class_id,
token_id,
receiver,
} => execute_transfer(deps, env, info, class_id, token_id, receiver),
ExecuteMsg::Burn { class_id, token_id } => {
execute_burn(deps, env, info, class_id, token_id)
}
}
}

fn execute_transfer(
deps: DepsMut,
env: Env,
info: MessageInfo,
class_id: String,
token_id: String,
receiver: String,
) -> Result<Response, ContractError> {
// This will error if the class_id does not exist so no need to check
let owner = get_owner(deps.as_ref(), class_id.clone(), token_id.clone())?;

// Check if we are the owner or the contract itself
if info.sender != env.contract.address && info.sender != owner.owner {
return Err(ContractError::Unauthorized {});
}

let msg = transfer(deps, class_id, token_id, receiver)?;
Ok(Response::new()
.add_attribute("action", "transfer")
.add_submessage(msg))
}

fn execute_burn(
deps: DepsMut,
env: Env,
info: MessageInfo,
class_id: String,
token_id: String,
) -> Result<Response, ContractError> {
// This will error if the class_id does not exist so no need to check
let owner = get_owner(deps.as_ref(), class_id.clone(), token_id.clone())?;

// Check if we are the owner or the contract itself
if info.sender != env.contract.address && info.sender != owner.owner {
return Err(ContractError::Unauthorized {});
}

let msg = burn(deps, class_id, token_id)?;
Ok(Response::new()
.add_attribute("action", "burn")
.add_submessage(msg))
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
match msg {}
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
match msg {
QueryMsg::GetOwner { token_id, class_id } => {
to_binary(&get_owner(deps, class_id, token_id)?)
}
QueryMsg::GetNft { class_id, token_id } => to_binary(&get_nft(deps, class_id, token_id)?),
QueryMsg::HasClass { class_id } => to_binary(&has_class(deps, class_id)?),
QueryMsg::GetClass { class_id } => to_binary(&get_class(deps, class_id)?),
}
}
9 changes: 9 additions & 0 deletions contracts/cw-ics721-bridge/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,13 @@ pub enum ContractError {

#[error("ICS 721 channels may not be closed.")]
CantCloseChannel {},

#[error("Unrecognised class ID")]
UnrecognisedClassId {},

#[error("Class ID already exists")]
ClassIdAlreadyExists {},

#[error("Unrecognised reply ID")]
UnrecognisedReplyId {},
}
131 changes: 131 additions & 0 deletions contracts/cw-ics721-bridge/src/helpers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
use crate::state::{CLASS_ID_TO_CLASS_URI, CLASS_URI_TO_CLASS_ID};
use crate::ContractError;
use cosmwasm_std::{to_binary, Addr, Deps, DepsMut, Empty, StdResult, SubMsg, WasmMsg};
use cw721::{NftInfoResponse, OwnerOfResponse};

pub const MINT_SUB_MSG_REPLY_ID: u64 = 0;
pub const TRANSFER_SUB_MSG_REPLY_ID: u64 = 1;
pub const BURN_SUB_MSG_REPLY_ID: u64 = 2;

pub fn save_class(deps: DepsMut, class_id: String, class_uri: String) -> Result<(), ContractError> {
if CLASS_ID_TO_CLASS_URI.has(deps.storage, class_id.clone()) {
return Err(ContractError::ClassIdAlreadyExists {});
}

let validated_class_uri = deps.api.addr_validate(&class_uri)?;

if CLASS_URI_TO_CLASS_ID.has(deps.storage, validated_class_uri.clone()) {
return Err(ContractError::ClassIdAlreadyExists {});
}

CLASS_ID_TO_CLASS_URI.save(deps.storage, class_id.clone(), &validated_class_uri)?;
CLASS_URI_TO_CLASS_ID.save(deps.storage, validated_class_uri, &class_id)?;

Ok(())
}

pub fn mint(
deps: DepsMut,
class_id: String,
token_id: String,
token_uri: String,
receiver: String,
) -> Result<SubMsg, ContractError> {
if !CLASS_ID_TO_CLASS_URI.has(deps.storage, class_id.clone()) {
return Err(ContractError::UnrecognisedClassId {});
}

let class_uri = CLASS_ID_TO_CLASS_URI.load(deps.storage, class_id)?;
let mint_msg = cw721_base::ExecuteMsg::<Empty>::Mint(cw721_base::MintMsg::<Empty> {
token_id,
owner: receiver,
token_uri: Some(token_uri),
extension: Empty {},
});
let msg = WasmMsg::Execute {
contract_addr: class_uri.to_string(),
msg: to_binary(&mint_msg)?,
funds: vec![],
};
let msg = SubMsg::reply_always(msg, MINT_SUB_MSG_REPLY_ID);

Ok(msg)
}

pub fn transfer(
deps: DepsMut,
class_id: String,
token_id: String,
receiver: String,
) -> Result<SubMsg, ContractError> {
if !CLASS_ID_TO_CLASS_URI.has(deps.storage, class_id.clone()) {
return Err(ContractError::UnrecognisedClassId {});
}
// Validate receiver
deps.api.addr_validate(&receiver)?;

// No need to perform other checks as we can piggyback on cw721-base erroring for us

let class_uri = CLASS_ID_TO_CLASS_URI.load(deps.storage, class_id)?;
let transfer_msg = cw721_base::ExecuteMsg::<Empty>::TransferNft {
recipient: receiver,
token_id,
};
let msg = WasmMsg::Execute {
contract_addr: class_uri.to_string(),
msg: to_binary(&transfer_msg)?,
funds: vec![],
};

let msg = SubMsg::reply_always(msg, TRANSFER_SUB_MSG_REPLY_ID);
Ok(msg)
}

pub fn burn(deps: DepsMut, class_id: String, token_id: String) -> Result<SubMsg, ContractError> {
if !CLASS_ID_TO_CLASS_URI.has(deps.storage, class_id.clone()) {
return Err(ContractError::UnrecognisedClassId {});
}

let class_uri = CLASS_ID_TO_CLASS_URI.load(deps.storage, class_id)?;
let burn_msg = cw721_base::ExecuteMsg::<Empty>::Burn { token_id };
let msg = WasmMsg::Execute {
contract_addr: class_uri.to_string(),
msg: to_binary(&burn_msg)?,
funds: vec![],
};

let msg = SubMsg::reply_always(msg, BURN_SUB_MSG_REPLY_ID);
Ok(msg)
}

pub fn get_owner(deps: Deps, class_id: String, token_id: String) -> StdResult<OwnerOfResponse> {
let class_uri = CLASS_ID_TO_CLASS_URI.load(deps.storage, class_id)?;
let resp: OwnerOfResponse = deps.querier.query_wasm_smart(
class_uri,
&cw721_base::QueryMsg::OwnerOf {
token_id,
include_expired: None,
},
)?;
Ok(resp)
}

pub fn get_nft(
deps: Deps,
class_id: String,
token_id: String,
) -> StdResult<NftInfoResponse<Empty>> {
let class_uri = CLASS_ID_TO_CLASS_URI.load(deps.storage, class_id)?;
let resp: NftInfoResponse<Empty> = deps
.querier
.query_wasm_smart(class_uri, &cw721_base::QueryMsg::NftInfo { token_id })?;
Ok(resp)
}

pub fn has_class(deps: Deps, class_id: String) -> StdResult<bool> {
Ok(CLASS_ID_TO_CLASS_URI.has(deps.storage, class_id))
}

pub fn get_class(deps: Deps, class_id: String) -> StdResult<Addr> {
CLASS_ID_TO_CLASS_URI.load(deps.storage, class_id)
}
25 changes: 24 additions & 1 deletion contracts/cw-ics721-bridge/src/ibc.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use cosmwasm_std::{
entry_point, to_binary, Binary, DepsMut, Empty, Env, IbcBasicResponse, IbcChannel,
IbcChannelCloseMsg, IbcChannelConnectMsg, IbcChannelOpenMsg, IbcOrder, StdError, StdResult,
IbcChannelCloseMsg, IbcChannelConnectMsg, IbcChannelOpenMsg, IbcOrder, Reply, Response,
StdError, StdResult, SubMsgResult,
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

use crate::helpers::{BURN_SUB_MSG_REPLY_ID, MINT_SUB_MSG_REPLY_ID, TRANSFER_SUB_MSG_REPLY_ID};
use crate::{state::CHANNELS, ContractError};

#[derive(Serialize, Deserialize, JsonSchema)]
Expand Down Expand Up @@ -32,6 +34,27 @@ pub struct NonFungibleTokenPacketData {

pub const IBC_VERSION: &str = "ics721-1";

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn reply(_deps: DepsMut, _env: Env, reply: Reply) -> Result<Response, ContractError> {
match reply.id {
MINT_SUB_MSG_REPLY_ID => Ok(()),
TRANSFER_SUB_MSG_REPLY_ID => Ok(()),
BURN_SUB_MSG_REPLY_ID => Ok(()),
_ => Err(ContractError::UnrecognisedReplyId {}),
}?;

match reply.result {
// On success we do nothing as all is ok!
SubMsgResult::Ok(_) => Ok(Response::new()),
// On error we need to use set_data to override the data field from our
// caller, the IBC packet recv, and acknowledge our failure.
// As per: https://github.com/CosmWasm/cosmwasm/blob/main/SEMANTICS.md#handling-the-reply
SubMsgResult::Err(err) => Ok(Response::new().set_data(
ack_fail(&err).unwrap_or_else(|_e| ack_fail("An unexpected error occurred - error text is hidden because it would serialize as ACK success.").unwrap()),
)),
}
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn ibc_channel_open(
_deps: DepsMut,
Expand Down
1 change: 1 addition & 0 deletions contracts/cw-ics721-bridge/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod contract;
mod error;
pub mod helpers;
pub mod ibc;
pub mod msg;
pub mod state;
Expand Down
Loading

0 comments on commit 6b42f1b

Please sign in to comment.