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

feat: dip721 proxy #17

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
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
11 changes: 8 additions & 3 deletions magic_bridge/ic/src/dip721_proxy/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,17 @@ version = "0.1.0"
edition = "2021"

[lib]
path = "src/lib.rs"
path = "src/main.rs"
crate-type = ["cdylib"]

[dependencies]
ic-kit = "0.4.4"
ic-cdk = "0.4.0"
ic-cdk-macros = "0.3"
candid = "0.7.4"
ic-cdk-macros = "0.4"
hex = "0.4.3"
serde = "1.0.116"
sha3 = "0.9.1"
async-trait = "0.1.51"
serde = "1.0.130"
serde_bytes = "0.11.5"
num-bigint = "0.4.3"
10 changes: 8 additions & 2 deletions magic_bridge/ic/src/dip721_proxy/dip721_proxy.did
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
type Result = variant { Ok : nat; Err : TxError };
type Result_1 = variant { Ok : vec record { text; nat }; Err : text };
type TxError = variant {
InsufficientAllowance;
InsufficientBalance;
ErrorOperationStyle;
Unauthorized;
LedgerTrap;
ErrorTo;
Other : text;
BlockUsed;
AmountTooSmall;
Other : text;
};
service : () -> {
service : {
burn : (principal, principal, nat) -> (Result);
get_all_token_balance : () -> (Result_1);
get_balance : (principal) -> (opt nat);
handle_message : (principal, nat, vec nat) -> (Result);
mint : (principal, nat, vec nat) -> (Result);
widthdraw : (principal, principal) -> (Result);
}
73 changes: 73 additions & 0 deletions magic_bridge/ic/src/dip721_proxy/src/api/burn.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use ic_kit::candid::candid_method;
use ic_kit::{ic, macros::update};

use crate::common::dip721::Dip721;
use crate::common::tera::Tera;
use crate::proxy::{ToNat, ERC721_ADDRESS_ETH, STATE, TERA_ADDRESS};
use ic_cdk::export::candid::{Nat, Principal};

use crate::common::types::{EthereumAddr, TokendId, TxError, TxReceipt};

// should we allow users to just pass in the corresponding eth_addr on ETH
// or should we use our magic_bridge to check if a key exists
#[update(name = "burn")]
#[candid_method(update, rename = "burn")]
async fn burn(token_id: TokendId, eth_addr: EthereumAddr, amount: Nat) -> TxReceipt {
let caller = ic::caller();
let self_id = ic::id();

if (token_id.name().await).is_err() {
return Err(TxError::Other(format!(
"Token {} canister is not responding!",
token_id.to_string(),
)));
}

let erc721_addr_hex = ERC721_ADDRESS_ETH.trim_start_matches("0x");
let erc721_addr_pid = Principal::from_slice(&hex::decode(erc721_addr_hex).unwrap());

let transfer_from = token_id
.transfer_from(caller, self_id, amount.clone())
.await;

match transfer_from {
Ok(_) => {
STATE.with(|s| s.add_balance(caller, token_id, amount.clone()));

let burn = token_id.burn(amount.clone()).await;

match burn {
Ok(burn_txn_id) => {
let tera_id = Principal::from_text(TERA_ADDRESS).unwrap();
let payload = [
token_id.clone().to_nat(),
eth_addr.clone().to_nat(),
amount.clone(),
]
.to_vec();

if tera_id.send_message(erc721_addr_pid, payload).await.is_err() {
return Err(TxError::Other(format!(
"Sending message to L1 failed with caller {:?}!",
caller.to_string()
)));
}

// there could be an underflow here
// like negative balance
let current_balance =
STATE.with(|s| s.get_balance(caller, token_id).unwrap_or(Nat::from(0)));

STATE.with(|s| {
s.update_balance(caller, token_id, current_balance - amount.clone())
});
return Ok(burn_txn_id);
}
Err(error) => {
return Err(error);
}
};
}
Err(error) => Err(error),
}
}
21 changes: 21 additions & 0 deletions magic_bridge/ic/src/dip721_proxy/src/api/get_balance.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use ic_kit::{
candid::{candid_method, Nat},
ic,
macros::update,
};

use crate::{common::types::TokendId, proxy::STATE};

#[update(name = "get_balance")]
#[candid_method(update, rename = "get_balance")]
pub async fn get_balance(token_id: TokendId) -> Option<Nat> {
let caller = ic::caller();
STATE.with(|s| s.get_balance(caller, token_id))
}

#[update(name = "get_all_token_balance")]
#[candid_method(update, rename = "get_all_token_balance")]
pub async fn get_all_balances() -> Result<Vec<(String, Nat)>, String> {
let caller = ic::caller();
STATE.with(|s| s.get_all_balances(caller))
}
43 changes: 43 additions & 0 deletions magic_bridge/ic/src/dip721_proxy/src/api/handle_message.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use crate::api::mint::mint;
use ic_kit::candid::candid_method;
use ic_kit::{ic, macros::update};

use ic_cdk::export::candid::{Nat, Principal};

use crate::common::types::{EthereumAddr, MagicResponse, Nonce, TokenType, TxError, TxReceipt};
use crate::proxy::{ERC721_ADDRESS_ETH, MAGIC_ADDRESS_IC};

#[update(name = "handle_message")]
#[candid_method(update, rename = "handle_message")]
async fn handler(eth_addr: EthereumAddr, nonce: Nonce, payload: Vec<Nat>) -> TxReceipt {
let erc721_addr_hex = hex::encode(eth_addr);

if !(erc721_addr_hex
== ERC721_ADDRESS_ETH
.trim_start_matches("0x")
.to_ascii_lowercase())
{
return Err(TxError::Other(format!(
"ERC721 Contract Address is inccorrect: {}",
erc721_addr_hex
)));
}

let magic_ic_addr_pid = Principal::from_text(MAGIC_ADDRESS_IC).unwrap();

let create_canister: (MagicResponse,) =
match ic::call(magic_ic_addr_pid, "create", (TokenType::DIP721, &payload)).await {
Ok(res) => res,
Err((code, err)) => {
return Err(TxError::Other(format!(
"RejectionCode: {:?}\n{}",
code, err
)))
}
};

match create_canister {
(Ok(token_id),) => mint(token_id, nonce, payload).await,
(Err(error),) => Err(TxError::Other(error.to_string())),
}
}
8 changes: 8 additions & 0 deletions magic_bridge/ic/src/dip721_proxy/src/api/init.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use ic_kit::{ic, macros::*};

use crate::proxy::STATE;

#[init]
pub fn init() {
STATE.with(|s| s.controllers.borrow_mut().push(ic::caller()));
}
87 changes: 87 additions & 0 deletions magic_bridge/ic/src/dip721_proxy/src/api/mint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
use ic_kit::candid::candid_method;
use ic_kit::{ic, macros::update};

use crate::common::dip721::Dip721;
use crate::common::tera::Tera;
use crate::common::utils::Keccak256HashFn;
use crate::proxy::{FromNat, ToNat, ERC721_ADDRESS_ETH, STATE, TERA_ADDRESS};
use ic_cdk::export::candid::{Nat, Principal};

use crate::common::types::{
IncomingMessageHashParams, Message, MessageStatus, Nonce, TokendId, TxError, TxReceipt,
};

#[update(name = "mint")]
#[candid_method(update, rename = "mint")]
pub async fn mint(token_id: TokendId, nonce: Nonce, payload: Vec<Nat>) -> TxReceipt {
if (token_id.name().await).is_err() {
return Err(TxError::Other(format!(
"Token {} canister is not responding!",
token_id.to_string()
)));
}

let self_id = ic::id();
let erc721_addr_hex = ERC721_ADDRESS_ETH.trim_start_matches("0x");
let erc721_addr_pid = Principal::from_slice(&hex::decode(erc721_addr_hex).unwrap());

let msg_hash = Message.calculate_hash(IncomingMessageHashParams {
from: erc721_addr_pid.to_nat(),
to: self_id.to_nat(),
nonce: nonce.clone(),
payload: payload.clone(),
});

let msg_exists = STATE.with(|s| s.get_message(&msg_hash));

if let Some(status) = msg_exists {
match status {
MessageStatus::ConsumedNotMinted => (),
_ => {
return Err(TxError::Other(format!(
"Meesage {}: is already being consumed/minted!",
&msg_hash
)));
}
}
} else {
let tera_id = Principal::from_text(TERA_ADDRESS).unwrap();
if tera_id
.consume_message(erc721_addr_pid, nonce, payload.clone())
.await
.is_err()
{
return Err(TxError::Other(format!(
"Consuming message from L1 failed with message {:?}!",
msg_hash,
)));
}
STATE.with(|s| s.store_incoming_message(msg_hash.clone()));
};

STATE.with(|s| s.update_incoming_message_status(msg_hash.clone(), MessageStatus::Consuming));

let amount = Nat::from(payload[2].0.clone());
let to = Principal::from_nat(payload[1].clone());

match token_id.mint(to, amount).await {
Ok(txn_id) => {
if STATE
.with(|s| s.remove_incoming_message(msg_hash.clone()))
.is_some()
{
return Ok(txn_id);
}
Err(TxError::Other(format!(
"Message {:?} does not exist!",
&msg_hash,
)))
}
Err(error) => {
STATE.with(|s| {
s.update_incoming_message_status(msg_hash.clone(), MessageStatus::ConsumedNotMinted)
});
Err(error)
}
}
}
7 changes: 7 additions & 0 deletions magic_bridge/ic/src/dip721_proxy/src/api/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
mod burn;
mod get_balance;
mod handle_message;
mod init;
mod mint;
mod upgrade;
mod withdraw;
22 changes: 22 additions & 0 deletions magic_bridge/ic/src/dip721_proxy/src/api/upgrade.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use ic_kit::ic;
use ic_kit::macros::{post_upgrade, pre_upgrade};

use crate::common::types::StableProxyState;
use crate::proxy::STATE;

#[pre_upgrade]
fn pre_upgrade() {
let stable_magic_state = STATE.with(|s| s.take_all());

ic::stable_store((stable_magic_state,)).expect("failed to messsage state");
}

#[post_upgrade]
fn post_upgrade() {
STATE.with(|s| s.clear_all());

let (stable_message_state,): (StableProxyState,) =
ic::stable_restore().expect("failed to restore stable messsage state");

STATE.with(|s| s.replace_all(stable_message_state));
}
52 changes: 52 additions & 0 deletions magic_bridge/ic/src/dip721_proxy/src/api/withdraw.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use ic_kit::{
candid::{candid_method, Nat},
ic,
macros::update,
Principal,
};

use crate::{
common::{
dip721::Dip721,
tera::Tera,
types::{EthereumAddr, TokendId, TxError, TxReceipt},
},
proxy::{ToNat, ERC721_ADDRESS_ETH, STATE, TERA_ADDRESS},
};

/// withdraw left over balance if burn/mint fails
/// this will attempt to bridge the leftover balance
/// todo withdraw specific balance
#[update(name = "withdraw")]
#[candid_method(update, rename = "withdraw")]
pub async fn withdraw(token_id: TokendId, eth_addr: EthereumAddr, _amount: Nat) -> TxReceipt {
let caller = ic::caller();

if (token_id.name().await).is_err() {
return Err(TxError::Other(format!(
"Token {} canister is not responding!",
token_id.to_string(),
)));
}

let erc721_addr_hex = ERC721_ADDRESS_ETH.trim_start_matches("0x");
let erc721_addr_pid = Principal::from_slice(&hex::decode(erc721_addr_hex).unwrap());

let get_balance = STATE.with(|s| s.get_balance(caller, token_id));
if let Some(balance) = get_balance {
let payload = [eth_addr.clone().to_nat(), balance.clone()].to_vec();
let tera_id = Principal::from_text(TERA_ADDRESS).unwrap();
if tera_id.send_message(erc721_addr_pid, payload).await.is_err() {
return Err(TxError::Other(format!("Sending message to L1 failed!")));
}

let zero = Nat::from(0_u32);
STATE.with(|s| s.update_balance(caller, token_id, zero));
}

Err(TxError::Other(format!(
"No balance for caller {:?} in canister {:?}!",
caller.to_string(),
token_id.to_string(),
)))
}
Loading