diff --git a/cairo-contracts/packages/apps/src/tests/transfer.cairo b/cairo-contracts/packages/apps/src/tests/transfer.cairo index eafc92ca6..de6cf8fe2 100644 --- a/cairo-contracts/packages/apps/src/tests/transfer.cairo +++ b/cairo-contracts/packages/apps/src/tests/transfer.cairo @@ -149,11 +149,13 @@ fn test_mint_ok() { let token_address = ics20.ibc_token_address(prefixed_denom.key()); + let erc20: ERC20Contract = token_address.into(); + // Assert the `CreateTokenEvent` emitted. - spy - .assert_create_token_event( - ics20.address, NAME(), SYMBOL(), DECIMAL_ZERO, token_address, cfg.amount, - ); + spy.assert_create_token_event(ics20.address, NAME(), SYMBOL(), DECIMAL_ZERO, token_address); + + // Assert if ICS20 performs the mint. + spy.assert_transfer_event(erc20.address, ics20.address, SN_USER(), cfg.amount); // Assert the `RecvEvent` emitted. spy @@ -161,8 +163,6 @@ fn test_mint_ok() { ics20.address, CS_USER(), SN_USER(), prefixed_denom.clone(), cfg.amount, true, ); - let erc20: ERC20Contract = token_address.into(); - // Assert if the transfer happens from the ICS20 address. spy.assert_transfer_event(erc20.address, ics20.address, SN_USER(), cfg.amount); diff --git a/cairo-contracts/packages/apps/src/transfer/components/transfer.cairo b/cairo-contracts/packages/apps/src/transfer/components/transfer.cairo index f34cfdb1a..e7d2b330f 100644 --- a/cairo-contracts/packages/apps/src/transfer/components/transfer.cairo +++ b/cairo-contracts/packages/apps/src/transfer/components/transfer.cairo @@ -113,7 +113,6 @@ pub mod TokenTransferComponent { pub decimals: u8, #[key] pub address: ContractAddress, - pub initial_supply: u256, } // ----------------------------------------------------------- @@ -714,15 +713,14 @@ pub mod TokenTransferComponent { amount: u256, ) { let mut token = self.get_token(denom.key()); - if token.is_non_zero() { - token.mint(get_contract_address(), amount); - } else { + if token.is_zero() { let name = denom.base.hosted().unwrap(); token = self.create_token(name, amount); self.record_ibc_token(denom, token.address); } + token.mint(amount); token.transfer(account, amount); } @@ -737,7 +735,7 @@ pub mod TokenTransferComponent { token.transfer_from(account, get_contract_address(), amount); - token.burn(get_contract_address(), amount); + token.burn(amount); } fn refund_execute( @@ -813,14 +811,12 @@ pub mod TokenTransferComponent { name.clone(), symbol.clone(), decimals, - amount.clone(), - get_contract_address(), get_contract_address(), ); self.write_salt(salt + 1); - self.emit_create_token_event(name, symbol, decimals, erc20_token.address, amount); + self.emit_create_token_event(name, symbol, decimals, erc20_token.address); erc20_token } @@ -1015,9 +1011,8 @@ pub mod TokenTransferComponent { symbol: ByteArray, decimals: u8, address: ContractAddress, - initial_supply: u256, ) { - let event = CreateTokenEvent { name, symbol, decimals, address, initial_supply }; + let event = CreateTokenEvent { name, symbol, decimals, address }; self.emit(event); } } diff --git a/cairo-contracts/packages/apps/src/transfer/erc20_call.cairo b/cairo-contracts/packages/apps/src/transfer/erc20_call.cairo index 0e42ecb16..b8a8e49d9 100644 --- a/cairo-contracts/packages/apps/src/transfer/erc20_call.cairo +++ b/cairo-contracts/packages/apps/src/transfer/erc20_call.cairo @@ -1,9 +1,8 @@ use core::num::traits::Zero; use core::starknet::SyscallResultTrait; use openzeppelin_token::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; -use starknet::syscalls::deploy_syscall; +use starknet::syscalls::{call_contract_syscall, deploy_syscall}; use starknet::{ClassHash, ContractAddress, contract_address_const}; -use starknet_ibc_utils::mintable::{IERC20MintableDispatcher, IERC20MintableDispatcherTrait}; #[derive(Copy, Debug, Drop, Serde)] pub struct ERC20Contract { @@ -28,23 +27,17 @@ pub impl ERC20ContractImpl of ERC20ContractTrait { ERC20ABIDispatcher { contract_address: *self.address } } - fn mintable_dispatcher(self: @ERC20Contract) -> IERC20MintableDispatcher { - IERC20MintableDispatcher { contract_address: *self.address } - } - fn create( class_hash: ClassHash, salt: felt252, name: ByteArray, symbol: ByteArray, decimals: u8, - amount: u256, - recipient: ContractAddress, owner: ContractAddress, ) -> ERC20Contract { let mut call_data = array![]; - (name, symbol, decimals, amount, recipient, owner).serialize(ref call_data); + (name, symbol, decimals, owner).serialize(ref call_data); let (address, _) = deploy_syscall(class_hash, salt, call_data.span(), false) .unwrap_syscall(); @@ -62,12 +55,16 @@ pub impl ERC20ContractImpl of ERC20ContractTrait { self.dispatcher().transfer_from(sender, recipient, amount) } - fn mint(self: @ERC20Contract, recipient: ContractAddress, amount: u256) { - self.mintable_dispatcher().permissioned_mint(recipient, amount) + fn mint(self: @ERC20Contract, amount: u256) { + let mut calldata = array![]; + amount.serialize(ref calldata); + call_contract_syscall(*self.address, selector!("mint"), calldata.span()).unwrap_syscall(); } - fn burn(self: @ERC20Contract, account: ContractAddress, amount: u256) { - self.mintable_dispatcher().permissioned_burn(account, amount) + fn burn(self: @ERC20Contract, amount: u256) { + let mut calldata = array![]; + amount.serialize(ref calldata); + call_contract_syscall(*self.address, selector!("burn"), calldata.span()).unwrap_syscall(); } fn balance_of(self: @ERC20Contract, from_account: ContractAddress) -> u256 { diff --git a/cairo-contracts/packages/contracts/src/erc20.cairo b/cairo-contracts/packages/contracts/src/erc20.cairo index e69955683..d4ef26687 100644 --- a/cairo-contracts/packages/contracts/src/erc20.cairo +++ b/cairo-contracts/packages/contracts/src/erc20.cairo @@ -1,14 +1,12 @@ #[starknet::contract] pub mod ERC20Mintable { + use core::num::traits::Zero; use openzeppelin_access::ownable::OwnableComponent; use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl, interface::IERC20Metadata}; - use starknet::ContractAddress; use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; - use starknet_ibc_utils::mintable::ERC20MintableComponent; - use starknet_ibc_utils::mintable::ERC20MintableComponent::ERC20MintableInternalTrait; + use starknet::{ContractAddress, get_caller_address}; component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); - component!(path: ERC20MintableComponent, storage: mintable, event: MintableEvent); component!(path: ERC20Component, storage: erc20, event: ERC20Event); // Ownable Mixin @@ -16,10 +14,6 @@ pub mod ERC20Mintable { impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl; impl OwnableInternalImpl = OwnableComponent::InternalImpl; - // ERC20 Mintable - #[abi(embed_v0)] - impl ERC20MintableImpl = ERC20MintableComponent::ERC20Mintable; - #[abi(embed_v0)] impl ERC20Impl = ERC20Component::ERC20Impl; #[abi(embed_v0)] @@ -31,8 +25,6 @@ pub mod ERC20Mintable { #[substorage(v0)] ownable: OwnableComponent::Storage, #[substorage(v0)] - mintable: ERC20MintableComponent::Storage, - #[substorage(v0)] erc20: ERC20Component::Storage, // The decimals value is stored locally in the contract. // ref: https://docs.openzeppelin.com/contracts-cairo/0.20.0/erc20#the_storage_approach @@ -45,8 +37,6 @@ pub mod ERC20Mintable { #[flat] OwnableEvent: OwnableComponent::Event, #[flat] - MintableEvent: ERC20MintableComponent::Event, - #[flat] ERC20Event: ERC20Component::Event, } @@ -56,14 +46,10 @@ pub mod ERC20Mintable { name: ByteArray, symbol: ByteArray, decimals: u8, - initial_supply: u256, - recipient: ContractAddress, owner: ContractAddress, ) { self.ownable.initializer(owner); - self.mintable.initializer(); self.erc20.initializer(name, symbol); - self.erc20.mint(recipient, initial_supply); self._set_decimals(decimals); } @@ -84,6 +70,24 @@ pub mod ERC20Mintable { } } + #[generate_trait] + #[abi(per_item)] + impl DynamicSupplyImpl of DynamicSupplyTrait { + #[external(v0)] + fn burn(ref self: ContractState, amount: u256) { + self.ownable.assert_only_owner(); + assert(amount.is_non_zero(), 'ERC20: burning amount is zero'); + self.erc20.burn(get_caller_address(), amount); + } + + #[external(v0)] + fn mint(ref self: ContractState, amount: u256) { + self.ownable.assert_only_owner(); + assert(amount.is_non_zero(), 'ERC20: minting amount is zero'); + self.erc20.mint(get_caller_address(), amount); + } + } + #[generate_trait] impl InternalImpl of InternalTrait { fn _set_decimals(ref self: ContractState, decimals: u8) { diff --git a/cairo-contracts/packages/contracts/src/lib.cairo b/cairo-contracts/packages/contracts/src/lib.cairo index 4347de730..9d8a6b27e 100644 --- a/cairo-contracts/packages/contracts/src/lib.cairo +++ b/cairo-contracts/packages/contracts/src/lib.cairo @@ -13,5 +13,6 @@ mod tests { pub(crate) mod channel; pub(crate) mod client; pub(crate) mod connection; + pub(crate) mod erc20; pub(crate) mod transfer; } diff --git a/cairo-contracts/packages/contracts/src/tests/erc20.cairo b/cairo-contracts/packages/contracts/src/tests/erc20.cairo new file mode 100644 index 000000000..027532b34 --- /dev/null +++ b/cairo-contracts/packages/contracts/src/tests/erc20.cairo @@ -0,0 +1,83 @@ +use snforge_std::{EventSpy, spy_events, start_cheat_caller_address}; +use starknet_ibc_apps::transfer::{ERC20Contract, ERC20ContractTrait}; +use starknet_ibc_testkit::dummies::{AMOUNT, OWNER, SN_USER, ZERO}; +use starknet_ibc_testkit::event_spy::{ERC20EventSpyExt, ERC20EventSpyExtImpl}; +use starknet_ibc_testkit::handles::ERC20Handle; +use starknet_ibc_testkit::setup::SetupImpl; + +fn setup() -> (ERC20Contract, EventSpy) { + let setup = SetupImpl::default(); + let erc20 = SetupImpl::deploy_erc20(@setup, OWNER()); + let spy = spy_events(); + (erc20, spy) +} + +#[test] +fn test_deploy_erc20_ok() { + setup(); +} + +#[test] +fn test_erc20_mint_ok() { + let (mut erc20, mut spy) = setup(); + start_cheat_caller_address(erc20.address, OWNER()); + erc20.mint(AMOUNT); + spy.assert_transfer_event(erc20.address, ZERO(), OWNER(), AMOUNT); + erc20.assert_balance(OWNER(), AMOUNT); + erc20.assert_total_supply(AMOUNT); +} + +#[test] +fn test_erc20_burn_ok() { + let (mut erc20, mut spy) = setup(); + start_cheat_caller_address(erc20.address, OWNER()); + erc20.mint(AMOUNT); + erc20.burn(AMOUNT); + spy.assert_transfer_event(erc20.address, OWNER(), ZERO(), AMOUNT); + erc20.assert_balance(OWNER(), 0); + erc20.assert_total_supply(0); +} + +#[test] +#[should_panic(expected: 'Caller is not the owner')] +fn test_erc20_unauthorized_mint() { + let (mut erc20, _) = setup(); + erc20.mint(AMOUNT); +} + +#[test] +#[should_panic(expected: 'Caller is not the owner')] +fn test_erc20_unauthorized_burn() { + let (mut erc20, _) = setup(); + start_cheat_caller_address(erc20.address, OWNER()); + erc20.mint(AMOUNT); + start_cheat_caller_address(erc20.address, SN_USER()); + erc20.burn(AMOUNT); +} + +#[test] +#[should_panic(expected: 'ERC20: insufficient allowance')] +fn test_erc20_transfer_without_user_approval() { + let (mut erc20, _) = setup(); + start_cheat_caller_address(erc20.address, OWNER()); + erc20.mint(AMOUNT); + erc20.transfer_from(OWNER(), SN_USER(), AMOUNT); + erc20.transfer_from(SN_USER(), OWNER(), AMOUNT); +} + +#[test] +#[should_panic(expected: 'ERC20: minting amount is zero')] +fn test_erc20_mint_zero_amount() { + let (mut erc20, _) = setup(); + start_cheat_caller_address(erc20.address, OWNER()); + erc20.mint(0); +} + +#[test] +#[should_panic(expected: 'ERC20: burning amount is zero')] +fn test_erc20_burn_zero_amount() { + let (mut erc20, _) = setup(); + start_cheat_caller_address(erc20.address, OWNER()); + erc20.mint(AMOUNT); + erc20.burn(0); +} diff --git a/cairo-contracts/packages/contracts/src/tests/transfer.cairo b/cairo-contracts/packages/contracts/src/tests/transfer.cairo index 1b285af91..f3b3d8796 100644 --- a/cairo-contracts/packages/contracts/src/tests/transfer.cairo +++ b/cairo-contracts/packages/contracts/src/tests/transfer.cairo @@ -97,11 +97,13 @@ fn test_mint_burn_roundtrip() { // Fetch the token address. let token_address = ics20.ibc_token_address(prefixed_denom.key()); + let mut erc20: ERC20Contract = token_address.into(); + // Assert the `CreateTokenEvent` emitted. - spy - .assert_create_token_event( - ics20.address, NAME(), SYMBOL(), DECIMAL_ZERO, token_address, transfer_cfg.amount, - ); + spy.assert_create_token_event(ics20.address, NAME(), SYMBOL(), DECIMAL_ZERO, token_address); + + // Assert if ICS20 performs the mint. + spy.assert_transfer_event(erc20.address, ics20.address, SN_USER(), transfer_cfg.amount); // Assert the `RecvEvent` emitted. spy @@ -109,8 +111,6 @@ fn test_mint_burn_roundtrip() { ics20.address, CS_USER(), SN_USER(), prefixed_denom.clone(), transfer_cfg.amount, true, ); - let mut erc20: ERC20Contract = token_address.into(); - // Assert if the transfer happens from the ICS20 address. spy.assert_transfer_event(erc20.address, ics20.address, SN_USER(), transfer_cfg.amount); @@ -191,7 +191,7 @@ fn test_create_ibc_token_ok() { let (_, ics20, _, _, _, transfer_cfg, mut spy) = setup(Mode::WithChannel); let prefixed_denom = transfer_cfg.prefix_hosted_denom(); let address = ics20.create_ibc_token(prefixed_denom.clone()); - spy.assert_create_token_event(ics20.address, NAME(), SYMBOL(), DECIMAL_ZERO, address, 0); + spy.assert_create_token_event(ics20.address, NAME(), SYMBOL(), DECIMAL_ZERO, address); let queried = ics20.ibc_token_address(prefixed_denom.key()); assert_eq!(address, queried); } @@ -208,7 +208,7 @@ fn test_create_ibc_token_with_multihop() { let prefixed_denom = transfer_cfg.prefix_hosted_denom(); let address = ics20.create_ibc_token(prefixed_denom.clone()); - spy.assert_create_token_event(ics20.address, NAME(), SYMBOL(), DECIMAL_ZERO, address, 0); + spy.assert_create_token_event(ics20.address, NAME(), SYMBOL(), DECIMAL_ZERO, address); let queried = ics20.ibc_token_address(prefixed_denom.key()); assert_eq!(address, queried); } diff --git a/cairo-contracts/packages/testkit/src/dummies/transfer.cairo b/cairo-contracts/packages/testkit/src/dummies/transfer.cairo index aaf5b9400..928835c67 100644 --- a/cairo-contracts/packages/testkit/src/dummies/transfer.cairo +++ b/cairo-contracts/packages/testkit/src/dummies/transfer.cairo @@ -24,6 +24,10 @@ pub fn CLASS_HASH() -> ClassHash { class_hash_const::<'ERC20Mintable'>() } +pub fn ZERO() -> ContractAddress { + contract_address_const::<0>() +} + pub fn ERC20() -> ERC20Contract { contract_address_const::<0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7>() .into() diff --git a/cairo-contracts/packages/testkit/src/event_spy/transfer.cairo b/cairo-contracts/packages/testkit/src/event_spy/transfer.cairo index b96ed7bad..3d1f75671 100644 --- a/cairo-contracts/packages/testkit/src/event_spy/transfer.cairo +++ b/cairo-contracts/packages/testkit/src/event_spy/transfer.cairo @@ -68,10 +68,9 @@ pub impl TransferEventSpyExtImpl of TransferEventSpyExt { symbol: ByteArray, decimals: u8, address: ContractAddress, - initial_supply: u256, ) { let expected = Event::CreateTokenEvent( - CreateTokenEvent { name, symbol, decimals, address, initial_supply }, + CreateTokenEvent { name, symbol, decimals, address }, ); self.assert_emitted_single(contract_address, expected); } diff --git a/cairo-contracts/packages/testkit/src/handles/erc20.cairo b/cairo-contracts/packages/testkit/src/handles/erc20.cairo index 6a94efe57..8f8993b5f 100644 --- a/cairo-contracts/packages/testkit/src/handles/erc20.cairo +++ b/cairo-contracts/packages/testkit/src/handles/erc20.cairo @@ -3,12 +3,12 @@ use openzeppelin_token::erc20::ERC20ABIDispatcherTrait; use snforge_std::{ContractClass, start_cheat_caller_address}; use starknet::ContractAddress; use starknet_ibc_apps::transfer::{ERC20Contract, ERC20ContractTrait}; -use starknet_ibc_testkit::dummies::{DECIMALS_18, NAME, OWNER, SN_USER, SUPPLY, SYMBOL}; +use starknet_ibc_testkit::dummies::{DECIMALS_18, NAME, SYMBOL}; #[generate_trait] pub impl ERC20HandleImpl of ERC20Handle { - fn deploy(contract_class: ContractClass) -> ERC20Contract { - deploy(contract_class, dummy_erc20_call_data()).into() + fn deploy(contract_class: ContractClass, owner: ContractAddress) -> ERC20Contract { + deploy(contract_class, dummy_erc20_calldata(owner)).into() } fn approve( @@ -30,13 +30,8 @@ pub impl ERC20HandleImpl of ERC20Handle { } } -pub(crate) fn dummy_erc20_call_data() -> Array { - let mut call_data: Array = array![]; - Serde::serialize(@NAME(), ref call_data); - Serde::serialize(@SYMBOL(), ref call_data); - Serde::serialize(@DECIMALS_18, ref call_data); - Serde::serialize(@SUPPLY, ref call_data); - Serde::serialize(@SN_USER(), ref call_data); - Serde::serialize(@OWNER(), ref call_data); - call_data +pub(crate) fn dummy_erc20_calldata(owner: ContractAddress) -> Array { + let mut calldata = array![]; + (NAME(), SYMBOL(), DECIMALS_18, owner).serialize(ref calldata); + calldata } diff --git a/cairo-contracts/packages/testkit/src/lib.cairo b/cairo-contracts/packages/testkit/src/lib.cairo index 577020742..b0030945b 100644 --- a/cairo-contracts/packages/testkit/src/lib.cairo +++ b/cairo-contracts/packages/testkit/src/lib.cairo @@ -35,7 +35,7 @@ pub mod dummies { pub use transfer::{ AMOUNT, CLASS_HASH, COSMOS, CS_USER, DECIMALS_18, DECIMAL_ZERO, EMPTY_MEMO, ERC20, HOSTED_DENOM, NAME, NATIVE_DENOM, OWNER, PACKET_COMMITMENT_ON_SN, PACKET_DATA_FROM_SN, SALT, - SN_USER, STARKNET, SUPPLY, SYMBOL, + SN_USER, STARKNET, SUPPLY, SYMBOL, ZERO, }; } pub mod event_spy { diff --git a/cairo-contracts/packages/testkit/src/setup.cairo b/cairo-contracts/packages/testkit/src/setup.cairo index 28a9ac928..e5e0bacac 100644 --- a/cairo-contracts/packages/testkit/src/setup.cairo +++ b/cairo-contracts/packages/testkit/src/setup.cairo @@ -1,18 +1,18 @@ use openzeppelin_testing::declare_class; use snforge_std::{ ContractClass, start_cheat_block_number_global, start_cheat_block_timestamp_global, - start_cheat_caller_address, + start_cheat_caller_address, stop_cheat_caller_address, }; use snforge_std::{EventSpy, spy_events}; use starknet::ContractAddress; -use starknet_ibc_apps::transfer::{ERC20Contract, TRANSFER_PORT_ID}; +use starknet_ibc_apps::transfer::{ERC20Contract, ERC20ContractTrait, TRANSFER_PORT_ID}; use starknet_ibc_core::client::ClientContract; use starknet_ibc_core::router::AppContract; use starknet_ibc_testkit::configs::{ CometClientConfig, CometClientConfigTrait, CoreConfig, CoreConfigTrait, TransferAppConfig, TransferAppConfigTrait, }; -use starknet_ibc_testkit::dummies::{CLIENT_TYPE, OWNER, RELAYER}; +use starknet_ibc_testkit::dummies::{CLIENT_TYPE, OWNER, RELAYER, SN_USER, SUPPLY}; use starknet_ibc_testkit::handles::{AppHandle, ClientHandle, CoreContract, CoreHandle, ERC20Handle}; #[derive(Drop, Serde)] @@ -41,9 +41,23 @@ pub impl SetupImpl of SetupTrait { ClientHandle::deploy_client(contract_name, *self.owner) } - /// Deploys an instance of ERC20 contract. - fn deploy_erc20(self: @Setup) -> ERC20Contract { - ERC20Handle::deploy(*self.erc20_contract_class) + /// Deploys an instance of IBC-compatible ERC20 contract with `0` initial supply. + fn deploy_erc20(self: @Setup, owner: ContractAddress) -> ERC20Contract { + ERC20Handle::deploy(*self.erc20_contract_class, owner) + } + + /// Deploys an IBC-compatible ERC-20 token with an initial supply. + /// The specified `receiver` will receive `amount` tokens after deployment. + fn deploy_erc20_with_supply( + self: @Setup, owner: ContractAddress, receiver: ContractAddress, amount: u256, + ) -> ERC20Contract { + let mut erc20 = self.deploy_erc20(owner); + start_cheat_caller_address(erc20.address, owner); + erc20.mint(amount); + stop_cheat_caller_address(erc20.address); + erc20.approve(owner, receiver, amount); + erc20.transfer_from(owner, receiver, amount); + erc20 } /// Deploys an instance of ICS-20 Token Transfer contract. @@ -82,13 +96,13 @@ pub impl SetupImpl of SetupTrait { fn setup_transfer(transfer_contract_name: ByteArray) -> (AppContract, ERC20Contract) { let mut setup = Self::default(); - let mut erc20 = setup.deploy_erc20(); - let ics20 = setup.deploy_transfer(transfer_contract_name); // Set the caller address to `OWNER`, as ICS-20 callbacks are permissioned. start_cheat_caller_address(ics20.address, OWNER()); + let erc20 = setup.deploy_erc20_with_supply(ics20.address, SN_USER(), SUPPLY); + (ics20, erc20) } @@ -117,10 +131,10 @@ pub impl SetupImpl of SetupTrait { core.register_client(CLIENT_TYPE(), comet.address); - let mut erc20 = setup.deploy_erc20(); - let mut ics20 = setup.deploy_transfer(transfer_contract_name); + let erc20 = setup.deploy_erc20_with_supply(ics20.address, SN_USER(), SUPPLY); + core.register_app(TRANSFER_PORT_ID(), ics20.address); (core, ics20, erc20) diff --git a/cairo-contracts/packages/utils/src/lib.cairo b/cairo-contracts/packages/utils/src/lib.cairo index facada24c..1ca14662e 100644 --- a/cairo-contracts/packages/utils/src/lib.cairo +++ b/cairo-contracts/packages/utils/src/lib.cairo @@ -12,12 +12,3 @@ pub mod governance { pub use component::IBCGovernanceComponent; pub use interface::{IGovernance, IGovernanceDispatcher, IGovernanceDispatcherTrait}; } -pub mod mintable { - mod component; - mod errors; - mod interface; - - pub use component::ERC20MintableComponent; - pub use errors::MintableErrors; - pub use interface::{IERC20Mintable, IERC20MintableDispatcher, IERC20MintableDispatcherTrait}; -} diff --git a/cairo-contracts/packages/utils/src/mintable/component.cairo b/cairo-contracts/packages/utils/src/mintable/component.cairo deleted file mode 100644 index e6c22aa62..000000000 --- a/cairo-contracts/packages/utils/src/mintable/component.cairo +++ /dev/null @@ -1,172 +0,0 @@ -#[starknet::component] -pub mod ERC20MintableComponent { - use core::num::traits::{CheckedAdd, CheckedSub, Zero}; - use openzeppelin_token::erc20::ERC20Component; - use openzeppelin_token::erc20::erc20::ERC20Component::Transfer; - use starknet::ContractAddress; - use starknet::get_caller_address; - use starknet::storage::{ - StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, - StoragePointerWriteAccess, - }; - use starknet_ibc_utils::mintable::errors::MintableErrors; - use starknet_ibc_utils::mintable::interface::IERC20Mintable; - - #[storage] - pub struct Storage { - permission: ContractAddress, - } - - #[event] - #[derive(Drop, Debug, starknet::Event)] - pub enum Event {} - - #[embeddable_as(ERC20Mintable)] - pub impl ERC20MintableImpl< - TContractState, - +HasComponent, - +Drop, - impl ERC20: ERC20Component::HasComponent, - > of IERC20Mintable> { - fn permissioned_mint( - ref self: ComponentState, recipient: ContractAddress, amount: u256, - ) { - let permitted_minter = self.read_permission(); - assert(permitted_minter == get_caller_address(), MintableErrors::UNAUTHORIZED_MINTER); - - self.mint(recipient, amount); - } - - fn permissioned_burn( - ref self: ComponentState, account: ContractAddress, amount: u256, - ) { - let permitted_burner = self.read_permission(); - assert(permitted_burner == get_caller_address(), MintableErrors::UNAUTHORIZED_BURNER); - self.burn(account, amount); - } - } - - #[generate_trait] - pub impl ERC20MintableInternalImpl< - TContractState, - +HasComponent, - +Drop, - impl ERC20: ERC20Component::HasComponent, - > of ERC20MintableInternalTrait { - fn initializer(ref self: ComponentState) { - self.write_permission(get_caller_address()); - } - - fn mint( - ref self: ComponentState, recipient: ContractAddress, amount: u256, - ) { - assert(recipient.is_non_zero(), MintableErrors::MINT_TO_ZERO); - - let new_amount = self.read_total_supply().checked_add(amount); - - assert(new_amount.is_some(), MintableErrors::OVERFLOWED_AMOUNT); - - self.write_total_supply(new_amount.unwrap()); - - let new_balance = self.read_balance(recipient).checked_add(amount); - - assert(new_balance.is_some(), MintableErrors::OVERFLOWED_AMOUNT); - - self.write_balance(recipient, new_balance.unwrap()); - - self.emit_transfer_event(Zero::zero(), recipient, amount); - } - - fn burn(ref self: ComponentState, account: ContractAddress, amount: u256) { - assert(account.is_non_zero(), MintableErrors::BURN_FROM_ZERO); - - let total_supply = self.read_total_supply(); - - assert(total_supply >= amount, MintableErrors::INSUFFICIENT_SUPPLY); - - let new_amount = total_supply.checked_sub(amount); - - assert(new_amount.is_some(), MintableErrors::OVERFLOWED_AMOUNT); - - self.write_total_supply(new_amount.unwrap()); - - let balance = self.read_balance(account); - - assert(balance >= amount, MintableErrors::INSUFFICIENT_BALANCE); - - let new_balance = balance.checked_sub(amount); - - assert(new_balance.is_some(), MintableErrors::OVERFLOWED_AMOUNT); - - self.write_balance(account, new_balance.unwrap()); - - self.emit_transfer_event(account, Zero::zero(), amount); - } - } - - #[generate_trait] - impl ERC20ReaderImpl< - TContractState, - +HasComponent, - +Drop, - impl ERC20: ERC20Component::HasComponent, - > of ERC20ReaderTrait { - fn read_permission(self: @ComponentState) -> ContractAddress { - self.permission.read() - } - - fn read_balance(self: @ComponentState, account: ContractAddress) -> u256 { - let erc20_comp = get_dep_component!(self, ERC20); - erc20_comp.ERC20_balances.read(account) - } - - fn read_total_supply(self: @ComponentState) -> u256 { - let erc20_comp = get_dep_component!(self, ERC20); - erc20_comp.ERC20_total_supply.read() - } - } - - #[generate_trait] - impl ERC20WriterImpl< - TContractState, - +HasComponent, - +Drop, - impl ERC20: ERC20Component::HasComponent, - > of ERC20WriterTrait { - fn write_permission( - ref self: ComponentState, permitted_minter: ContractAddress, - ) { - self.permission.write(permitted_minter); - } - - fn write_balance( - ref self: ComponentState, account: ContractAddress, amount: u256, - ) { - let mut erc20_comp = get_dep_component_mut!(ref self, ERC20); - erc20_comp.ERC20_balances.write(account, amount); - } - - fn write_total_supply(ref self: ComponentState, amount: u256) { - let mut erc20_comp = get_dep_component_mut!(ref self, ERC20); - erc20_comp.ERC20_total_supply.write(amount); - } - } - - #[generate_trait] - impl ERC20EventEmitterImpl< - TContractState, - +HasComponent, - +Drop, - impl ERC20: ERC20Component::HasComponent, - > of ERC20EventEmitterTrait { - fn emit_transfer_event( - ref self: ComponentState, - from: ContractAddress, - to: ContractAddress, - value: u256, - ) { - let mut erc20_comp = get_dep_component_mut!(ref self, ERC20); - erc20_comp.emit(Transfer { from, to, value }); - } - } -} diff --git a/cairo-contracts/packages/utils/src/mintable/errors.cairo b/cairo-contracts/packages/utils/src/mintable/errors.cairo deleted file mode 100644 index 21a1b9857..000000000 --- a/cairo-contracts/packages/utils/src/mintable/errors.cairo +++ /dev/null @@ -1,10 +0,0 @@ -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'; - pub const OVERFLOWED_AMOUNT: felt252 = 'ERC20: overflowed amount'; -} - diff --git a/cairo-contracts/packages/utils/src/mintable/interface.cairo b/cairo-contracts/packages/utils/src/mintable/interface.cairo deleted file mode 100644 index 20ae555cf..000000000 --- a/cairo-contracts/packages/utils/src/mintable/interface.cairo +++ /dev/null @@ -1,7 +0,0 @@ -use starknet::ContractAddress; - -#[starknet::interface] -pub trait IERC20Mintable { - fn permissioned_mint(ref self: TContractState, recipient: ContractAddress, amount: u256); - fn permissioned_burn(ref self: TContractState, account: ContractAddress, amount: u256); -} diff --git a/relayer/crates/starknet-chain-components/src/types/events/ics20.rs b/relayer/crates/starknet-chain-components/src/types/events/ics20.rs index d94b6e678..f3830708a 100644 --- a/relayer/crates/starknet-chain-components/src/types/events/ics20.rs +++ b/relayer/crates/starknet-chain-components/src/types/events/ics20.rs @@ -71,7 +71,6 @@ pub struct CreateIbcTokenEvent { pub symbol: String, pub decimals: u8, pub address: StarknetAddress, - pub initial_supply: U256, } pub struct DecodeIbcTransferEvents; @@ -291,8 +290,7 @@ where + HasEncoding + CanRaiseAsyncError, CairoEncoding: HasEncodedType> - + CanDecode - + CanDecode, + + CanDecode, { fn decode( event_encoding: &EventEncoding, @@ -304,16 +302,11 @@ where .decode(&event.keys) .map_err(EventEncoding::raise_error)?; - let product![initial_supply] = cairo_encoding - .decode(&event.data) - .map_err(EventEncoding::raise_error)?; - Ok(CreateIbcTokenEvent { name, symbol, decimals, address, - initial_supply, }) } } diff --git a/relayer/crates/starknet-chain-components/src/types/messages/erc20/deploy.rs b/relayer/crates/starknet-chain-components/src/types/messages/erc20/deploy.rs index f7bbdd12d..1dec6cd12 100644 --- a/relayer/crates/starknet-chain-components/src/types/messages/erc20/deploy.rs +++ b/relayer/crates/starknet-chain-components/src/types/messages/erc20/deploy.rs @@ -2,7 +2,6 @@ use cgp::core::component::UseContext; use cgp::prelude::*; use hermes_encoding_components::impls::encode_mut::combine::CombineEncoders; use hermes_encoding_components::impls::encode_mut::field::EncodeField; -use starknet::core::types::U256; use crate::impls::types::address::StarknetAddress; #[derive(HasField)] @@ -10,8 +9,6 @@ pub struct DeployErc20TokenMessage { pub name: String, pub symbol: String, pub decimals: u8, - pub fixed_supply: U256, - pub recipient: StarknetAddress, pub owner: StarknetAddress, } @@ -20,8 +17,6 @@ pub type EncodeDeployErc20TokenMessage = CombineEncoders< EncodeField, EncodeField, EncodeField, - EncodeField, - EncodeField, EncodeField, ], >; diff --git a/relayer/crates/starknet-integration-tests/src/tests/erc20.rs b/relayer/crates/starknet-integration-tests/src/tests/erc20.rs index 7aec009b1..dcfbcc006 100644 --- a/relayer/crates/starknet-integration-tests/src/tests/erc20.rs +++ b/relayer/crates/starknet-integration-tests/src/tests/erc20.rs @@ -6,6 +6,7 @@ use hermes_error::types::Error; use hermes_relayer_components::chain::traits::send_message::CanSendSingleMessage; use hermes_runtime_components::traits::fs::read_file::CanReadFileAsString; use hermes_starknet_chain_components::impls::encoding::events::CanFilterDecodeEvents; +use hermes_starknet_chain_components::impls::types::message::StarknetMessage; use hermes_starknet_chain_components::traits::contract::declare::CanDeclareContract; use hermes_starknet_chain_components::traits::contract::deploy::CanDeployContract; use hermes_starknet_chain_components::traits::messages::transfer::CanBuildTransferTokenMessage; @@ -16,6 +17,9 @@ use hermes_starknet_chain_components::types::messages::erc20::deploy::DeployErc2 use hermes_starknet_chain_context::contexts::encoding::cairo::StarknetCairoEncoding; use hermes_starknet_chain_context::contexts::encoding::event::StarknetEventEncoding; use hermes_test_components::bootstrap::traits::chain::CanBootstrapChain; +use starknet::accounts::Call; +use starknet::core::types::U256; +use starknet::macros::selector; use tracing::info; use crate::contexts::bootstrap::StarknetBootstrap; @@ -58,6 +62,8 @@ fn test_erc20_transfer() -> Result<(), Error> { class_hash }; + let initial_supply = 1000u32; + let token_address = { let relayer_address = chain_driver.relayer_wallet.account_address; @@ -65,8 +71,6 @@ fn test_erc20_transfer() -> Result<(), Error> { name: "token".into(), symbol: "token".into(), decimals: 18, - fixed_supply: 1000u32.into(), - recipient: relayer_address, owner: relayer_address, }; @@ -78,13 +82,23 @@ fn test_erc20_transfer() -> Result<(), Error> { info!("deployed ERC20 contract to address: {:?}", token_address); + let calldata = StarknetCairoEncoding.encode(&U256::from(initial_supply))?; + + let call = Call { + to: *token_address, + selector: selector!("mint"), + calldata, + }; + + chain.send_message(StarknetMessage::new(call)).await?; + + info!("Mint {initial_supply} initial supply to address: {relayer_address}"); + let balance = chain .query_token_balance(&token_address, &relayer_address) .await?; - info!("initial balance: {}", balance); - - assert_eq!(balance.quantity, 1000u32.into()); + assert_eq!(balance.quantity, initial_supply.into()); token_address };