diff --git a/Scarb.lock b/Scarb.lock index 95f1006..597c2e3 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -1,15 +1,6 @@ # Code generated by scarb DO NOT EDIT. version = 1 -[[package]] -name = "cairo_appchain_bridge" -version = "0.1.0" -dependencies = [ - "openzeppelin", - "piltover", - "snforge_std", -] - [[package]] name = "openzeppelin" version = "0.14.0" @@ -18,7 +9,7 @@ source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.14.0#f0 [[package]] name = "piltover" version = "0.1.0" -source = "git+https://github.com/byteZorvin/piltover?branch=main#34be166da6ec295f950c65ad231890127da68b5e" +source = "git+https://github.com/byteZorvin/piltover?branch=bridge-testing#a4021a0bb5e1638e3aebf634f5810ebb17da6a38" dependencies = [ "openzeppelin", ] @@ -27,3 +18,12 @@ dependencies = [ name = "snforge_std" version = "0.26.0" source = "git+https://github.com/foundry-rs/starknet-foundry?tag=v0.26.0#50eb589db65e113efe4f09241feb59b574228c7e" + +[[package]] +name = "starknet_bridge" +version = "0.1.0" +dependencies = [ + "openzeppelin", + "piltover", + "snforge_std", +] diff --git a/Scarb.toml b/Scarb.toml index 84b37b4..d33fb78 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -1,5 +1,5 @@ [package] -name = "cairo_appchain_bridge" +name = "starknet_bridge" version = "0.1.0" edition = "2023_11" @@ -8,7 +8,7 @@ edition = "2023_11" [dependencies] openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v0.14.0" } starknet = "2.6.4" -piltover = { git = "https://github.com/byteZorvin/piltover", branch="main"} +piltover = { git = "https://github.com/byteZorvin/piltover", branch="bridge-testing"} [dev-dependencies] snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry", tag = "v0.26.0" } @@ -17,10 +17,5 @@ snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry", tag = "v casm = true build-external-contracts = ["piltover::appchain::appchain"] -[[tool.snforge.fork]] -name = "mainnet" -url = "https://starknet-mainnet.public.blastapi.io" -block_id.tag = "Latest" - [scripts] test = "snforge test" diff --git a/src/bridge/interface.cairo b/src/bridge/interface.cairo index 311fdfa..f23c4c1 100644 --- a/src/bridge/interface.cairo +++ b/src/bridge/interface.cairo @@ -1,22 +1,5 @@ use starknet::ContractAddress; - -#[derive(Serde, Drop, starknet::Store, PartialEq)] -pub enum TokenStatus { - #[default] - Unknown, - Pending, - Active, - Blocked -} - -#[derive(Serde, Drop, starknet::Store)] -pub struct TokenSettings { - pub token_status: TokenStatus, - pub deployment_message_hash: felt252, - pub pending_deployment_expiration: u64, - pub max_total_balance: u256, - pub withdrawal_limit_applied: bool -} +use starknet_bridge::bridge::types::{TokenStatus, TokenSettings}; #[starknet::interface] pub trait ITokenBridgeAdmin { @@ -34,8 +17,8 @@ pub trait ITokenBridgeAdmin { pub trait ITokenBridge { fn appchain_bridge(self: @TContractState) -> ContractAddress; fn identity(self: @TContractState) -> ByteArray; - fn getStatus(self: @TContractState, token: ContractAddress) -> TokenStatus; - fn isServicingToken(self: @TContractState, token: ContractAddress) -> bool; + fn get_status(self: @TContractState, token: ContractAddress) -> TokenStatus; + fn is_servicing_token(self: @TContractState, token: ContractAddress) -> bool; fn is_withdrawal_limit_applied(self: @TContractState, token: ContractAddress) -> bool; fn enroll_token(ref self: TContractState, token: ContractAddress); diff --git a/src/bridge/token_bridge.cairo b/src/bridge/token_bridge.cairo index 3e97c10..a428260 100644 --- a/src/bridge/token_bridge.cairo +++ b/src/bridge/token_bridge.cairo @@ -16,7 +16,7 @@ pub mod TokenBridge { use openzeppelin::upgrades::UpgradeableComponent; use openzeppelin::upgrades::interface::IUpgradeable; - use cairo_appchain_bridge::withdrawal_limit::component::WithdrawalLimitComponent; + use starknet_bridge::withdrawal_limit::component::WithdrawalLimitComponent; component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); @@ -25,12 +25,13 @@ pub mod TokenBridge { use core::num::traits::zero::Zero; use starknet::{ContractAddress, get_contract_address, get_caller_address, get_block_timestamp}; - use cairo_appchain_bridge::bridge::interface::{ - TokenStatus, TokenSettings, ITokenBridge, ITokenBridgeAdmin + use starknet_bridge::bridge::{ + types::{TokenStatus, TokenSettings, MessageHash, Nonce}, + interface::{ITokenBridge, ITokenBridgeAdmin} }; use piltover::messaging::interface::IMessagingDispatcher; use piltover::messaging::interface::IMessagingDispatcherTrait; - use cairo_appchain_bridge::constants; + use starknet_bridge::constants; use starknet::ClassHash; @@ -49,8 +50,11 @@ pub mod TokenBridge { #[storage] struct Storage { + // corresponding bridge contract_address deployed on the appchain appchain_bridge: ContractAddress, + // the core messaging contract deployed on starknet used for l2 - l3 messsaging messaging_contract: IMessagingDispatcher, + // All token related settings and its status token_settings: LegacyMap, #[substorage(v0)] ownable: OwnableComponent::Storage, @@ -67,7 +71,7 @@ pub mod TokenBridge { pub const APPCHAIN_BRIDGE_NOT_SET: felt252 = 'L3 bridge not set'; pub const ZERO_DEPOSIT: felt252 = 'Zero amount'; pub const ALREADY_ENROLLED: felt252 = 'Already enrolled'; - pub const DEPLOYMENT_MESSAGE_NOT_EXIST: felt252 = 'Deployment message inexistent'; + pub const DEPLOYMENT_MESSAGE_DOES_NOT_EXIST: felt252 = 'Deployment message inexistent'; pub const CANNOT_DEACTIVATE: felt252 = 'Cannot deactivate and block'; pub const CANNOT_BLOCK: felt252 = 'Cannot block'; pub const INVALID_RECIPIENT: felt252 = 'Invalid recipient'; @@ -111,7 +115,7 @@ pub mod TokenBridge { #[derive(Drop, starknet::Event)] struct TokenEnrollmentInitiated { token: ContractAddress, - deployment_message_hash: felt252 + deployment_message_hash: MessageHash } @@ -124,7 +128,7 @@ pub mod TokenBridge { amount: u256, #[key] appchain_recipient: ContractAddress, - nonce: felt252, + nonce: Nonce, } #[derive(Drop, starknet::Event)] @@ -137,7 +141,7 @@ pub mod TokenBridge { #[key] appchain_recipient: ContractAddress, message: Span, - nonce: felt252, + nonce: Nonce, } #[derive(Drop, starknet::Event)] @@ -149,7 +153,7 @@ pub mod TokenBridge { amount: u256, #[key] appchain_recipient: ContractAddress, - nonce: felt252, + nonce: Nonce, } #[derive(Drop, starknet::Event)] @@ -174,7 +178,7 @@ pub mod TokenBridge { amount: u256, #[key] appchain_recipient: ContractAddress, - nonce: felt252 + nonce: Nonce } #[derive(Drop, starknet::Event)] @@ -187,7 +191,7 @@ pub mod TokenBridge { #[key] appchain_recipient: ContractAddress, message: Span, - nonce: felt252 + nonce: Nonce } #[derive(Drop, starknet::Event)] @@ -239,39 +243,16 @@ pub mod TokenBridge { #[generate_trait] impl TokenBridgeInternalImpl of TokenBridgeInternal { - fn deployment_message_payload( - self: @ContractState, token: ContractAddress - ) -> Span { - // Create the calldata that will be sent to on_receive. l2_token, amount and - // depositor are the fields from the deposit context. - let mut calldata = ArrayTrait::new(); - let dispatcher = IERC20MetadataDispatcher { contract_address: token }; - dispatcher.name().serialize(ref calldata); - dispatcher.symbol().serialize(ref calldata); - dispatcher.decimals().serialize(ref calldata); - calldata.span() - } - - - fn accept_deposit(self: @ContractState, token: ContractAddress, amount: u256) { - let caller = get_caller_address(); - let dispatcher = IERC20Dispatcher { contract_address: token }; - assert(dispatcher.balance_of(caller) == amount, 'Not enough balance'); - dispatcher.transfer_from(caller, get_contract_address(), amount); - } - fn send_deploy_message(self: @ContractState, token: ContractAddress) -> felt252 { assert(self.appchain_bridge().is_non_zero(), Errors::APPCHAIN_BRIDGE_NOT_SET); - // TODO: Check fees not sure if needed - // TODO: Add the token deployment selector as a constant here let (hash, _nonce) = self .messaging_contract .read() .send_message_to_appchain( self.appchain_bridge(), constants::HANDLE_TOKEN_DEPLOYMENT_SELECTOR, - self.deployment_message_payload(token) + deployment_message_payload(token) ); return hash; } @@ -283,7 +264,7 @@ pub mod TokenBridge { appchain_recipient: ContractAddress, message: Span, selector: felt252, - nonce: felt252, + nonce: Nonce, ) { let is_with_message = selector == constants::HANDLE_DEPOSIT_WITH_MESSAGE_SELECTOR; let caller = get_caller_address(); @@ -321,7 +302,7 @@ pub mod TokenBridge { appchain_recipient: ContractAddress, message: Span, selector: felt252, - ) -> felt252 { + ) -> Nonce { assert(self.appchain_bridge().is_non_zero(), Errors::APPCHAIN_BRIDGE_NOT_SET); assert(amount > 0, Errors::ZERO_DEPOSIT); @@ -347,6 +328,7 @@ pub mod TokenBridge { let mut payload = ArrayTrait::new(); constants::TRANSFER_FROM_STARKNET.serialize(ref payload); recipient.serialize(ref payload); + token.serialize(ref payload); amount.serialize(ref payload); self .messaging_contract @@ -354,7 +336,7 @@ pub mod TokenBridge { .consume_message_from_appchain(appchain_bridge, payload.span()); } - fn _block_token(ref self: ContractState, token: ContractAddress) { + fn block_token_internal(ref self: ContractState, token: ContractAddress) { let new_settings = TokenSettings { token_status: TokenStatus::Blocked, ..self.token_settings.read(token) }; @@ -383,26 +365,44 @@ pub mod TokenBridge { return payload.span(); } + + fn deployment_message_payload(token: ContractAddress) -> Span { + // Create the calldata that will be sent to on_receive. l2_token, amount and + // depositor are the fields from the deposit context. + let mut calldata = ArrayTrait::new(); + let dispatcher = IERC20MetadataDispatcher { contract_address: token }; + token.serialize(ref calldata); + dispatcher.name().serialize(ref calldata); + dispatcher.symbol().serialize(ref calldata); + dispatcher.decimals().serialize(ref calldata); + calldata.span() + } + + + fn accept_deposit(token: ContractAddress, amount: u256) { + let caller = get_caller_address(); + let dispatcher = IERC20Dispatcher { contract_address: token }; + assert(dispatcher.balance_of(caller) == amount, 'Not enough balance'); + dispatcher.transfer_from(caller, get_contract_address(), amount); + } + + #[abi(embed_v0)] impl TokenBrdigeAdminImpl of ITokenBridgeAdmin { fn set_appchain_token_bridge(ref self: ContractState, appchain_bridge: ContractAddress) { + self.ownable.assert_only_owner(); self.appchain_bridge.write(appchain_bridge); } - // Deactivates a token in the system. - // This fun ction is used to deactivate a token that was previously enrolled. - // Only the manager, who initiated the enrollment, can call this function. - // // @param token The address of the token contract to be deactivated. - // No return value, but it updates the token's status to 'Deactivated'. - // Emits a `TokenDeactivated` event when the deactivation is successful. + // No return value, but it updates the token's status to 'Blocked'. + // Emits a `TokenBlocked` event when the deactivation is successful. // Throws an error if the token is not enrolled or if the sender is not the manager. fn block_token(ref self: ContractState, token: ContractAddress) { self.ownable.assert_only_owner(); - let settings = self.token_settings.read(token); - assert(settings.token_status == TokenStatus::Unknown, Errors::CANNOT_BLOCK); + assert(self.get_status(token) == TokenStatus::Unknown, Errors::CANNOT_BLOCK); - self._block_token(:token); + self.block_token_internal(token); self.emit(TokenBlocked { token }); } @@ -416,7 +416,7 @@ pub mod TokenBridge { Errors::CANNOT_DEACTIVATE ); - self._block_token(:token); + self.block_token_internal(:token); self.emit(TokenDeactivated { token }); self.emit(TokenBlocked { token }); @@ -459,25 +459,21 @@ pub mod TokenBridge { } fn identity(self: @ContractState) -> ByteArray { - "cairo_appchain_bridge" + "STARKNET_BRIDGE_0.1.0" } fn enroll_token(ref self: ContractState, token: ContractAddress) { - let status = self.token_settings.read(token).token_status; - assert(status == TokenStatus::Unknown, Errors::ALREADY_ENROLLED); + assert(self.get_status(token) == TokenStatus::Unknown, Errors::ALREADY_ENROLLED); // Send message to appchain let deployment_message_hash = self.send_deploy_message(token); - // TODO: check the deployment msg has been sent by calling l1ToL2Messages() > 0 - - // Dep(piltover): Will uncomment once piltover updates interface - // let nonce = self - // .messaging_contract - // .read() - // .sn_to_appchain_messages(deployment_message_hash); - // assert(nonce > 0, Errors::DEPLOYMENT_MESSAGE_NOT_EXIST); + let nonce = self + .messaging_contract + .read() + .sn_to_appchain_messages(deployment_message_hash); + assert(nonce.is_non_zero(), Errors::DEPLOYMENT_MESSAGE_DOES_NOT_EXIST); let token_status = TokenSettings { token_status: TokenStatus::Pending, @@ -499,7 +495,7 @@ pub mod TokenBridge { appchain_recipient: ContractAddress ) { let no_message: Span = array![].span(); - self.accept_deposit(token, amount); + accept_deposit(token, amount); let nonce = self .send_deposit_message( token, @@ -528,7 +524,7 @@ pub mod TokenBridge { appchain_recipient: ContractAddress, message: Span ) { - self.accept_deposit(token, amount); + accept_deposit(token, amount); let nonce = self .send_deposit_message( token, @@ -551,6 +547,7 @@ pub mod TokenBridge { // Piggy-back the deposit tx to check and update the status of token bridge deployment. self.check_deployment_status(token); } + // // checks token deployment status. // relies on starknet clearing l1-l2 message upon successful completion of deployment. @@ -559,28 +556,22 @@ pub mod TokenBridge { // fn check_deployment_status(ref self: ContractState, token: ContractAddress) { let settings = self.token_settings.read(token); - if (settings.token_status == TokenStatus::Pending) { + if (settings.token_status != TokenStatus::Pending) { return; } - let _msg_hash = settings.deployment_message_hash; - // DEP(piltover) : to uncomment once the interface of piltover changes - // if (self.messaging_contract.read().sn_to_appchain_messages(msg_hash) > 0) { - // let new_settings = TokenSettings { - // token_status: TokenStatus::Active, ..settings - // }; - // self.token_settings.write(token, new_settings); - // } else if (get_block_timestamp() > settings.pending_deployment_expiration) { - // let new_settings = TokenSettings { - // token_status: TokenStatus::Unknown, - // deployment_msg_hash: 0, - // pending_deployment_expiration: 0, - // max_total_balance: 0, - // withdrawal_limit_applied: false - // }; - // self.token_settings.write(token, new_settings); - // } - + let nonce = self + .messaging_contract + .read() + .sn_to_appchain_messages(settings.deployment_message_hash); + + if (nonce.is_non_zero()) { + let new_settings = TokenSettings { token_status: TokenStatus::Active, ..settings }; + self.token_settings.write(token, new_settings); + } else if (get_block_timestamp() > settings.pending_deployment_expiration) { + let new_settings = TokenSettings { token_status: TokenStatus::Unknown, ..settings }; + self.token_settings.write(token, new_settings); + } } @@ -617,7 +608,7 @@ pub mod TokenBridge { token: ContractAddress, amount: u256, appchain_recipient: ContractAddress, - nonce: felt252 + nonce: Nonce ) { let no_message: Span = array![].span(); self @@ -643,7 +634,7 @@ pub mod TokenBridge { amount: u256, appchain_recipient: ContractAddress, message: Span, - nonce: felt252 + nonce: Nonce ) { self .messaging_contract @@ -673,7 +664,7 @@ pub mod TokenBridge { amount: u256, appchain_recipient: ContractAddress, message: Span, - nonce: felt252 + nonce: Nonce ) { self .messaging_contract @@ -706,7 +697,7 @@ pub mod TokenBridge { token: ContractAddress, amount: u256, appchain_recipient: ContractAddress, - nonce: felt252 + nonce: Nonce ) { let no_message: Span = array![].span(); self @@ -731,11 +722,11 @@ pub mod TokenBridge { } - fn getStatus(self: @ContractState, token: ContractAddress) -> TokenStatus { + fn get_status(self: @ContractState, token: ContractAddress) -> TokenStatus { self.token_settings.read(token).token_status } - fn isServicingToken(self: @ContractState, token: ContractAddress) -> bool { + fn is_servicing_token(self: @ContractState, token: ContractAddress) -> bool { self.token_settings.read(token).token_status == TokenStatus::Active } diff --git a/src/bridge/types.cairo b/src/bridge/types.cairo new file mode 100644 index 0000000..5c22f48 --- /dev/null +++ b/src/bridge/types.cairo @@ -0,0 +1,20 @@ +pub type MessageHash = felt252; +pub type Nonce = felt252; + +#[derive(Serde, Drop, starknet::Store, PartialEq)] +pub enum TokenStatus { + #[default] + Unknown, + Pending, + Active, + Blocked +} + +#[derive(Serde, Drop, starknet::Store)] +pub struct TokenSettings { + pub token_status: TokenStatus, + pub deployment_message_hash: MessageHash, + pub pending_deployment_expiration: u64, + pub max_total_balance: u256, + pub withdrawal_limit_applied: bool +} diff --git a/src/constants.cairo b/src/constants.cairo index 29289ec..3a06bf3 100644 --- a/src/constants.cairo +++ b/src/constants.cairo @@ -1,6 +1,4 @@ // Starknet L1 handler selectors. -pub const HANDLE_DEPOSIT_SELECTOR: felt252 = - 1285101517810983806491589552491143496277809242732141897358598292095611420389; pub const HANDLE_TOKEN_DEPOSIT_SELECTOR: felt252 = 774397379524139446221206168840917193112228400237242521560346153613428128537; @@ -9,24 +7,11 @@ pub const HANDLE_DEPOSIT_WITH_MESSAGE_SELECTOR: felt252 = pub const HANDLE_TOKEN_DEPLOYMENT_SELECTOR: felt252 = 1737780302748468118210503507461757847859991634169290761669750067796330642876; - -pub const TRANSFER_FROM_STARKNET: felt252 = 0; -pub const UINT256_PART_SIZE_BITS: felt252 = 128; -pub const UINT256_PART_SIZE: felt252 = 2 * UINT256_PART_SIZE_BITS; pub const MAX_PENDING_DURATION: felt252 = 5 * 86400; - +pub const TRANSFER_FROM_STARKNET: felt252 = 0; // Withdrawal limit - -pub const WITHDRAW_MESSAGE: felt252 = 0; -pub const CONTRACT_IDENTITY: felt252 = 'STARKGATE'; -pub const CONTRACT_VERSION: felt252 = 2; - -pub const DEFAULT_DAILY_WITHDRAW_LIMIT_PCT: u8 = 5; - pub const SECONDS_IN_DAY: u64 = 86400; -pub const DEFAULT_UPGRADE_DELAY: u64 = 0; - // When storing the remaining quota for today, we add 1 to the value. This is because we want // that 0 will mean that it was not set yet. diff --git a/src/lib.cairo b/src/lib.cairo index 8fa7e77..abfba84 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -1,6 +1,7 @@ pub mod bridge { pub mod token_bridge; pub mod interface; + pub mod types; } pub mod withdrawal_limit { diff --git a/src/withdrawal_limit/component.cairo b/src/withdrawal_limit/component.cairo index 40e3840..0973343 100644 --- a/src/withdrawal_limit/component.cairo +++ b/src/withdrawal_limit/component.cairo @@ -1,12 +1,12 @@ #[starknet::component] pub mod WithdrawalLimitComponent { use starknet::{ContractAddress, get_block_timestamp}; - use cairo_appchain_bridge::bridge::interface::ITokenBridge; - use cairo_appchain_bridge::constants; + use starknet_bridge::bridge::interface::ITokenBridge; + use starknet_bridge::constants; use core::integer::BoundedInt; use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; - use cairo_appchain_bridge::withdrawal_limit::interface::IWithdrawalLimit; + use starknet_bridge::withdrawal_limit::interface::IWithdrawalLimit; #[storage] struct Storage { diff --git a/tests/token_bridge_test.cairo b/tests/token_bridge_test.cairo index 7d03d60..2573fa2 100644 --- a/tests/token_bridge_test.cairo +++ b/tests/token_bridge_test.cairo @@ -1,6 +1,6 @@ #[cfg(test)] mod tests { - use cairo_appchain_bridge::bridge::interface::ITokenBridgeDispatcherTrait; + use starknet_bridge::bridge::interface::ITokenBridgeDispatcherTrait; use core::serde::Serde; use core::result::ResultTrait; use core::option::OptionTrait; @@ -8,8 +8,8 @@ mod tests { use snforge_std as snf; use snforge_std::{ContractClassTrait}; use starknet::{ContractAddress, storage::StorageMemberAccessTrait}; - use cairo_appchain_bridge::mocks::erc20::ERC20; - use cairo_appchain_bridge::bridge::interface::{ + use starknet_bridge::mocks::erc20::ERC20; + use starknet_bridge::bridge::interface::{ ITokenBridgeAdmin, ITokenBridge, ITokenBridgeDispatcher }; use starknet::contract_address::{contract_address_const};