Skip to content

Commit

Permalink
feat: implementation of token transfer (ICS20) contract (#24)
Browse files Browse the repository at this point in the history
* feat: define interfaces + initial implementation for escrow_* methods

* imp: implmement register_token(), enhance tests, write scripts, add tests job

* feat: revise contract + implement all methods + successful escrowing test

* fix: lint group

* fix: lint group

* fix: make clippy happy

* impl bytes_to_felt252

* mv bytes_to_felt252 to utils

* add felt252_to_bytes

* add comment for null char

* fix typo

* more comments on leading null chars

* refactor

* imp: overhaul implementation + make erc20 mintable + happy transfer tests

* fix: taplo fmt

---------

Co-authored-by: Ranadeep Biswas <[email protected]>
Co-authored-by: Soares Chen <[email protected]>
  • Loading branch information
3 people authored Jul 30, 2024
1 parent 98c5e90 commit 656103f
Show file tree
Hide file tree
Showing 42 changed files with 1,821 additions and 158 deletions.
9 changes: 9 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
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>

ERC20_CLASS_HASH=""
ICS20_CLASS_HASH=""
CONTRACT_ADDRESS=""
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 @@ -3,5 +3,7 @@ corelib/
target/
**/Cargo.lock

.env

# vscode
.vscode/
3 changes: 3 additions & 0 deletions contracts/Scarb.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry", tag = "
[lib]

[[target.starknet-contract]]
allowed-libfuncs-list.name = "experimental"
sierra = true
casm = false

[tool.fmt]
sort-module-level-items = true
3 changes: 3 additions & 0 deletions contracts/src/apps.cairo
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
pub mod governance;
pub mod mintable;
pub mod transfer;
pub mod transferrable;
2 changes: 2 additions & 0 deletions contracts/src/apps/governance.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod component;
pub mod interface;
30 changes: 30 additions & 0 deletions contracts/src/apps/governance/component.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#[starknet::component]
pub mod IBCGovernanceComponent {
use starknet::ContractAddress;
use starknet::get_caller_address;
use starknet_ibc::apps::governance::interface::IGovernance;

#[storage]
struct Storage {
governor: ContractAddress,
}

#[event]
#[derive(Drop, Debug, starknet::Event)]
pub enum Event {}

#[embeddable_as(Governance)]
pub impl GovernanceImpl<
TContractState, +HasComponent<TContractState>, +Drop<TContractState>
> of IGovernance<ComponentState<TContractState>> {}

#[generate_trait]
pub impl GovernanceInternalImpl<
TContractState, +HasComponent<TContractState>, +Drop<TContractState>
> of GovernanceInternalTrait<TContractState> {
fn initializer(ref self: ComponentState<TContractState>) {
self.governor.write(get_caller_address());
}
}
}

3 changes: 3 additions & 0 deletions contracts/src/apps/governance/interface.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#[starknet::interface]
pub trait IGovernance<TContractState> {}

3 changes: 3 additions & 0 deletions contracts/src/apps/mintable.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod component;
pub mod errors;
pub mod interface;
86 changes: 86 additions & 0 deletions contracts/src/apps/mintable/component.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#[starknet::component]
pub mod ERC20MintableComponent {
use core::num::traits::Zero;
use openzeppelin::token::erc20::ERC20Component::InternalTrait;
use openzeppelin::token::erc20::ERC20Component;
use openzeppelin::token::erc20::erc20::ERC20Component::Transfer;
use starknet::ContractAddress;
use starknet::get_caller_address;
use starknet_ibc::apps::mintable::errors::MintableErrors;
use starknet_ibc::apps::mintable::interface::IERC20Mintable;

#[storage]
struct Storage {
permission: ContractAddress,
}

#[event]
#[derive(Drop, Debug, starknet::Event)]
pub enum Event {}

#[embeddable_as(ERC20Mintable)]
pub impl ERC20MintableImpl<
TContractState,
+HasComponent<TContractState>,
+Drop<TContractState>,
impl ERC20: ERC20Component::HasComponent<TContractState>,
> of IERC20Mintable<ComponentState<TContractState>> {
fn permissioned_mint(
ref self: ComponentState<TContractState>, recipient: ContractAddress, amount: u256
) {
let permitted_minter = self.permission.read();
assert(permitted_minter == get_caller_address(), MintableErrors::UNAUTHORIZED_MINTER);

self.mint(recipient, amount);
}

fn permissioned_burn(
ref self: ComponentState<TContractState>, account: ContractAddress, amount: u256
) {
let permitted_burner = self.permission.read();
assert(permitted_burner == get_caller_address(), MintableErrors::UNAUTHORIZED_BURNER);
self.burn(account, amount);
}
}

#[generate_trait]
pub impl ERC20MintableInternalImpl<
TContractState,
+HasComponent<TContractState>,
+Drop<TContractState>,
impl ERC20: ERC20Component::HasComponent<TContractState>
> of ERC20MintableInternalTrait<TContractState> {
fn initializer(ref self: ComponentState<TContractState>) {
self.permission.write(get_caller_address());
}

fn mint(
ref self: ComponentState<TContractState>, recipient: ContractAddress, amount: u256
) {
let mut erc20_comp = get_dep_component_mut!(ref self, ERC20);
assert(recipient.is_non_zero(), MintableErrors::MINT_TO_ZERO);

erc20_comp.ERC20_total_supply.write(erc20_comp.ERC20_total_supply.read() + amount);
erc20_comp
.ERC20_balances
.write(recipient, erc20_comp.ERC20_balances.read(recipient) + amount);

erc20_comp.emit(Transfer { from: Zero::zero(), to: recipient, value: amount });
}

fn burn(ref self: ComponentState<TContractState>, account: ContractAddress, amount: u256) {
let mut erc20_comp = get_dep_component_mut!(ref self, ERC20);
assert(account.is_non_zero(), MintableErrors::BURN_FROM_ZERO);

let total_supply = erc20_comp.ERC20_total_supply.read();
assert(total_supply >= amount, MintableErrors::INSUFFICIENT_SUPPLY);
erc20_comp.ERC20_total_supply.write(total_supply - amount);

let balance = erc20_comp.ERC20_balances.read(account);
assert(balance >= amount, MintableErrors::INSUFFICIENT_BALANCE);
erc20_comp.ERC20_balances.write(account, balance - amount);

erc20_comp.emit(Transfer { from: account, to: Zero::zero(), value: amount });
}
}
}
9 changes: 9 additions & 0 deletions contracts/src/apps/mintable/errors.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
pub mod MintableErrors {
pub const UNAUTHORIZED_MINTER: felt252 = 'Unauthorized minter';
pub const UNAUTHORIZED_BURNER: felt252 = 'Unauthorized burner';
pub const BURN_FROM_ZERO: felt252 = 'ERC20: burn from 0';
pub const MINT_TO_ZERO: felt252 = 'ERC20: mint to 0';
pub const INSUFFICIENT_BALANCE: felt252 = 'ERC20: insufficient balance';
pub const INSUFFICIENT_SUPPLY: felt252 = 'ERC20: insufficient supply';
}

7 changes: 7 additions & 0 deletions contracts/src/apps/mintable/interface.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use starknet::ContractAddress;

#[starknet::interface]
pub trait IERC20Mintable<TContractState> {
fn permissioned_mint(ref self: TContractState, recipient: ContractAddress, amount: u256);
fn permissioned_burn(ref self: TContractState, account: ContractAddress, amount: u256);
}
1 change: 1 addition & 0 deletions contracts/src/apps/transfer.cairo
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod component;
pub mod errors;
pub mod interface;
pub mod types;
Loading

0 comments on commit 656103f

Please sign in to comment.