Skip to content

Commit

Permalink
imp: implmement register_token(), enhance tests, write scripts, add t…
Browse files Browse the repository at this point in the history
…ests job
  • Loading branch information
Farhad-Shabani committed Jul 24, 2024
1 parent e55efa4 commit bfc3646
Show file tree
Hide file tree
Showing 15 changed files with 351 additions and 57 deletions.
8 changes: 8 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
CONTRACT_SRC=${CONTRACT_SRC:-$(pwd)/contracts/target/dev/starknet_ibc_Transfer.contract_class.json}
RPC_URL=https://starknet-sepolia.public.blastapi.io/rpc/v0_7
ACCOUNT_SRC="${HOME}/.starkli-wallets/deployer/account.json"
KEYSTORE_SRC="${HOME}/.starkli-wallets/deployer/keystore.json"
KEYSTORE_PASS=<KEYSTORE_PASSWORD>

CONTRACT_ADDRESS=""
CLASS_HASH=""
30 changes: 30 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Tests
on:
pull_request:
paths:
- .github/workflows/tests.yaml
- contracts/**
- justfile

push:
tags:
- v[0-9]+.*
branches:
- "release/*"
- main

jobs:
test-contracts:
name: Test Cairo Contracts
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- name: Install Scarb
uses: software-mansion/setup-scarb@v1
with:
scarb-version: "2.6.5"
- name: Install Just
uses: extractions/setup-just@v1
- name: Run Tests
run: just test-contracts
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@
corelib/
target/

.env

# vscode
.vscode/
111 changes: 99 additions & 12 deletions contracts/src/apps/transfer/component.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub mod ICS20TransferComponent {
use starknet::syscalls::deploy_syscall;
use starknet_ibc::apps::transfer::errors::ICS20Errors;
use starknet_ibc::apps::transfer::interface::{
ITransfer, ITransferValidationContext, ITransferExecutionContext
ITransfer, ITransferrable, ITransferValidationContext, ITransferExecutionContext
};
use starknet_ibc::apps::transfer::types::{MsgTransfer, PrefixedCoin, Memo, MAXIMUM_MEMO_LENGTH};
use starknet_ibc::core::types::{PortId, ChannelId};
Expand All @@ -25,6 +25,8 @@ pub mod ICS20TransferComponent {
struct Storage {
salt: felt252,
governor: ContractAddress,
send_capability: bool,
receive_capability: bool,
registered_tokens: LegacyMap::<felt252, ContractAddress>,
minted_tokens: LegacyMap::<felt252, ContractAddress>,
}
Expand All @@ -46,28 +48,99 @@ pub mod ICS20TransferComponent {

#[embeddable_as(Transfer)]
impl TransferImpl<
TContractState, +HasComponent<TContractState>, +Drop<TContractState>
TContractState,
+HasComponent<TContractState>,
+ERC20ABI<TContractState>,
+Drop<TContractState>
> of ITransfer<ComponentState<TContractState>> {
fn send_transfer(ref self: ComponentState<TContractState>, msg: MsgTransfer) {}
fn send_transfer(ref self: ComponentState<TContractState>, msg: MsgTransfer) {
self.can_send();

let is_sender_chain_source = self.is_sender_chain_source(msg.packet_data.token.denom);

let is_receiver_chain_source = self
.is_receiver_chain_source(msg.packet_data.token.denom);

assert(
!is_sender_chain_source && !is_receiver_chain_source,
ICS20Errors::INVALID_TOKEN_NAME
);

if is_sender_chain_source {
self
.escrow_validate(
msg.packet_data.sender.clone(),
msg.port_id_on_a.clone(),
msg.chan_id_on_a.clone(),
msg.packet_data.token.clone(),
msg.packet_data.memo.clone(),
);

self
.escrow_execute(
msg.packet_data.sender.clone(),
msg.port_id_on_a.clone(),
msg.chan_id_on_a.clone(),
msg.packet_data.token.clone(),
msg.packet_data.memo.clone(),
);
}

if is_receiver_chain_source {
self
.burn_validate(
msg.packet_data.sender.clone(),
msg.packet_data.token.clone(),
msg.packet_data.memo.clone(),
);

self
.burn_execute(
msg.packet_data.sender.clone(),
msg.packet_data.token.clone(),
msg.packet_data.memo.clone(),
);
}
}

fn register_token(
ref self: ComponentState<TContractState>,
token_name: felt252,
token_address: ContractAddress
) {
let governor = self.governor.read();
let maybe_governor = get_caller_address();
assert(maybe_governor == governor, ICS20Errors::UNAUTHORIZED_REGISTAR);

assert(governor == get_caller_address(), ICS20Errors::UNAUTHORIZED_REGISTAR);

assert(token_name.is_non_zero(), ICS20Errors::ZERO_TOKEN_NAME);

let registered_token_address: ContractAddress = self.registered_tokens.read(token_name);
assert(registered_token_address.is_non_zero(), ICS20Errors::ALREADY_LISTED_TOKEN);
assert(registered_token_address == token_address, ICS20Errors::ALREADY_LISTED_TOKEN);

assert(registered_token_address.is_zero(), ICS20Errors::ALREADY_LISTED_TOKEN);

assert(token_address.is_non_zero(), ICS20Errors::ZERO_TOKEN_ADDRESS);

self.registered_tokens.write(token_name, token_address);
}
}

#[embeddable_as(TransferrableImpl)]
pub impl Transferrable<
TContractState, +HasComponent<TContractState>, +Drop<TContractState>
> of ITransferrable<ComponentState<TContractState>> {
fn can_send(self: @ComponentState<TContractState>) {
let send_capability = self.send_capability.read();
assert(send_capability, ICS20Errors::NO_SEND_CAPABILITY);
}
fn can_receive(self: @ComponentState<TContractState>) {
let receive_capability = self.receive_capability.read();
assert(receive_capability, ICS20Errors::NO_RECEIVE_CAPABILITY);
}
}


#[embeddable_as(TransferValidationImpl)]
impl TransferValidationContext<
pub impl TransferValidationContext<
TContractState,
+HasComponent<TContractState>,
+ERC20ABI<TContractState>,
Expand Down Expand Up @@ -119,10 +192,10 @@ pub mod ICS20TransferComponent {
fn escrow_execute(
ref self: ComponentState<TContractState>,
from_address: ContractAddress,
port_id: felt252,
channel_id: felt252,
port_id: PortId,
channel_id: ChannelId,
coin: PrefixedCoin,
memo: ByteArray,
memo: Memo,
) {
let to_address = get_contract_address();
let mut contract = self.get_contract_mut();
Expand Down Expand Up @@ -150,12 +223,26 @@ pub mod ICS20TransferComponent {
}

#[generate_trait]
pub impl TransferInternalImpl<
pub(crate) impl TransferInternalImpl<
TContractState,
+HasComponent<TContractState>,
+ERC20ABI<TContractState>,
+Drop<TContractState>,
> of TransferInternalTrait<TContractState> {
fn initializer(ref self: ComponentState<TContractState>) {
self.governor.write(get_caller_address());
self.send_capability.write(true);
self.receive_capability.write(true);
}

fn is_sender_chain_source(self: @ComponentState<TContractState>, denom: felt252) -> bool {
self.registered_tokens.read(denom).is_zero()
}

fn is_receiver_chain_source(self: @ComponentState<TContractState>, denom: felt252) -> bool {
self.minted_tokens.read(denom).is_zero()
}

fn create_token(ref self: ComponentState<TContractState>) -> ContractAddress {
// unimplemented! > Dummy value to pass the type check
0.try_into().unwrap()
Expand Down
6 changes: 5 additions & 1 deletion contracts/src/apps/transfer/errors.cairo
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
pub mod ICS20Errors {
pub const ALREADY_LISTED_TOKEN: felt252 = 'ICS20: token is already listed';
pub const NO_SEND_CAPABILITY: felt252 = 'ICS20: No send capability';
pub const NO_RECEIVE_CAPABILITY: felt252 = 'ICS20: No receive capability';
pub const ZERO_TOKEN_NAME: felt252 = 'ICS20: token name is 0';
pub const ZERO_TOKEN_ADDRESS: felt252 = 'ICS20: token address is 0';
pub const ALREADY_LISTED_TOKEN: felt252 = 'ICS20: token is already listed';
pub const INVALID_TOKEN_NAME: felt252 = 'ICS20: token name is invalid';
pub const UNAUTHORIZED_REGISTAR: felt252 = 'ICS20: unauthorized registrar';
pub const MAXIMUM_MEMO_LENGTH: felt252 = 'ICS20: memo exceeds max length';
pub const INSUFFICIENT_BALANCE: felt252 = 'ICS20: insufficient balance';
Expand Down
12 changes: 9 additions & 3 deletions contracts/src/apps/transfer/interface.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ pub trait ITransfer<TContractState> {
);
}

#[starknet::interface]
pub trait ITransferrable<TContractState> {
fn can_send(self: @TContractState);
fn can_receive(self: @TContractState);
}

#[starknet::interface]
pub trait ITransferValidationContext<TContractState> {
fn escrow_validate(
Expand Down Expand Up @@ -38,10 +44,10 @@ pub trait ITransferExecutionContext<TContractState> {
fn escrow_execute(
ref self: TContractState,
from_address: ContractAddress,
port_id: felt252,
channel_id: felt252,
port_id: PortId,
channel_id: ChannelId,
coin: PrefixedCoin,
memo: ByteArray,
memo: Memo,
);
fn unescrow_execute(
ref self: TContractState,
Expand Down
8 changes: 4 additions & 4 deletions contracts/src/apps/transfer/types.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,28 @@ use starknet_ibc::core::types::{PortId, ChannelId};
/// the `MaximumMemoLength` in the `ibc-go`.
pub(crate) const MAXIMUM_MEMO_LENGTH: u32 = 32768;

#[derive(Drop, Serde, Store)]
#[derive(Clone, Debug, Drop, Serde, Store)]
pub struct MsgTransfer {
pub port_id_on_a: PortId,
pub chan_id_on_a: ChannelId,
pub packet_data: PacketData,
}

#[derive(Drop, Serde, Store)]
#[derive(Clone, Debug, Drop, Serde, Store)]
pub struct PacketData {
pub token: PrefixedCoin,
pub sender: ContractAddress,
pub receiver: ContractAddress,
pub memo: Memo,
}

#[derive(Drop, Serde, Store)]
#[derive(Clone, Debug, Drop, Serde, Store)]
pub struct PrefixedCoin {
pub denom: felt252,
pub amount: u256,
}

#[derive(Drop, Serde, Store)]
#[derive(Clone, Debug, Drop, Serde, Store)]
pub struct Memo {
pub memo: ByteArray,
}
Expand Down
38 changes: 3 additions & 35 deletions contracts/src/contract.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub(crate) mod Transfer {

#[abi(embed_v0)]
impl ICS20TransferImpl = ICS20TransferComponent::Transfer<ContractState>;
impl TransferreableImpl = ICS20TransferComponent::Transferrable<ContractState>;
impl TransferValidationImpl = ICS20TransferComponent::TransferValidationImpl<ContractState>;
impl TransferExecutionImpl = ICS20TransferComponent::TransferExecutionImpl<ContractState>;
impl TransferInternalImpl = ICS20TransferComponent::TransferInternalImpl<ContractState>;
Expand All @@ -35,40 +36,7 @@ pub(crate) mod Transfer {
}

#[constructor]
fn constructor(
ref self: ContractState,
name: ByteArray,
symbol: ByteArray,
fixed_supply: u256,
recipient: ContractAddress,
owner: ContractAddress
) {
self.erc20.initializer(name, symbol);
}
}

#[cfg(test)]
mod tests {
use core::starknet::SyscallResultTrait;
use starknet::ContractAddress;
use starknet::contract_address_const;
use starknet::syscalls::deploy_syscall;
use starknet_ibc::apps::transfer::interface::{ITransferDispatcher, ITransferDispatcherTrait,};
use super::Transfer;

fn deploy() -> (ITransferDispatcher, ContractAddress) {
let recipient: ContractAddress = contract_address_const::<'sender'>();

let (contract_address, _) = deploy_syscall(
Transfer::TEST_CLASS_HASH.try_into().unwrap(), recipient.into(), array![0].span(), false
)
.unwrap_syscall();

(ITransferDispatcher { contract_address }, contract_address)
}

#[test]
fn test_transfer() {
deploy();
fn constructor(ref self: ContractState,) {
self.transfer.initializer();
}
}
4 changes: 2 additions & 2 deletions contracts/src/core/types.cairo
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use starknet::ContractAddress;
use starknet::Store;

#[derive(Drop, Serde, Store)]
#[derive(Clone, Debug, Drop, Serde, Store)]
pub struct ChannelId {
channel_id: felt252,
}

#[derive(Drop, Serde, Store)]
#[derive(Clone, Debug, Drop, Serde, Store)]
pub struct PortId {
port_id: felt252,
}
1 change: 1 addition & 0 deletions contracts/src/lib.cairo
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod apps;
pub mod contract;
pub mod core;
pub mod tests;
4 changes: 4 additions & 0 deletions contracts/src/tests.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#[cfg(test)]
mod transfer;

mod utils;
Loading

0 comments on commit bfc3646

Please sign in to comment.