diff --git a/Scarb.lock b/Scarb.lock index e8dc8dc..797b9e9 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -4,7 +4,7 @@ version = 1 [[package]] name = "alexandria_bytes" version = "0.1.0" -source = "git+https://github.com/keep-starknet-strange/alexandria.git#8ddf02430dfe04656ed5bf1b99de7024da1aef41" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=8ddf0243#8ddf02430dfe04656ed5bf1b99de7024da1aef41" dependencies = [ "alexandria_data_structures", "alexandria_math", @@ -13,7 +13,7 @@ dependencies = [ [[package]] name = "alexandria_data_structures" version = "0.2.0" -source = "git+https://github.com/keep-starknet-strange/alexandria.git#8ddf02430dfe04656ed5bf1b99de7024da1aef41" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=8ddf0243#8ddf02430dfe04656ed5bf1b99de7024da1aef41" dependencies = [ "alexandria_encoding", ] @@ -21,7 +21,7 @@ dependencies = [ [[package]] name = "alexandria_encoding" version = "0.1.0" -source = "git+https://github.com/keep-starknet-strange/alexandria.git#8ddf02430dfe04656ed5bf1b99de7024da1aef41" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=8ddf0243#8ddf02430dfe04656ed5bf1b99de7024da1aef41" dependencies = [ "alexandria_math", "alexandria_numeric", @@ -30,7 +30,7 @@ dependencies = [ [[package]] name = "alexandria_math" version = "0.2.0" -source = "git+https://github.com/keep-starknet-strange/alexandria.git#8ddf02430dfe04656ed5bf1b99de7024da1aef41" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=8ddf0243#8ddf02430dfe04656ed5bf1b99de7024da1aef41" dependencies = [ "alexandria_data_structures", ] @@ -38,7 +38,7 @@ dependencies = [ [[package]] name = "alexandria_numeric" version = "0.1.0" -source = "git+https://github.com/keep-starknet-strange/alexandria.git#8ddf02430dfe04656ed5bf1b99de7024da1aef41" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=8ddf0243#8ddf02430dfe04656ed5bf1b99de7024da1aef41" dependencies = [ "alexandria_math", ] @@ -51,6 +51,7 @@ dependencies = [ "alexandria_math", "openzeppelin", "snforge_std", + "succinct_sn", ] [[package]] @@ -62,3 +63,13 @@ source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?rev=2815498#28 name = "snforge_std" version = "0.17.0" source = "git+https://github.com/foundry-rs/starknet-foundry?tag=v0.17.0#63f7f0b533a3d852e2b60214e0f40b99c9dcbb26" + +[[package]] +name = "succinct_sn" +version = "0.1.0" +source = "git+https://github.com/keep-starknet-strange/succinct-starknet.git#33bfd3d3d31a136a36fa9432439de31808b9424c" +dependencies = [ + "alexandria_bytes", + "openzeppelin", + "snforge_std", +] diff --git a/Scarb.toml b/Scarb.toml index 32944fd..9baa103 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -9,12 +9,18 @@ test = "snforge test" snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry", tag = "v0.17.0" } starknet = ">=2.5.3" openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", rev = "2815498" } -alexandria_bytes = { git = "https://github.com/keep-starknet-strange/alexandria.git" } -alexandria_math = { git = "https://github.com/keep-starknet-strange/alexandria.git" } +alexandria_bytes = { git = "https://github.com/keep-starknet-strange/alexandria.git", rev = "8ddf0243" } +alexandria_math = { git = "https://github.com/keep-starknet-strange/alexandria.git", rev = "8ddf0243" } +succinct_sn = { git = "https://github.com/keep-starknet-strange/succinct-starknet.git" } [[target.starknet-contract]] casm = true sierra = true +build-external-contracts = [ + "succinct_sn::fee_vault::succinct_fee_vault", + "succinct_sn::gateway::succinct_gateway", + "succinct_sn::mocks::erc20_mock::SnakeERC20Mock", +] [cairo] sierra-replace-ids = true diff --git a/src/blobstreamx.cairo b/src/blobstreamx.cairo index 716b13c..6327f55 100644 --- a/src/blobstreamx.cairo +++ b/src/blobstreamx.cairo @@ -4,9 +4,6 @@ mod blobstreamx { use blobstream_sn::interfaces::{ DataRoot, TendermintXErrors, IBlobstreamX, IDAOracle, ITendermintX }; - use blobstream_sn::succinctx::interfaces::{ - ISuccinctGatewayDispatcher, ISuccinctGatewayDispatcherTrait - }; use blobstream_sn::tree::binary::merkle_proof::BinaryMerkleProof; use core::starknet::event::EventEmitter; use core::traits::Into; @@ -14,6 +11,7 @@ mod blobstreamx { use openzeppelin::upgrades::{interface::IUpgradeable, upgradeable::UpgradeableComponent}; use starknet::info::get_block_number; use starknet::{ClassHash, ContractAddress, get_contract_address}; + use succinct_sn::interfaces::{ISuccinctGatewayDispatcher, ISuccinctGatewayDispatcherTrait}; component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); diff --git a/src/lib.cairo b/src/lib.cairo index 6c1ddb3..1012d60 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -6,22 +6,6 @@ mod mocks { mod upgradeable; } -mod succinctx { - mod fee_vault; - mod gateway; - mod interfaces; - mod function_registry { - mod component; - mod erc20_mock; - mod interfaces; - mod mock; - } - #[cfg(test)] - mod tests { - mod test_fee_vault; - } -} - #[cfg(test)] mod tests { mod common; diff --git a/src/mocks/function_verifier.cairo b/src/mocks/function_verifier.cairo index f8f51f7..1648f67 100644 --- a/src/mocks/function_verifier.cairo +++ b/src/mocks/function_verifier.cairo @@ -1,7 +1,7 @@ #[starknet::contract] mod function_verifier_mock { use alexandria_bytes::Bytes; - use blobstream_sn::succinctx::interfaces::IFunctionVerifier; + use succinct_sn::interfaces::IFunctionVerifier; #[storage] struct Storage { diff --git a/src/succinctx/fee_vault.cairo b/src/succinctx/fee_vault.cairo deleted file mode 100644 index a9c8221..0000000 --- a/src/succinctx/fee_vault.cairo +++ /dev/null @@ -1,250 +0,0 @@ -#[starknet::contract] -mod succinct_fee_vault { - use blobstream_sn::succinctx::interfaces::IFeeVault; - use core::starknet::event::EventEmitter; - use openzeppelin::access::ownable::OwnableComponent; - use openzeppelin::access::ownable::ownable::OwnableComponent::InternalTrait; - use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; - use openzeppelin::upgrades::{interface::IUpgradeable, upgradeable::UpgradeableComponent}; - use starknet::{ContractAddress, get_caller_address, ClassHash}; - - component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); - component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); - - #[abi(embed_v0)] - impl OwnableImpl = OwnableComponent::OwnableImpl; - impl OwnableInternalImpl = OwnableComponent::InternalImpl; - impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; - - #[storage] - struct Storage { - // balances[token][account] => token balance for Succinct services. - balances: LegacyMap::<(ContractAddress, ContractAddress), u256>, - allowed_deductors: LegacyMap::, - native_currency_address: ContractAddress, - #[substorage(v0)] - ownable: OwnableComponent::Storage, - #[substorage(v0)] - upgradeable: UpgradeableComponent::Storage, - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - Received: Received, - Deducted: Deducted, - Collected: Collected, - // COMPONENT EVENTS - #[flat] - OwnableEvent: OwnableComponent::Event, - #[flat] - UpgradeableEvent: UpgradeableComponent::Event, - } - - #[derive(Drop, starknet::Event)] - struct Received { - #[key] - account: ContractAddress, - #[key] - token: ContractAddress, - amount: u256 - } - #[derive(Drop, starknet::Event)] - struct Deducted { - #[key] - account: ContractAddress, - #[key] - token: ContractAddress, - amount: u256 - } - - #[derive(Drop, starknet::Event)] - struct Collected { - #[key] - to: ContractAddress, - #[key] - token: ContractAddress, - amount: u256, - } - - - mod Errors { - /// Data commitment for specified block range does not exist - const InvalidAccount: felt252 = 'Invalid account'; - const InvalidToken: felt252 = 'Invalid token'; - const InsufficentAllowance: felt252 = 'Insufficent allowance'; - const OnlyDeductor: felt252 = 'Only deductor allowed'; - const InsufficentBalance: felt252 = 'Insufficent balance'; - } - - #[abi(embed_v0)] - impl Upgradeable of IUpgradeable { - fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { - self.ownable.assert_only_owner(); - self.upgradeable._upgrade(new_class_hash); - } - } - - #[constructor] - fn constructor( - ref self: ContractState, native_currency_address: ContractAddress, owner: ContractAddress - ) { - self.native_currency_address.write(native_currency_address); - self.ownable.initializer(owner); - } - - #[abi(embed_v0)] - impl IFeeVaultImpl of IFeeVault { - /// Get the current native currency address - /// # Returns - /// The native currency address defined. - fn get_native_currency(self: @ContractState) -> ContractAddress { - self.native_currency_address.read() - } - - /// Set the native currency address - /// # Arguments - /// * `_new_native_address`- The new native currency address to be set - fn set_native_currency(ref self: ContractState, _new_native_address: ContractAddress) { - self.ownable.assert_only_owner(); - assert(!_new_native_address.is_zero(), Errors::InvalidToken); - self.native_currency_address.write(_new_native_address); - } - - - /// Check if the specified deductor is allowed to deduct from the vault. - /// # Arguments - /// * `_deductor` - The deductor to check. - /// # Returns - /// True if the deductor is allowed to deduct from the vault, false otherwise. - fn is_deductor(self: @ContractState, _deductor: ContractAddress) -> bool { - self.allowed_deductors.read(_deductor) - } - - /// Add the specified deductor - /// # Arguments - /// * `_deductor` - The address of the deductor to add. - fn add_deductor(ref self: ContractState, _deductor: ContractAddress) { - self.ownable.assert_only_owner(); - self.allowed_deductors.write(_deductor, true); - } - - /// Remove the specified deductor - /// # Arguments - /// * `_deductor` - The address of the deductor to remove. - fn remove_deductor(ref self: ContractState, _deductor: ContractAddress) { - self.ownable.assert_only_owner(); - self.allowed_deductors.write(_deductor, false); - } - - /// Get the balance for a given token and account to use for Succinct services. - /// # Arguments - /// * `_account` - The account to retrieve the balance for. - /// * `_token` - The token address to consider. - /// # Returns - /// The associated balance. - fn get_account_balance( - self: @ContractState, _account: ContractAddress, _token: ContractAddress - ) -> u256 { - self.balances.read((_token, _account)) - } - - /// Deposit the specified amount of native currency from the caller. - /// Dev: the native currency address is defined in the storage slot native_currency - /// Dev: MUST approve this contract to spend at least _amount of the native_currency before calling this. - /// # Arguments - /// * `_account` - The account to deposit the native currency for. - fn deposit_native(ref self: ContractState, _account: ContractAddress) { - let native_currency = self.native_currency_address.read(); - self - .deposit( - _account, native_currency, starknet::info::get_tx_info().unbox().max_fee.into() - ); - } - - /// Deposit the specified amount of the specified token from the caller. - /// Dev: MUST approve this contract to spend at least _amount of _token before calling this. - /// # Arguments - /// * `_account` - The account to deposit the native currency for. - /// * `_token` - The address of the token to deposit. - /// * `_amount` - The amoun to deposit. - fn deposit( - ref self: ContractState, - _account: ContractAddress, - _token: ContractAddress, - _amount: u256 - ) { - let caller_address = get_caller_address(); - let contract_address = starknet::info::get_contract_address(); - assert(!_account.is_zero(), Errors::InvalidAccount); - assert(!_token.is_zero(), Errors::InvalidToken); - let erc20_dispatcher = IERC20Dispatcher { contract_address: _token }; - let allowance = erc20_dispatcher.allowance(caller_address, contract_address); - assert(allowance >= _amount, Errors::InsufficentAllowance); - erc20_dispatcher.transfer_from(caller_address, contract_address, _amount); - let current_balance = self.balances.read((_token, _account)); - self.balances.write((_token, _account), current_balance + _amount); - self.emit(Received { account: _account, token: _token, amount: _amount }); - } - - /// Deduct the specified amount of native currency from the specified account. - /// # Arguments - /// * `_account` - The account to deduct the native currency from. - fn deduct_native(ref self: ContractState, _account: ContractAddress) { - let native_currency = self.native_currency_address.read(); - self - .deduct( - _account, native_currency, starknet::info::get_tx_info().unbox().max_fee.into() - ); - } - - /// Deduct the specified amount of native currency from the specified account. - /// # Arguments - /// * `_account` - The account to deduct the native currency from. - /// * `_token` - The address of the token to deduct. - /// * `_amount` - The amount of the token to deduct. - fn deduct( - ref self: ContractState, - _account: ContractAddress, - _token: ContractAddress, - _amount: u256 - ) { - let caller_address = get_caller_address(); - assert(self.is_deductor(caller_address), Errors::OnlyDeductor); - assert(!_account.is_zero(), Errors::InvalidAccount); - assert(!_token.is_zero(), Errors::InvalidToken); - let current_balance = self.balances.read((_token, _account)); - assert(current_balance >= _amount, Errors::InsufficentBalance); - self.balances.write((_token, _account), current_balance - _amount); - self.emit(Deducted { account: _account, token: _token, amount: _amount }); - } - - /// Collect the specified amount of native currency. - /// * `_to`- The address to send the collected native currency to. - /// * `_amount`- The amount of native currency to collect. - fn collect_native(ref self: ContractState, _to: ContractAddress, _amount: u256) { - self.ownable.assert_only_owner(); - let native_currency = self.native_currency_address.read(); - self.collect(_to, native_currency, _amount); - } - - /// Collect the specified amount of the specified token. - /// * `_to`- The address to send the collected tokens to. - /// * `_token` - The address of the token to collect. - /// * `_amount`- The amount of the token to collect. - fn collect( - ref self: ContractState, _to: ContractAddress, _token: ContractAddress, _amount: u256 - ) { - self.ownable.assert_only_owner(); - let contract_address = starknet::info::get_contract_address(); - assert(!_token.is_zero(), Errors::InvalidToken); - let erc20_dispatcher = IERC20Dispatcher { contract_address: _token }; - assert( - erc20_dispatcher.balance_of(contract_address) >= _amount, Errors::InsufficentBalance - ); - erc20_dispatcher.transfer(_to, _amount); - self.emit(Collected { to: _to, token: _token, amount: _amount }) - } - } -} - diff --git a/src/succinctx/function_registry/component.cairo b/src/succinctx/function_registry/component.cairo deleted file mode 100644 index b5b56b9..0000000 --- a/src/succinctx/function_registry/component.cairo +++ /dev/null @@ -1,104 +0,0 @@ -mod errors { - const EMPTY_BYTECODE: felt252 = 'EMPTY_BYTECODE'; - const FAILED_DEPLOY: felt252 = 'FAILED_DEPLOY'; - const VERIFIER_CANNOT_BE_ZERO: felt252 = 'VERIFIER_CANNOT_BE_ZERO'; - const VERIFIER_ALREADY_UPDATED: felt252 = 'VERIFIER_ALREADY_UPDATED'; - const FUNCTION_ALREADY_REGISTERED: felt252 = 'FUNCTION_ALREADY_REGISTERED'; - const NOT_FUNCTION_OWNER: felt252 = 'NOT_FUNCTION_OWNER'; -} - -#[starknet::component] -mod function_registry_cpt { - use alexandria_bytes::{Bytes, BytesTrait}; - use blobstream_sn::succinctx::function_registry::interfaces::IFunctionRegistry; - use core::traits::Into; - use starknet::info::get_caller_address; - use starknet::{ContractAddress, contract_address_const}; - use super::errors; - - #[storage] - struct Storage { - verifiers: LegacyMap, - verifier_owners: LegacyMap, - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - FunctionRegistered: FunctionRegistered, - FunctionVerifierUpdated: FunctionVerifierUpdated, - } - - #[derive(Drop, starknet::Event)] - struct FunctionRegistered { - #[key] - function_id: u256, - verifier: ContractAddress, - name: felt252, - owner: ContractAddress, - } - - #[derive(Drop, starknet::Event)] - struct FunctionVerifierUpdated { - #[key] - function_id: u256, - verifier: ContractAddress, - } - - #[embeddable_as(FunctionRegistryImpl)] - impl FunctionRegistry< - TContractState, +HasComponent - > of IFunctionRegistry> { - fn get_verifier( - self: @ComponentState, function_id: u256 - ) -> ContractAddress { - self.verifiers.read(function_id) - } - fn get_verifier_owner( - self: @ComponentState, function_id: u256 - ) -> ContractAddress { - self.verifier_owners.read(function_id) - } - fn get_function_id( - self: @ComponentState, owner: ContractAddress, name: felt252 - ) -> u256 { - let mut function_id_digest = BytesTrait::new_empty(); - function_id_digest.append_felt252(owner.into()); - function_id_digest.append_felt252(name); - function_id_digest.keccak() - } - fn register_function( - ref self: ComponentState, - owner: ContractAddress, - verifier: ContractAddress, - name: felt252 - ) -> u256 { - assert(verifier.is_non_zero(), errors::VERIFIER_CANNOT_BE_ZERO); - - let function_id = self.get_function_id(owner, name); - assert(self.verifiers.read(function_id).is_zero(), errors::FUNCTION_ALREADY_REGISTERED); - - self.verifier_owners.write(function_id, owner); - self.verifiers.write(function_id, verifier); - - self.emit(FunctionRegistered { function_id, verifier, name, owner, }); - - function_id - } - fn update_function( - ref self: ComponentState, verifier: ContractAddress, name: felt252 - ) -> u256 { - assert(verifier.is_non_zero(), errors::VERIFIER_CANNOT_BE_ZERO); - - let caller = get_caller_address(); - let function_id = self.get_function_id(caller, name); - assert(self.verifier_owners.read(function_id) != caller, errors::NOT_FUNCTION_OWNER); - assert(self.verifiers.read(function_id) == verifier, errors::VERIFIER_ALREADY_UPDATED); - - self.verifiers.write(function_id, verifier); - self.emit(FunctionVerifierUpdated { function_id, verifier }); - - function_id - } - } -} diff --git a/src/succinctx/function_registry/erc20_mock.cairo b/src/succinctx/function_registry/erc20_mock.cairo deleted file mode 100644 index e904409..0000000 --- a/src/succinctx/function_registry/erc20_mock.cairo +++ /dev/null @@ -1,81 +0,0 @@ -use starknet::ContractAddress; - -#[starknet::interface] -trait IMockERC20 { - fn mint_to(ref self: TContractState, recipient: ContractAddress, amount: u256); - fn approve(ref self: TContractState, spender: ContractAddress, amount: u256) -> bool; - fn transfer_from( - ref self: TContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256 - ) -> bool; - fn transfer(ref self: TContractState, recipient: ContractAddress, amount: u256) -> bool; - fn balance_of(self: @TContractState, account: ContractAddress) -> u256; - fn allowance(self: @TContractState, owner: ContractAddress, spender: ContractAddress) -> u256; -} - -#[starknet::contract] -mod MockERC20 { - use openzeppelin::token::erc20::ERC20Component; - use starknet::{ContractAddress, get_caller_address}; - use super::IMockERC20; - - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - - impl ERC20Impl = ERC20Component::ERC20Impl; - impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl; - impl ERC20CamelOnlyImpl = ERC20Component::ERC20CamelOnlyImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - erc20: ERC20Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC20Event: ERC20Component::Event - } - - #[constructor] - fn constructor(ref self: ContractState, name: felt252, symbol: felt252) { - self.erc20.initializer(name, symbol); - } - - #[abi(embed_v0)] - impl IMockERC20Impl of IMockERC20 { - fn mint_to(ref self: ContractState, recipient: ContractAddress, amount: u256) { - self.erc20._mint(recipient, amount); - } - - fn approve(ref self: ContractState, spender: ContractAddress, amount: u256) -> bool { - let caller = get_caller_address(); - self.erc20._approve(caller, spender, amount); - true - } - - fn transfer_from( - ref self: ContractState, - sender: ContractAddress, - recipient: ContractAddress, - amount: u256 - ) -> bool { - self.erc20.transfer_from(sender, recipient, amount) - } - - fn transfer(ref self: ContractState, recipient: ContractAddress, amount: u256) -> bool { - self.erc20.transfer(recipient, amount) - } - - fn balance_of(self: @ContractState, account: ContractAddress) -> u256 { - self.erc20.balance_of(account) - } - - fn allowance( - self: @ContractState, owner: ContractAddress, spender: ContractAddress - ) -> u256 { - self.erc20.allowance(owner, spender) - } - } -} diff --git a/src/succinctx/function_registry/interfaces.cairo b/src/succinctx/function_registry/interfaces.cairo deleted file mode 100644 index bf626a2..0000000 --- a/src/succinctx/function_registry/interfaces.cairo +++ /dev/null @@ -1,12 +0,0 @@ -use starknet::ContractAddress; - -#[starknet::interface] -trait IFunctionRegistry { - fn get_verifier(self: @TContractState, function_id: u256) -> ContractAddress; - fn get_verifier_owner(self: @TContractState, function_id: u256) -> ContractAddress; - fn get_function_id(self: @TContractState, owner: ContractAddress, name: felt252) -> u256; - fn register_function( - ref self: TContractState, owner: ContractAddress, verifier: ContractAddress, name: felt252 - ) -> u256; - fn update_function(ref self: TContractState, verifier: ContractAddress, name: felt252) -> u256; -} diff --git a/src/succinctx/function_registry/mock.cairo b/src/succinctx/function_registry/mock.cairo deleted file mode 100644 index 7d7eb4d..0000000 --- a/src/succinctx/function_registry/mock.cairo +++ /dev/null @@ -1,27 +0,0 @@ -#[starknet::contract] -mod function_registry_mock { - use blobstream_sn::succinctx::function_registry::component::function_registry_cpt; - use blobstream_sn::succinctx::function_registry::interfaces::IFunctionRegistry; - use starknet::ContractAddress; - - component!( - path: function_registry_cpt, storage: function_registry, event: FunctionRegistryEvent - ); - - #[abi(embed_v0)] - impl FunctionRegistryImpl = - function_registry_cpt::FunctionRegistryImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - function_registry: function_registry_cpt::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - FunctionRegistryEvent: function_registry_cpt::Event - } -} diff --git a/src/succinctx/gateway.cairo b/src/succinctx/gateway.cairo deleted file mode 100644 index b94ba05..0000000 --- a/src/succinctx/gateway.cairo +++ /dev/null @@ -1,358 +0,0 @@ -#[starknet::contract] -mod succinct_gateway { - use alexandria_bytes::{Bytes, BytesTrait}; - use blobstream_sn::succinctx::function_registry::component::function_registry_cpt; - use blobstream_sn::succinctx::function_registry::interfaces::IFunctionRegistry; - use blobstream_sn::succinctx::interfaces::{ - ISuccinctGateway, IFunctionVerifierDispatcher, IFunctionVerifierDispatcherTrait, - IFeeVaultDispatcher, IFeeVaultDispatcherTrait - }; - use core::array::SpanTrait; - use openzeppelin::access::ownable::{OwnableComponent as ownable_cpt, interface::IOwnable}; - use openzeppelin::security::reentrancyguard::ReentrancyGuardComponent; - use starknet::{ContractAddress, SyscallResultTrait, syscalls::call_contract_syscall}; - - component!( - path: function_registry_cpt, storage: function_registry, event: FunctionRegistryEvent - ); - component!(path: ownable_cpt, storage: ownable, event: OwnableEvent); - component!( - path: ReentrancyGuardComponent, storage: reentrancy_guard, event: ReentrancyGuardEvent - ); - - impl ReentrancyGuardInternalImpl = ReentrancyGuardComponent::InternalImpl; - #[abi(embed_v0)] - impl FunctionRegistryImpl = - function_registry_cpt::FunctionRegistryImpl; - #[abi(embed_v0)] - impl OwnableImpl = ownable_cpt::OwnableImpl; - impl OwnableInternalImpl = ownable_cpt::InternalImpl; - - #[storage] - struct Storage { - allowed_provers: LegacyMap, - is_callback: bool, - nonce: u32, - requests: LegacyMap, - verified_function_id: u256, - verified_input_hash: u256, - verified_output: (u256, u256), - fee_vault_address: ContractAddress, - #[substorage(v0)] - function_registry: function_registry_cpt::Storage, - #[substorage(v0)] - ownable: ownable_cpt::Storage, - #[substorage(v0)] - reentrancy_guard: ReentrancyGuardComponent::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - RequestCall: RequestCall, - RequestCallback: RequestCallback, - RequestFulfilled: RequestFulfilled, - Call: Call, - #[flat] - FunctionRegistryEvent: function_registry_cpt::Event, - #[flat] - OwnableEvent: ownable_cpt::Event, - #[flat] - ReentrancyGuardEvent: ReentrancyGuardComponent::Event - } - - #[derive(Drop, starknet::Event)] - struct RequestCall { - #[key] - function_id: u256, - input: Bytes, - entry_address: ContractAddress, - entry_calldata: Bytes, - entry_gas_limit: u32, - sender: ContractAddress, - fee_amount: u128, - } - - #[derive(Drop, starknet::Event)] - struct RequestCallback { - #[key] - nonce: u32, - #[key] - function_id: u256, - input: Bytes, - context: Bytes, - callback_addr: ContractAddress, - callback_selector: felt252, - callback_gas_limit: u32, - fee_amount: u128, - } - - #[derive(Drop, starknet::Event)] - struct RequestFulfilled { - #[key] - nonce: u32, - #[key] - function_id: u256, - input_hash: u256, - output_hash: u256, - } - - #[derive(Drop, starknet::Event)] - struct Call { - #[key] - function_id: u256, - input_hash: u256, - output_hash: u256, - } - - mod Errors { - const INVALID_CALL: felt252 = 'Invalid call to verify'; - const INVALID_REQUEST: felt252 = 'Invalid request for fullfilment'; - const INVALID_PROOF: felt252 = 'Invalid proof provided'; - const FEE_VAULT_NOT_INITIALIZED: felt252 = 'Fee vault not initialized'; - } - - #[constructor] - fn constructor( - ref self: ContractState, owner: ContractAddress, fee_vault_address: ContractAddress - ) { - self.ownable.initializer(owner); - self.fee_vault_address.write(fee_vault_address); - } - - #[abi(embed_v0)] - impl ISuccinctGatewayImpl of ISuccinctGateway { - /// Creates a onchain request for a proof. The output and proof is fulfilled asynchronously - /// by the provided callback. - /// - /// # Arguments - /// - /// * `function_id` - The function identifier. - /// * `input` - The function input. - /// * `context` - The function context. - /// * `callback_selector` - The selector of the callback function. - /// * `callback_gas_limit` - The gas limit for the callback function. - fn request_callback( - ref self: ContractState, - function_id: u256, - input: Bytes, - context: Bytes, - callback_selector: felt252, - callback_gas_limit: u32, - ) -> u256 { - let nonce = self.nonce.read(); - let callback_addr = starknet::info::get_caller_address(); - let request_hash = InternalImpl::_request_hash( - nonce, - function_id, - input.sha256(), - context.keccak(), - callback_addr, - callback_selector, - callback_gas_limit - ); - self.requests.write(nonce, request_hash); - self - .emit( - RequestCallback { - nonce, - function_id, - input, - context, - callback_addr, - callback_selector, - callback_gas_limit, - fee_amount: starknet::info::get_tx_info().unbox().max_fee, - } - ); - - self.nonce.write(nonce + 1); - - // Fee Vault - - let fee_vault_address = self.fee_vault_address.read(); - assert(!fee_vault_address.is_zero(), Errors::FEE_VAULT_NOT_INITIALIZED); - let fee_vault = IFeeVaultDispatcher { contract_address: fee_vault_address }; - fee_vault.deposit_native(callback_addr); - - request_hash - } - /// Creates a proof request for a call. Equivalent to an off-chain request through an API. - /// - /// # Arguments - /// - /// * `function_id` - The function identifier. - /// * `input` - The function input. - /// * `entry_address` - The address of the callback contract. - /// * `entry_calldata` - The entry calldata for the call. - /// * `entry_gas_limit` - The gas limit for the call. - fn request_call( - ref self: ContractState, - function_id: u256, - input: Bytes, - entry_address: ContractAddress, - entry_calldata: Bytes, - entry_gas_limit: u32 - ) { - self - .emit( - RequestCall { - function_id, - input, - entry_address, - entry_calldata, - entry_gas_limit, - sender: starknet::info::get_caller_address(), - fee_amount: starknet::info::get_tx_info().unbox().max_fee, - } - ); - - // Fee Vault - - let fee_vault_address = self.fee_vault_address.read(); - assert(!fee_vault_address.is_zero(), Errors::FEE_VAULT_NOT_INITIALIZED); - let fee_vault = IFeeVaultDispatcher { contract_address: fee_vault_address }; - fee_vault.deposit_native(starknet::info::get_caller_address()); - } - - /// If the call matches the currently verified function, returns the output. - /// Else this function reverts. - /// - /// # Arguments - /// * `function_id` The function identifier. - /// * `input` The function input. - fn verified_call(self: @ContractState, function_id: u256, input: Bytes) -> (u256, u256) { - assert(self.verified_function_id.read() == function_id, Errors::INVALID_CALL); - assert(self.verified_input_hash.read() == input.sha256(), Errors::INVALID_CALL); - - self.verified_output.read() - } - - fn fulfill_callback( - ref self: ContractState, - nonce: u32, - function_id: u256, - input_hash: u256, - callback_addr: ContractAddress, - callback_selector: felt252, - callback_calldata: Span, - callback_gas_limit: u32, - context: Bytes, - output: Bytes, - proof: Bytes - ) { - self.reentrancy_guard.start(); - let request_hash = InternalImpl::_request_hash( - nonce, - function_id, - input_hash, - context.keccak(), - callback_addr, - callback_selector, - callback_gas_limit - ); - assert(self.requests.read(nonce) != request_hash, Errors::INVALID_REQUEST); - - let output_hash = output.sha256(); - - let verifier = self.function_registry.verifiers.read(function_id); - let is_valid_proof: bool = IFunctionVerifierDispatcher { contract_address: verifier } - .verify(input_hash, output_hash, proof); - assert(is_valid_proof, Errors::INVALID_PROOF); - - self.is_callback.write(true); - call_contract_syscall( - address: callback_addr, - entry_point_selector: callback_selector, - calldata: callback_calldata - ) - .unwrap_syscall(); - self.is_callback.write(false); - - self.emit(RequestFulfilled { nonce, function_id, input_hash, output_hash, }); - - self.reentrancy_guard.end(); - } - - fn fulfill_call( - ref self: ContractState, - function_id: u256, - input: Bytes, - output: Bytes, - proof: Bytes, - callback_addr: ContractAddress, - callback_selector: felt252, - callback_calldata: Span, - ) { - self.reentrancy_guard.start(); - - let input_hash = input.sha256(); - let output_hash = output.sha256(); - - let verifier = self.function_registry.verifiers.read(function_id); - - let is_valid_proof: bool = IFunctionVerifierDispatcher { contract_address: verifier } - .verify(input_hash, output_hash, proof); - assert(is_valid_proof, Errors::INVALID_PROOF); - - // Set the current verified call. - self.verified_function_id.write(function_id); - self.verified_input_hash.write(input_hash); - - // TODO: make generic after refactor - let (offset, data_commitment) = output.read_u256(0); - let (_, next_header) = output.read_u256(offset); - self.verified_output.write((data_commitment, next_header)); - - call_contract_syscall( - address: callback_addr, - entry_point_selector: callback_selector, - calldata: callback_calldata - ) - .unwrap_syscall(); - - // reset current verified call - self.verified_function_id.write(0); - self.verified_input_hash.write(0); - self.verified_output.write((0, 0)); - - self.emit(Call { function_id, input_hash, output_hash, }); - - self.reentrancy_guard.end(); - } - } - - #[generate_trait] - impl InternalImpl of InternalTrait { - /// Computes a unique identifier for a request. - /// - /// # Arguments - /// - /// * `nonce` The contract nonce. - /// * `function_id` The function identifier. - /// * `input_hash` The hash of the function input. - /// * `context_hash` The hash of the function context. - /// * `callback_address` The address of the callback contract. - /// * `callback_selector` The selector of the callback function. - /// * `callback_gas_limit` The gas limit for the callback function. - fn _request_hash( - nonce: u32, - function_id: u256, - input_hash: u256, - context_hash: u256, - callback_addr: ContractAddress, - callback_selector: felt252, - callback_gas_limit: u32 - ) -> u256 { - let mut packed_req = BytesTrait::new_empty(); - packed_req.append_u32(nonce); - packed_req.append_u256(function_id); - packed_req.append_u256(input_hash); - packed_req.append_u256(context_hash); - packed_req.append_felt252(callback_addr.into()); - packed_req.append_felt252(callback_selector); - packed_req.append_u32(callback_gas_limit); - packed_req.keccak() - } - } -} diff --git a/src/succinctx/interfaces.cairo b/src/succinctx/interfaces.cairo deleted file mode 100644 index dc1c066..0000000 --- a/src/succinctx/interfaces.cairo +++ /dev/null @@ -1,77 +0,0 @@ -use alexandria_bytes::{Bytes, BytesTrait}; -use starknet::ContractAddress; - -#[starknet::interface] -trait IFunctionVerifier { - fn verify(self: @TContractState, input_hash: u256, output_hash: u256, proof: Bytes) -> bool; - fn verification_key_hash(self: @TContractState) -> u256; -} - -#[starknet::interface] -trait ISuccinctGateway { - fn request_callback( - ref self: TContractState, - function_id: u256, - input: Bytes, - context: Bytes, - callback_selector: felt252, - callback_gas_limit: u32, - ) -> u256; - fn request_call( - ref self: TContractState, - function_id: u256, - input: Bytes, - entry_address: ContractAddress, - entry_calldata: Bytes, - entry_gas_limit: u32 - ); - fn verified_call(self: @TContractState, function_id: u256, input: Bytes) -> (u256, u256); - fn fulfill_callback( - ref self: TContractState, - nonce: u32, - function_id: u256, - input_hash: u256, - callback_addr: ContractAddress, - callback_selector: felt252, - callback_calldata: Span, - callback_gas_limit: u32, - context: Bytes, - output: Bytes, - proof: Bytes - ); - fn fulfill_call( - ref self: TContractState, - function_id: u256, - input: Bytes, - output: Bytes, - proof: Bytes, - callback_addr: ContractAddress, - callback_selector: felt252, - callback_calldata: Span, - ); -} - - -#[starknet::interface] -trait IFeeVault { - fn get_native_currency(self: @TContractState) -> ContractAddress; - fn set_native_currency(ref self: TContractState, _new_native_address: ContractAddress); - fn is_deductor(self: @TContractState, _deductor: ContractAddress) -> bool; - fn add_deductor(ref self: TContractState, _deductor: ContractAddress); - fn remove_deductor(ref self: TContractState, _deductor: ContractAddress); - fn get_account_balance( - self: @TContractState, _account: ContractAddress, _token: ContractAddress - ) -> u256; - fn deposit_native(ref self: TContractState, _account: ContractAddress); - fn deposit( - ref self: TContractState, _account: ContractAddress, _token: ContractAddress, _amount: u256 - ); - fn deduct_native(ref self: TContractState, _account: ContractAddress); - fn deduct( - ref self: TContractState, _account: ContractAddress, _token: ContractAddress, _amount: u256 - ); - fn collect_native(ref self: TContractState, _to: ContractAddress, _amount: u256); - fn collect( - ref self: TContractState, _to: ContractAddress, _token: ContractAddress, _amount: u256 - ); -} diff --git a/src/succinctx/tests/test_fee_vault.cairo b/src/succinctx/tests/test_fee_vault.cairo deleted file mode 100644 index cac1b41..0000000 --- a/src/succinctx/tests/test_fee_vault.cairo +++ /dev/null @@ -1,306 +0,0 @@ -use blobstream_sn::succinctx::fee_vault::succinct_fee_vault; -use blobstream_sn::succinctx::function_registry::erc20_mock::{ - IMockERC20Dispatcher, IMockERC20DispatcherTrait, MockERC20 -}; -use blobstream_sn::succinctx::interfaces::{IFeeVaultDispatcher, IFeeVaultDispatcherTrait}; -use debug::PrintTrait; -use openzeppelin::tests::utils::constants::{OWNER, NEW_OWNER, SPENDER}; -use snforge_std as snf; -use snforge_std::{ContractClassTrait, CheatTarget, SpyOn, EventSpy}; -use starknet::ContractAddress; -use starknet::contract_address_const; -use starknet::get_caller_address; -const TOTAL_SUPPPLY: u256 = 0x100000000000000000000000000000001; - - -fn setup_contracts() -> (IMockERC20Dispatcher, IFeeVaultDispatcher) { - let token_class = snf::declare('MockERC20'); - let token_calldata = array!['FeeToken', 'FT']; - let token_address = token_class.deploy(@token_calldata).unwrap(); - let fee_vault_class = snf::declare('succinct_fee_vault'); - let fee_calldata = array![token_address.into(), OWNER().into()]; - let fee_vault_address = fee_vault_class.deploy(@fee_calldata).unwrap(); - ( - IMockERC20Dispatcher { contract_address: token_address }, - IFeeVaultDispatcher { contract_address: fee_vault_address } - ) -} - -#[test] -fn fee_vault_set_native_currency_address() { - let (_, fee_vault) = setup_contracts(); - let new_currency_address = contract_address_const::<0x12345>(); - snf::start_prank(CheatTarget::One(fee_vault.contract_address), OWNER()); - fee_vault.set_native_currency(new_currency_address); - assert( - fee_vault.get_native_currency() == new_currency_address, 'wrong initial native currency' - ); - snf::stop_prank(CheatTarget::One(fee_vault.contract_address)); -} - -#[test] -#[should_panic(expected: ('Invalid token',))] -fn fee_vault_set_native_currency_address_fails_null_address() { - let (_, fee_vault) = setup_contracts(); - snf::start_prank(CheatTarget::One(fee_vault.contract_address), OWNER()); - let new_currency_address = contract_address_const::<0>(); - fee_vault.set_native_currency(new_currency_address); - snf::stop_prank(CheatTarget::One(fee_vault.contract_address)); -} - -#[test] -#[should_panic(expected: ('Caller is not the owner',))] -fn test_vault_set_native_currency_fails_not_owner() { - let (_, fee_vault) = setup_contracts(); - let new_currency_address = contract_address_const::<0x1234>(); - fee_vault.set_native_currency(new_currency_address); -} - -#[test] -fn fee_vault_deductor_operations() { - let (_, fee_vault) = setup_contracts(); - snf::start_prank(CheatTarget::One(fee_vault.contract_address), OWNER()); - // Adding a new deductor - fee_vault.add_deductor(SPENDER()); - assert(fee_vault.is_deductor(SPENDER()), 'deductors not updated'); - - // Removing the same deductor - fee_vault.remove_deductor(SPENDER()); - assert(!fee_vault.is_deductor(SPENDER()), 'deductors not updated'); - snf::stop_prank(CheatTarget::One(fee_vault.contract_address)); -} - - -#[test] -#[should_panic(expected: ('Caller is not the owner',))] -fn fee_vault_add_deductor_fails_if_not_owner() { - let (_, fee_vault) = setup_contracts(); - // Adding a new deductor - fee_vault.add_deductor(SPENDER()); - assert(fee_vault.is_deductor(SPENDER()), 'deductors not updated'); -} - - -#[test] -#[should_panic(expected: ('Caller is not the owner',))] -fn fee_vault_remove_deductor_fails_if_not_owner() { - let (_, fee_vault) = setup_contracts(); - // Adding a new deductor - fee_vault.remove_deductor(SPENDER()); - assert(!fee_vault.is_deductor(SPENDER()), 'deductors not updated'); -} - -#[test] -fn fee_vault_deposit_native() { - let (erc20, fee_vault) = setup_contracts(); - erc20.mint_to(SPENDER(), 0x10000); - snf::start_prank(CheatTarget::One(erc20.contract_address), SPENDER()); - let fee = starknet::info::get_tx_info().unbox().max_fee.into(); - erc20.approve(fee_vault.contract_address, fee); - snf::stop_prank(CheatTarget::One(erc20.contract_address)); - snf::start_prank(CheatTarget::One(fee_vault.contract_address), SPENDER()); - fee_vault.deposit_native(SPENDER()); - assert( - fee_vault.get_account_balance(SPENDER(), erc20.contract_address) == fee, - 'balances not updated' - ); - snf::stop_prank(CheatTarget::One(fee_vault.contract_address)); -} - -#[test] -fn fee_vault_deposit() { - let (erc20, fee_vault) = setup_contracts(); - erc20.mint_to(SPENDER(), 0x10000); - snf::start_prank(CheatTarget::One(erc20.contract_address), SPENDER()); - erc20.approve(fee_vault.contract_address, 0x10000); - snf::stop_prank(CheatTarget::One(erc20.contract_address)); - snf::start_prank(CheatTarget::One(fee_vault.contract_address), SPENDER()); - fee_vault.deposit(SPENDER(), erc20.contract_address, 0x10000); - assert( - fee_vault.get_account_balance(SPENDER(), erc20.contract_address) == 0x10000, - 'balances deposit not updated' - ); - snf::stop_prank(CheatTarget::One(fee_vault.contract_address)); -} - -#[test] -#[should_panic(expected: ('Invalid account',))] -fn fee_vault_deposit_fails_if_null_account() { - let (erc20, fee_vault) = setup_contracts(); - fee_vault.deposit(contract_address_const::<0>(), erc20.contract_address, 0x10000); -} - -#[test] -#[should_panic(expected: ('Invalid token',))] -fn fee_vault_deposit_fails_if_null_token() { - let (_, fee_vault) = setup_contracts(); - fee_vault.deposit(SPENDER(), contract_address_const::<0>(), 0x10000); -} - -#[test] -#[should_panic(expected: ('Insufficent allowance',))] -fn fee_vault_deposit_fails_if_insufficent_allowance() { - let (erc20, fee_vault) = setup_contracts(); - erc20.mint_to(SPENDER(), 0x10000); - snf::start_prank(CheatTarget::One(fee_vault.contract_address), SPENDER()); - fee_vault.deposit(SPENDER(), erc20.contract_address, 0x10000); - assert( - fee_vault.get_account_balance(SPENDER(), erc20.contract_address) == 0x10000, - 'balances deposit not updated' - ); - snf::stop_prank(CheatTarget::One(fee_vault.contract_address)); -} - -#[test] -fn fee_vault_deduct_native() { - let (erc20, fee_vault) = setup_contracts(); - let fee = starknet::info::get_tx_info().unbox().max_fee.into(); - snf::start_prank(CheatTarget::One(fee_vault.contract_address), OWNER()); - fee_vault.add_deductor(SPENDER()); - snf::stop_prank(CheatTarget::One(fee_vault.contract_address)); - erc20.mint_to(SPENDER(), 0x10000); - snf::start_prank(CheatTarget::One(erc20.contract_address), SPENDER()); - erc20.approve(fee_vault.contract_address, 0x10000); - snf::stop_prank(CheatTarget::One(erc20.contract_address)); - snf::start_prank(CheatTarget::One(fee_vault.contract_address), SPENDER()); - fee_vault.deposit_native(SPENDER()); - assert( - fee_vault.get_account_balance(SPENDER(), erc20.contract_address) == fee, - 'balances deposit not updated' - ); - fee_vault.deduct_native(SPENDER()); - assert( - fee_vault.get_account_balance(SPENDER(), erc20.contract_address) == 0, - 'balances deduct not updated' - ); - snf::stop_prank(CheatTarget::One(fee_vault.contract_address)); -} - -#[test] -fn fee_vault_deduct() { - let (erc20, fee_vault) = setup_contracts(); - snf::start_prank(CheatTarget::One(fee_vault.contract_address), OWNER()); - fee_vault.add_deductor(SPENDER()); - snf::stop_prank(CheatTarget::One(fee_vault.contract_address)); - erc20.mint_to(SPENDER(), 0x10000); - snf::start_prank(CheatTarget::One(erc20.contract_address), SPENDER()); - erc20.approve(fee_vault.contract_address, 0x10000); - snf::stop_prank(CheatTarget::One(erc20.contract_address)); - snf::start_prank(CheatTarget::One(fee_vault.contract_address), SPENDER()); - fee_vault.deposit(SPENDER(), erc20.contract_address, 0x10000); - fee_vault.deduct(SPENDER(), erc20.contract_address, 0x8000); - assert( - fee_vault.get_account_balance(SPENDER(), erc20.contract_address) == 0x10000 - 0x8000, - 'balances deduct not updated' - ); - snf::stop_prank(CheatTarget::One(fee_vault.contract_address)); -} - -#[test] -#[should_panic(expected: ('Only deductor allowed',))] -fn fee_vault_deduct_fails_if_not_deductor() { - let (erc20, fee_vault) = setup_contracts(); - erc20.mint_to(SPENDER(), 0x10000); - snf::start_prank(CheatTarget::One(erc20.contract_address), SPENDER()); - erc20.approve(fee_vault.contract_address, 0x10000); - snf::stop_prank(CheatTarget::One(erc20.contract_address)); - snf::start_prank(CheatTarget::One(fee_vault.contract_address), SPENDER()); - fee_vault.deposit(SPENDER(), erc20.contract_address, 0x10000); - fee_vault.deduct(SPENDER(), erc20.contract_address, 0x8000); - snf::stop_prank(CheatTarget::One(fee_vault.contract_address)); -} - -#[test] -#[should_panic(expected: ('Insufficent balance',))] -fn fee_vault_deduct_fails_if_insufficent_balance() { - let (erc20, fee_vault) = setup_contracts(); - snf::start_prank(CheatTarget::One(fee_vault.contract_address), OWNER()); - fee_vault.add_deductor(SPENDER()); - snf::stop_prank(CheatTarget::One(fee_vault.contract_address)); - erc20.mint_to(SPENDER(), 0x10000); - snf::start_prank(CheatTarget::One(erc20.contract_address), SPENDER()); - erc20.approve(fee_vault.contract_address, 0x10000); - snf::stop_prank(CheatTarget::One(erc20.contract_address)); - snf::start_prank(CheatTarget::One(fee_vault.contract_address), SPENDER()); - fee_vault.deposit(SPENDER(), erc20.contract_address, 0x1000); - fee_vault.deduct(SPENDER(), erc20.contract_address, 0x8000); - snf::stop_prank(CheatTarget::One(fee_vault.contract_address)); -} - - -#[test] -fn fee_vault_collect() { - let (erc20, fee_vault) = setup_contracts(); - snf::start_prank(CheatTarget::One(fee_vault.contract_address), OWNER()); - fee_vault.add_deductor(SPENDER()); - snf::stop_prank(CheatTarget::One(fee_vault.contract_address)); - erc20.mint_to(SPENDER(), 0x10000); - snf::start_prank(CheatTarget::One(erc20.contract_address), SPENDER()); - erc20.approve(fee_vault.contract_address, 0x10000); - snf::stop_prank(CheatTarget::One(erc20.contract_address)); - snf::start_prank(CheatTarget::One(fee_vault.contract_address), SPENDER()); - fee_vault.deposit(SPENDER(), erc20.contract_address, 0x10000); - fee_vault.deduct(SPENDER(), erc20.contract_address, 0x8000); - snf::stop_prank(CheatTarget::One(fee_vault.contract_address)); - snf::start_prank(CheatTarget::One(fee_vault.contract_address), OWNER()); - fee_vault.collect(OWNER(), erc20.contract_address, 0x10000); - assert(erc20.balance_of(OWNER()) == 0x10000, 'balance collect failed'); - snf::stop_prank(CheatTarget::One(fee_vault.contract_address)); -} - - -#[test] -fn fee_vault_collect_native() { - let (erc20, fee_vault) = setup_contracts(); - snf::start_prank(CheatTarget::One(fee_vault.contract_address), OWNER()); - fee_vault.add_deductor(SPENDER()); - snf::stop_prank(CheatTarget::One(fee_vault.contract_address)); - erc20.mint_to(SPENDER(), 0x10000); - snf::start_prank(CheatTarget::One(erc20.contract_address), SPENDER()); - erc20.approve(fee_vault.contract_address, 0x10000); - snf::stop_prank(CheatTarget::One(erc20.contract_address)); - snf::start_prank(CheatTarget::One(fee_vault.contract_address), SPENDER()); - fee_vault.deposit(SPENDER(), erc20.contract_address, 0x10000); - fee_vault.deduct(SPENDER(), erc20.contract_address, 0x8000); - snf::stop_prank(CheatTarget::One(fee_vault.contract_address)); - snf::start_prank(CheatTarget::One(fee_vault.contract_address), OWNER()); - fee_vault.collect_native(OWNER(), 0x10000); - assert(erc20.balance_of(OWNER()) == 0x10000, 'balance collect failed'); - snf::stop_prank(CheatTarget::One(fee_vault.contract_address)); -} - -#[test] -#[should_panic(expected: ('Caller is not the owner',))] -fn fee_vault_collect_fails_if_not_owner() { - let (erc20, fee_vault) = setup_contracts(); - snf::start_prank(CheatTarget::One(fee_vault.contract_address), OWNER()); - fee_vault.add_deductor(SPENDER()); - snf::stop_prank(CheatTarget::One(fee_vault.contract_address)); - erc20.mint_to(SPENDER(), 0x10000); - snf::start_prank(CheatTarget::One(erc20.contract_address), SPENDER()); - erc20.approve(fee_vault.contract_address, 0x10000); - snf::stop_prank(CheatTarget::One(erc20.contract_address)); - snf::start_prank(CheatTarget::One(fee_vault.contract_address), SPENDER()); - fee_vault.deposit(SPENDER(), erc20.contract_address, 0x10000); - fee_vault.collect(SPENDER(), erc20.contract_address, 0x10000); - snf::stop_prank(CheatTarget::One(fee_vault.contract_address)); -} - -#[test] -#[should_panic(expected: ('Insufficent balance',))] -fn fee_vault_collect_fails_if_insufficent_balance() { - let (erc20, fee_vault) = setup_contracts(); - snf::start_prank(CheatTarget::One(fee_vault.contract_address), OWNER()); - fee_vault.add_deductor(SPENDER()); - snf::stop_prank(CheatTarget::One(fee_vault.contract_address)); - erc20.mint_to(SPENDER(), 0x10000); - snf::start_prank(CheatTarget::One(erc20.contract_address), SPENDER()); - erc20.approve(fee_vault.contract_address, 0x10000); - snf::stop_prank(CheatTarget::One(erc20.contract_address)); - snf::start_prank(CheatTarget::One(fee_vault.contract_address), SPENDER()); - fee_vault.deposit(SPENDER(), erc20.contract_address, 0x1000); - snf::stop_prank(CheatTarget::One(fee_vault.contract_address)); - snf::start_prank(CheatTarget::One(fee_vault.contract_address), OWNER()); - fee_vault.collect(OWNER(), erc20.contract_address, 0x10000); - snf::stop_prank(CheatTarget::One(fee_vault.contract_address)); -} diff --git a/src/tests/common.cairo b/src/tests/common.cairo index 1ee6b0e..4c43314 100644 --- a/src/tests/common.cairo +++ b/src/tests/common.cairo @@ -1,15 +1,13 @@ -use blobstream_sn::succinctx::fee_vault::succinct_fee_vault; -use blobstream_sn::succinctx::function_registry::erc20_mock::{ - IMockERC20Dispatcher, IMockERC20DispatcherTrait, MockERC20 -}; -use blobstream_sn::succinctx::function_registry::interfaces::{ - IFunctionRegistryDispatcher, IFunctionRegistryDispatcherTrait -}; -use blobstream_sn::succinctx::interfaces::{IFeeVaultDispatcher, IFeeVaultDispatcherTrait}; use openzeppelin::tests::utils::constants::OWNER; +use openzeppelin::utils::serde::SerializedAppend; use snforge_std as snf; use snforge_std::{ContractClassTrait, CheatTarget, SpyOn, EventSpy}; use starknet::ContractAddress; +use succinct_sn::fee_vault::succinct_fee_vault; +use succinct_sn::function_registry::interfaces::{ + IFunctionRegistryDispatcher, IFunctionRegistryDispatcherTrait +}; +use succinct_sn::interfaces::{IFeeVaultDispatcher, IFeeVaultDispatcherTrait}; // https://sepolia.etherscan.io/tx/0xadced8dc7f4bb01d730ed78daecbf9640417c5bd60b0ada23c9045cc953481a5#eventlog const TEST_START_BLOCK: u64 = 846054; @@ -17,12 +15,17 @@ const TEST_END_BLOCK: u64 = 846360; const TEST_HEADER: u256 = 0x47D040565942B111F7CD569BE78CE310644596F3929DF25584F3E5ADFD9F5001; const HEADER_RANGE_DIGEST: u256 = 0xb646edd6dbb2e5482b2449404cf1888b8f4cd6958c790aa075e99226c2c1d62; const NEXT_HEADER_DIGEST: u256 = 0xfd6c88812a160ff288fe557111815b3433c539c77a3561086cfcdd9482bceb8; +const TOTAL_SUPPLY: u256 = 0x100000000000000000000000000000001; fn setup_base() -> ContractAddress { // deploy the token associated with the fee vault - let token_class = snf::declare('MockERC20'); - let token_calldata = array!['FeeToken', 'FT']; - let token_address = token_class.deploy(@token_calldata).unwrap(); + let mut calldata = array![]; + calldata.append_serde('FeeToken'); + calldata.append_serde('FT'); + calldata.append_serde(TOTAL_SUPPLY); + calldata.append_serde(OWNER()); + let token_class = snf::declare('SnakeERC20Mock'); + let token_address = token_class.deploy(@calldata).unwrap(); // deploy the fee vault let fee_vault_class = snf::declare('succinct_fee_vault'); @@ -76,9 +79,13 @@ fn setup_spied() -> (ContractAddress, EventSpy) { fn setup_succinct_gateway() -> ContractAddress { // deploy the token associated with the fee vault - let token_class = snf::declare('MockERC20'); - let token_calldata = array!['FeeToken', 'FT']; - let token_address = token_class.deploy(@token_calldata).unwrap(); + let mut calldata = array![]; + calldata.append_serde('FeeToken'); + calldata.append_serde('FT'); + calldata.append_serde(TOTAL_SUPPLY); + calldata.append_serde(OWNER()); + let token_class = snf::declare('SnakeERC20Mock'); + let token_address = token_class.deploy(@calldata).unwrap(); // deploy the fee vault let fee_vault_class = snf::declare('succinct_fee_vault'); diff --git a/src/tests/test_blobstreamx.cairo b/src/tests/test_blobstreamx.cairo index 7917f33..c525846 100644 --- a/src/tests/test_blobstreamx.cairo +++ b/src/tests/test_blobstreamx.cairo @@ -4,9 +4,6 @@ use blobstream_sn::interfaces::{ IBlobstreamXDispatcher, IBlobstreamXDispatcherTrait, Validator, ITendermintXDispatcher, ITendermintXDispatcherTrait }; -use blobstream_sn::succinctx::interfaces::{ - ISuccinctGatewayDispatcher, ISuccinctGatewayDispatcherTrait -}; use blobstream_sn::tests::common::{ setup_base, setup_spied, setup_succinct_gateway, TEST_START_BLOCK, TEST_END_BLOCK, TEST_HEADER, }; @@ -15,6 +12,7 @@ use snforge_std as snf; use snforge_std::{CheatTarget, EventSpy, EventAssertions}; use starknet::secp256_trait::Signature; use starknet::{ContractAddress, EthAddress, info::get_block_number}; +use succinct_sn::interfaces::{ISuccinctGatewayDispatcher, ISuccinctGatewayDispatcherTrait}; fn setup_blobstreamx() -> IBlobstreamXDispatcher { IBlobstreamXDispatcher { contract_address: setup_base() }