diff --git a/contracts/src/components/escrow/escrow.cairo b/contracts/src/components/escrow/escrow.cairo index 38374ac..116a190 100644 --- a/contracts/src/components/escrow/escrow.cairo +++ b/contracts/src/components/escrow/escrow.cairo @@ -20,7 +20,6 @@ pub mod EscrowComponent { // pub mod Errors { - pub const PROOF_OF_DEPOSIT_FAILED: felt252 = 'Proof of deposit failed'; pub const INSUFFICIENT_BALANCE: felt252 = 'Insufficient deposit balance'; } @@ -33,13 +32,13 @@ pub mod EscrowComponent { TContractState, +HasComponent, +Drop, > of interface::IEscrow> { fn lock(ref self: ComponentState, from: ContractAddress, token: ContractAddress, amount: u256) { - let locked_amount = self.deposits.read((from, token)); - - // transfers funds to escrow let erc20_dispatcher = IERC20Dispatcher { contract_address: token }; - erc20_dispatcher.transfer_from(from, get_contract_address(), amount); + // transfers funds to escrow + erc20_dispatcher.transfer_from(sender: from, recipient: get_contract_address(), :amount); + // update locked amount + let locked_amount = self.deposits.read((from, token)); self.deposits.write((from, token), amount + locked_amount); } @@ -52,17 +51,13 @@ pub mod EscrowComponent { ) { let locked_amount = self.deposits.read((from, token)); - // TODO - // check for proof of deposit - assert(true, Errors::PROOF_OF_DEPOSIT_FAILED); - // check deposit balance assert(locked_amount >= amount, Errors::INSUFFICIENT_BALANCE); // transfert of the amount to `to` let erc20_dispatcher = IERC20Dispatcher { contract_address: token }; - erc20_dispatcher.transfer_from(get_contract_address(), to, amount); + erc20_dispatcher.transfer(recipient: to, :amount); // update locked amount self.deposits.write((from, token), locked_amount - amount); diff --git a/contracts/src/components/escrow/escrow_test.cairo b/contracts/src/components/escrow/escrow_test.cairo index e90fbbc..924894c 100644 --- a/contracts/src/components/escrow/escrow_test.cairo +++ b/contracts/src/components/escrow/escrow_test.cairo @@ -1,105 +1,294 @@ -use core::starknet::get_contract_address; -use core::starknet::storage::StorageMapReadAccess; +use core::num::traits::Bounded; use openzeppelin::presets::interfaces::ERC20UpgradeableABIDispatcherTrait; -use snforge_std::start_cheat_caller_address; +use snforge_std::{start_cheat_caller_address, stop_cheat_caller_address, test_address}; +use starknet::storage::StorageMapReadAccess; use zkramp::components::escrow::escrow::EscrowComponent::EscrowImpl; use zkramp::components::escrow::escrow_mock::{TestingStateDefault, ComponentState}; use zkramp::tests::constants; use zkramp::tests::utils; +fn COMPONENT_STATE() -> ComponentState { + Default::default() +} + +fn setup() -> ComponentState { + COMPONENT_STATE() + // no more setup needed here +} + // -// Externals +// lock // #[test] fn test_lock() { - let token_dispatcher = utils::setup_erc20(recipient: constants::OWNER()); - start_cheat_caller_address(token_dispatcher.contract_address, constants::OWNER()); - - token_dispatcher.transfer(constants::SPENDER(), 100); - - start_cheat_caller_address(token_dispatcher.contract_address, constants::SPENDER()); - - token_dispatcher.approve(constants::RECIPIENT(), 42); - start_cheat_caller_address(token_dispatcher.contract_address, constants::RECIPIENT()); - - let mut escrow: ComponentState = Default::default(); - - escrow.lock(constants::SPENDER(), token_dispatcher.contract_address, 42); - - assert_eq!(token_dispatcher.balance_of(constants::SPENDER()), 58); - assert_eq!(token_dispatcher.allowance(constants::SPENDER(), constants::RECIPIENT()), 0); + let mut state = setup(); + let locker = constants::OWNER(); + let erc20 = utils::setup_erc20(recipient: locker); + let contract_address = test_address(); + let amount: u256 = 42; + + // approve escrow to spend funds + start_cheat_caller_address(erc20.contract_address, locker); + erc20.approve(spender: contract_address, amount: Bounded::MAX); + stop_cheat_caller_address(erc20.contract_address); + + // assert state before + assert_eq!(erc20.balance_of(locker), constants::SUPPLY); + assert_eq!(erc20.balance_of(contract_address), 0); + assert_eq!(state.deposits.read((locker, erc20.contract_address)), 0); + + // lock + state.lock(from: locker, token: erc20.contract_address, :amount); + + // assert state after + assert_eq!(erc20.balance_of(locker), constants::SUPPLY - amount); + assert_eq!(erc20.balance_of(contract_address), amount); + assert_eq!(state.deposits.read((locker, erc20.contract_address)), amount); } #[test] -fn test_lock_unlock() { - let token_dispatcher = utils::setup_erc20(recipient: constants::OWNER()); - start_cheat_caller_address(token_dispatcher.contract_address, constants::OWNER()); - - token_dispatcher.transfer(constants::SPENDER(), 100); - - start_cheat_caller_address(token_dispatcher.contract_address, constants::SPENDER()); - - token_dispatcher.approve(constants::RECIPIENT(), 42); - start_cheat_caller_address(token_dispatcher.contract_address, constants::RECIPIENT()); - - let mut escrow: ComponentState = Default::default(); - - escrow.lock(constants::SPENDER(), token_dispatcher.contract_address, 42); +fn test_lock_zero() { + let mut state = setup(); + let locker = constants::OWNER(); + let erc20 = utils::setup_erc20(recipient: locker); + let contract_address = test_address(); + let amount: u256 = 0; + + // approve escrow to spend funds + start_cheat_caller_address(erc20.contract_address, locker); + erc20.approve(spender: contract_address, amount: Bounded::MAX); + stop_cheat_caller_address(erc20.contract_address); + + // assert state before + assert_eq!(erc20.balance_of(locker), constants::SUPPLY); + assert_eq!(erc20.balance_of(contract_address), 0); + assert_eq!(state.deposits.read((locker, erc20.contract_address)), 0); + + // lock + state.lock(from: locker, token: erc20.contract_address, :amount); + + // assert state after + assert_eq!(erc20.balance_of(locker), constants::SUPPLY - amount); + assert_eq!(erc20.balance_of(contract_address), amount); + assert_eq!(state.deposits.read((locker, erc20.contract_address)), amount); +} - start_cheat_caller_address(token_dispatcher.contract_address, get_contract_address()); +#[test] +fn test_lock_twice() { + let mut state = setup(); + let locker = constants::OWNER(); + let erc20 = utils::setup_erc20(recipient: locker); + let contract_address = test_address(); + let amount1: u256 = 42; + let amount2: u256 = 75; + + // approve escrow to spend funds + start_cheat_caller_address(erc20.contract_address, locker); + erc20.approve(spender: contract_address, amount: Bounded::MAX); + stop_cheat_caller_address(erc20.contract_address); + + // assert state before + assert_eq!(erc20.balance_of(locker), constants::SUPPLY); + assert_eq!(erc20.balance_of(contract_address), 0); + assert_eq!(state.deposits.read((locker, erc20.contract_address)), 0); + + // lock + state.lock(from: locker, token: erc20.contract_address, amount: amount1); + state.lock(from: locker, token: erc20.contract_address, amount: amount2); + + // assert state after + assert_eq!(erc20.balance_of(locker), constants::SUPPLY - amount1 - amount2); + assert_eq!(erc20.balance_of(contract_address), amount1 + amount2); + assert_eq!(state.deposits.read((locker, erc20.contract_address)), amount1 + amount2); +} - token_dispatcher.approve(constants::RECIPIENT(), 42); +#[test] +fn test_lock_multiple_tokens() { + let mut state = setup(); + let locker = constants::OWNER(); + let erc20_1 = utils::setup_erc20(recipient: locker); + let erc20_2 = utils::setup_erc20(recipient: locker); + let contract_address = test_address(); + let amount1: u256 = 42; + let amount2: u256 = 75; + + // approve escrow to spend funds for token 1 + start_cheat_caller_address(erc20_1.contract_address, locker); + erc20_1.approve(spender: contract_address, amount: Bounded::MAX); + stop_cheat_caller_address(erc20_1.contract_address); + + // approve escrow to spend funds for token 2 + start_cheat_caller_address(erc20_2.contract_address, locker); + erc20_2.approve(spender: contract_address, amount: Bounded::MAX); + stop_cheat_caller_address(erc20_2.contract_address); + + // assert state before + assert_eq!(erc20_1.balance_of(locker), constants::SUPPLY); + assert_eq!(erc20_1.balance_of(contract_address), 0); + assert_eq!(state.deposits.read((locker, erc20_1.contract_address)), 0); + assert_eq!(erc20_2.balance_of(locker), constants::SUPPLY); + assert_eq!(erc20_2.balance_of(contract_address), 0); + assert_eq!(state.deposits.read((locker, erc20_2.contract_address)), 0); + + // lock + state.lock(from: locker, token: erc20_1.contract_address, amount: amount1); + state.lock(from: locker, token: erc20_2.contract_address, amount: amount2); + + // assert state after + assert_eq!(erc20_1.balance_of(locker), constants::SUPPLY - amount1); + assert_eq!(erc20_1.balance_of(contract_address), amount1); + assert_eq!(state.deposits.read((locker, erc20_1.contract_address)), amount1); + assert_eq!(erc20_2.balance_of(locker), constants::SUPPLY - amount2); + assert_eq!(erc20_2.balance_of(contract_address), amount2); + assert_eq!(state.deposits.read((locker, erc20_2.contract_address)), amount2); +} - start_cheat_caller_address(token_dispatcher.contract_address, constants::RECIPIENT()); - escrow.unlock(constants::SPENDER(), constants::RECIPIENT(), token_dispatcher.contract_address, 42); +// +// unlock +// - assert_eq!(token_dispatcher.balance_of(constants::SPENDER()), 58); - assert_eq!(token_dispatcher.balance_of(constants::RECIPIENT()), 42); - assert_eq!(token_dispatcher.allowance(constants::SPENDER(), constants::RECIPIENT()), 0); - assert_eq!(escrow.deposits.read((constants::SPENDER(), token_dispatcher.contract_address)), 0); +#[test] +#[should_panic(expected: 'Insufficient deposit balance')] +fn test_unlock_without_lock() { + let mut state = setup(); + let locker = constants::OWNER(); + let unlocker = constants::RECIPIENT(); + let erc20 = utils::setup_erc20(recipient: locker); + let amount: u256 = 42; + + // unlock + state.unlock(from: locker, to: unlocker, token: erc20.contract_address, :amount); } #[test] #[should_panic(expected: 'Insufficient deposit balance')] -fn test_lock_unlock_greater_than_balance() { - let token_dispatcher = utils::setup_erc20(recipient: constants::OWNER()); - start_cheat_caller_address(token_dispatcher.contract_address, constants::OWNER()); - - token_dispatcher.transfer(constants::SPENDER(), 1000); - - start_cheat_caller_address(token_dispatcher.contract_address, constants::SPENDER()); - - token_dispatcher.approve(constants::RECIPIENT(), 42); - start_cheat_caller_address(token_dispatcher.contract_address, constants::RECIPIENT()); - - let mut escrow: ComponentState = Default::default(); - - escrow.lock(constants::SPENDER(), token_dispatcher.contract_address, 42); - - start_cheat_caller_address(token_dispatcher.contract_address, get_contract_address()); - - token_dispatcher.approve(constants::RECIPIENT(), 42); - - start_cheat_caller_address(token_dispatcher.contract_address, constants::RECIPIENT()); - escrow.unlock(constants::SPENDER(), constants::RECIPIENT(), token_dispatcher.contract_address, 420); +fn test_unlock_amount_too_high() { + let mut state = setup(); + let locker = constants::OWNER(); + let unlocker = constants::RECIPIENT(); + let erc20 = utils::setup_erc20(recipient: locker); + let contract_address = test_address(); + let amount: u256 = 42; + + // approve escrow to spend funds + start_cheat_caller_address(erc20.contract_address, locker); + erc20.approve(spender: contract_address, amount: Bounded::MAX); + stop_cheat_caller_address(erc20.contract_address); + + // lock + state.lock(from: locker, token: erc20.contract_address, :amount); + + // unlock + state.unlock(from: locker, to: unlocker, token: erc20.contract_address, amount: amount + 1); } #[test] -#[should_panic(expected: 'ERC20: insufficient allowance')] -fn test_lock_unallowed_caller() { - let token_dispatcher = utils::setup_erc20(recipient: constants::OWNER()); - start_cheat_caller_address(token_dispatcher.contract_address, constants::OWNER()); - - token_dispatcher.transfer(constants::SPENDER(), 100); - - start_cheat_caller_address(token_dispatcher.contract_address, constants::SPENDER()); - - token_dispatcher.approve(constants::RECIPIENT(), 42); +#[should_panic(expected: 'Insufficient deposit balance')] +fn test_unlock_wrong_token() { + let mut state = setup(); + let locker = constants::OWNER(); + let unlocker = constants::RECIPIENT(); + let erc20 = utils::setup_erc20(recipient: locker); + let other_erc20 = utils::setup_erc20(recipient: locker); + let contract_address = test_address(); + let amount: u256 = 42; + + // approve escrow to spend funds + start_cheat_caller_address(erc20.contract_address, locker); + erc20.approve(spender: contract_address, amount: Bounded::MAX); + stop_cheat_caller_address(erc20.contract_address); + + // lock + state.lock(from: locker, token: erc20.contract_address, :amount); + + // unlock + state.unlock(from: locker, to: unlocker, token: other_erc20.contract_address, :amount); +} - start_cheat_caller_address(token_dispatcher.contract_address, constants::CALLER()); +#[test] +#[should_panic(expected: 'Insufficient deposit balance')] +fn test_unlock_from_wrong_locker() { + let mut state = setup(); + let locker = constants::OWNER(); + let unlocker = constants::RECIPIENT(); + let other = constants::OTHER(); + let erc20 = utils::setup_erc20(recipient: locker); + let contract_address = test_address(); + let amount: u256 = 42; + + // approve escrow to spend funds + start_cheat_caller_address(erc20.contract_address, locker); + erc20.approve(spender: contract_address, amount: Bounded::MAX); + stop_cheat_caller_address(erc20.contract_address); + + // lock + state.lock(from: locker, token: erc20.contract_address, :amount); + + // unlock + state.unlock(from: other, to: unlocker, token: erc20.contract_address, :amount); +} - let mut escrow: ComponentState = Default::default(); +#[test] +fn test_unlock() { + let mut state = setup(); + let locker = constants::OWNER(); + let unlocker = constants::RECIPIENT(); + let erc20 = utils::setup_erc20(recipient: locker); + let contract_address = test_address(); + let amount: u256 = 42; + + // approve escrow to spend funds + start_cheat_caller_address(erc20.contract_address, locker); + erc20.approve(spender: contract_address, amount: Bounded::MAX); + stop_cheat_caller_address(erc20.contract_address); + + // lock + state.lock(from: locker, token: erc20.contract_address, :amount); + + // assert state before + assert_eq!(erc20.balance_of(locker), constants::SUPPLY - amount); + assert_eq!(erc20.balance_of(constants::RECIPIENT()), 0); + assert_eq!(erc20.balance_of(contract_address), amount); + + // unlock + state.unlock(from: locker, to: unlocker, token: erc20.contract_address, :amount); + + // assert state after + assert_eq!(erc20.balance_of(locker), constants::SUPPLY - amount); + assert_eq!(erc20.balance_of(unlocker), amount); + assert_eq!(erc20.balance_of(contract_address), 0); +} - escrow.lock(constants::SPENDER(), token_dispatcher.contract_address, 42); +#[test] +fn test_unlock_twice() { + let mut state = setup(); + let locker = constants::OWNER(); + let unlocker = constants::RECIPIENT(); + let erc20 = utils::setup_erc20(recipient: locker); + let contract_address = test_address(); + let amount1: u256 = 42; + let amount2: u256 = 75; + + // approve escrow to spend funds + start_cheat_caller_address(erc20.contract_address, locker); + erc20.approve(spender: contract_address, amount: Bounded::MAX); + stop_cheat_caller_address(erc20.contract_address); + + // lock + state.lock(from: locker, token: erc20.contract_address, amount: amount1 + amount2); + + // assert state before + assert_eq!(erc20.balance_of(locker), constants::SUPPLY - amount1 - amount2); + assert_eq!(erc20.balance_of(constants::RECIPIENT()), 0); + assert_eq!(erc20.balance_of(contract_address), amount1 + amount2); + + // unlock + state.unlock(from: locker, to: unlocker, token: erc20.contract_address, amount: amount1); + state.unlock(from: locker, to: unlocker, token: erc20.contract_address, amount: amount2); + + // assert state after + assert_eq!(erc20.balance_of(locker), constants::SUPPLY - amount1 - amount2); + assert_eq!(erc20.balance_of(unlocker), amount1 + amount2); + assert_eq!(erc20.balance_of(contract_address), 0); } diff --git a/contracts/src/components/registry/registry.cairo b/contracts/src/components/registry/registry.cairo index 78a85c1..c9c8889 100644 --- a/contracts/src/components/registry/registry.cairo +++ b/contracts/src/components/registry/registry.cairo @@ -60,8 +60,6 @@ pub mod RegistryComponent { // verify caller assert(caller.is_non_zero(), Errors::ZERO_ADDRESS_CALLER); - // TODO: caller a processor to verify the proof of registration - // save registration self.Registry_registrations.write((caller, offchain_id), true); diff --git a/contracts/src/components/registry/registry_test.cairo b/contracts/src/components/registry/registry_test.cairo index 2390640..916868f 100644 --- a/contracts/src/components/registry/registry_test.cairo +++ b/contracts/src/components/registry/registry_test.cairo @@ -1,42 +1,50 @@ -use core::starknet::ContractAddress; -use snforge_std::{start_cheat_caller_address, EventSpyAssertionsTrait, spy_events, test_address}; +use snforge_std::{EventSpyAssertionsTrait, spy_events, test_address, start_cheat_caller_address}; use zkramp::components::registry::registry::{RegistryComponent::{Event, RegistrationEvent, RegistryImpl}}; use zkramp::components::registry::registry_mock::{TestingStateDefault, ComponentState}; use zkramp::tests::constants; +fn COMPONENT_STATE() -> ComponentState { + Default::default() +} + +fn setup() -> ComponentState { + COMPONENT_STATE() + // no more setup needed here +} + // -// Externals +// register // #[test] -fn test_is_registered() { - let test_address: ContractAddress = test_address(); - - start_cheat_caller_address(test_address, constants::CALLER()); - - let mut registry: ComponentState = Default::default(); - - registry.register(offchain_id: constants::REVOLUT_ID()); +#[should_panic(expected: 'Caller is the zero address')] +fn test_register_from_zero() { + let mut state = setup(); - assert!(registry.is_registered(constants::CALLER(), constants::REVOLUT_ID())); + state.register(offchain_id: constants::REVOLUT_ID()); } #[test] -fn test_registration_event() { - let test_address: ContractAddress = test_address(); +fn test_register() { + let mut state = setup(); let mut spy = spy_events(); + let contract_address = test_address(); - start_cheat_caller_address(test_address, constants::CALLER()); + // setup caller + start_cheat_caller_address(contract_address, constants::CALLER()); - let mut registry: ComponentState = Default::default(); + // register + state.register(offchain_id: constants::REVOLUT_ID()); - registry.register(offchain_id: constants::REVOLUT_ID()); + // assert state after + assert!(state.is_registered(constants::CALLER(), constants::REVOLUT_ID())); + // check on emitted events spy .assert_emitted( @array![ ( - test_address, + contract_address, Event::RegistrationEvent( RegistrationEvent { caller: constants::CALLER(), offchain_id: constants::REVOLUT_ID() } ) @@ -45,10 +53,26 @@ fn test_registration_event() { ) } -#[test] -#[should_panic(expected: 'Caller is the zero address')] -fn test_register_from_zero() { - let mut registry: ComponentState = Default::default(); +// #[test] +fn test_register_twice_same_offchain_id() { + panic!("Not implemented yet"); +} - registry.register(offchain_id: constants::REVOLUT_ID()); +// #[test] +fn test_register_two_different_offchain_id() { + panic!("Not implemented yet"); +} + +// #[test] +fn test_register_same_offchain_id_from_two_different_callers() { + panic!("Not implemented yet"); +} + +// +// is_registered +// + +// #[test] +fn test_is_registered() { + panic!("Not implemented yet"); } diff --git a/contracts/src/contracts/ramps/revolut/interface.cairo b/contracts/src/contracts/ramps/revolut/interface.cairo index 6be18af..e977848 100644 --- a/contracts/src/contracts/ramps/revolut/interface.cairo +++ b/contracts/src/contracts/ramps/revolut/interface.cairo @@ -1,6 +1,10 @@ use starknet::{ContractAddress, ClassHash}; use zkramp::components::registry::interface::OffchainId; +// +// Structs +// + #[derive(Drop, Serde)] pub struct Proof { foo: felt252 @@ -12,7 +16,7 @@ pub struct LiquidityKey { pub offchain_id: OffchainId, } -#[derive(Drop, Copy, starknet::Store)] +#[derive(Drop, Copy, Serde, starknet::Store)] pub struct LiquidityShareRequest { pub requestor: ContractAddress, pub liquidity_key: LiquidityKey, @@ -20,8 +24,15 @@ pub struct LiquidityShareRequest { pub expiration_date: u64, } +// +// Interfaces +// + #[starknet::interface] pub trait IZKRampLiquidity { + fn all_liquidity(self: @TState, liquidity_key: LiquidityKey) -> u256; + fn available_liquidity(self: @TState, liquidity_key: LiquidityKey) -> u256; + fn liquidity_share_request(self: @TState, offchain_id: OffchainId) -> Option; fn add_liquidity(ref self: TState, amount: u256, offchain_id: OffchainId); fn retrieve_liquidity(ref self: TState, liquidity_key: LiquidityKey); fn initiate_liquidity_retrieval(ref self: TState, liquidity_key: LiquidityKey); @@ -31,9 +42,16 @@ pub trait IZKRampLiquidity { fn withdraw_liquidity(ref self: TState, liquidity_key: LiquidityKey, offchain_id: OffchainId, proof: Proof); } +// +// full ABI +// + #[starknet::interface] pub trait ZKRampABI { // IZKRampLiquidity + fn all_liquidity(self: @TState, liquidity_key: LiquidityKey) -> u256; + fn available_liquidity(self: @TState, liquidity_key: LiquidityKey) -> u256; + fn liquidity_share_request(self: @TState, offchain_id: OffchainId) -> Option; fn add_liquidity(ref self: TState, amount: u256, offchain_id: OffchainId); fn retrieve_liquidity(ref self: TState, liquidity_key: LiquidityKey); fn initiate_liquidity_withdrawal( diff --git a/contracts/src/contracts/ramps/revolut/revolut.cairo b/contracts/src/contracts/ramps/revolut/revolut.cairo index 0601341..ad4bad1 100644 --- a/contracts/src/contracts/ramps/revolut/revolut.cairo +++ b/contracts/src/contracts/ramps/revolut/revolut.cairo @@ -67,7 +67,6 @@ pub mod RevolutRamp { pub mod Errors { pub const NOT_REGISTERED: felt252 = 'Caller is not registered'; - pub const INVALID_AMOUNT: felt252 = 'Invalid amount'; pub const CALLER_IS_NOT_OWNER: felt252 = 'Caller is not the owner'; pub const CALLER_IS_OWNER: felt252 = 'Caller is the owner'; pub const NULL_AMOUNT: felt252 = 'Amount cannot be null'; @@ -154,8 +153,34 @@ pub mod RevolutRamp { self.token.write(token); } + // + // ZkRamp Liquidity impl + // + #[abi(embed_v0)] impl ZKRampLiquidityImpl of IZKRampLiquidity { + fn all_liquidity(self: @ContractState, liquidity_key: LiquidityKey) -> u256 { + self.liquidity.read(liquidity_key) + } + + fn available_liquidity(self: @ContractState, liquidity_key: LiquidityKey) -> u256 { + if self.locked_liquidity.read(liquidity_key) { + 0 + } else { + self._get_available_liquidity(:liquidity_key) + } + } + + fn liquidity_share_request(self: @ContractState, offchain_id: OffchainId) -> Option { + let share_request = self.liquidity_share_request.read(offchain_id); + + if share_request.expiration_date > get_block_timestamp() { + Option::Some(share_request) + } else { + Option::None + } + } + /// Create a liquidity position by locking an amonunt and asking for /// its equivalent on a specific offchain ID. /// @@ -167,7 +192,8 @@ pub mod RevolutRamp { // assert caller registered the offchain ID assert(self.registry.is_registered(contract_address: caller, :offchain_id), Errors::NOT_REGISTERED); - assert(amount.is_non_zero(), Errors::INVALID_AMOUNT); + // asserts amount is non null + assert(amount.is_non_zero(), Errors::NULL_AMOUNT); // get liquidity key let liquidity_key = LiquidityKey { owner: caller, offchain_id }; @@ -221,6 +247,9 @@ pub mod RevolutRamp { self.emit(LiquidityRetrieved { liquidity_key, amount }); } + // If the requested amount is valid according to the available amount, + // this share of the liquidity becomes unavailable for other users and the on-ramper has a defined period + // to provide proof of the off-chain transfer in order to withdraw the funds. fn initiate_liquidity_withdrawal( ref self: ContractState, liquidity_key: LiquidityKey, amount: u256, offchain_id: OffchainId ) { @@ -229,6 +258,8 @@ pub mod RevolutRamp { // assert caller is not the liquidity owner assert(liquidity_key.owner != caller, Errors::CALLER_IS_OWNER); + // asserts amount is non null + assert(amount.is_non_zero(), Errors::NULL_AMOUNT); // assert liquidity is unlocked assert(!self.locked_liquidity.read(liquidity_key), Errors::LOCKED_LIQUIDITY_WITHDRAW); // assert caller registered the offchain ID @@ -266,6 +297,9 @@ pub mod RevolutRamp { ) } + /// If the proof is valid according to the amount the caller requested using + /// the `initiate_liquidity_withdrawal` method, then the requested portion of the liquidity + /// is transferred to the caller. fn withdraw_liquidity( ref self: ContractState, liquidity_key: LiquidityKey, offchain_id: OffchainId, proof: Proof ) { @@ -276,7 +310,7 @@ pub mod RevolutRamp { // assert caller has a valid pending withdrawal assert( - share_request.expiration_date <= current_timestamp && share_request.requestor == caller, + share_request.expiration_date > current_timestamp && share_request.requestor == caller, Errors::LIQUIDITY_SHARE_NOT_AVAILABLE ); diff --git a/contracts/src/contracts/ramps/revolut/revolut_test.cairo b/contracts/src/contracts/ramps/revolut/revolut_test.cairo index c045727..c4a7d46 100644 --- a/contracts/src/contracts/ramps/revolut/revolut_test.cairo +++ b/contracts/src/contracts/ramps/revolut/revolut_test.cairo @@ -1,33 +1,344 @@ use core::starknet::{ContractAddress, get_caller_address}; +use openzeppelin::presets::interfaces::ERC20UpgradeableABIDispatcher; use openzeppelin::utils::serde::SerializedAppend; -use snforge_std::{declare, start_cheat_caller_address, test_address, DeclareResultTrait, ContractClassTrait}; +use snforge_std::{declare, DeclareResultTrait, ContractClassTrait}; use zkramp::contracts::ramps::revolut::interface::{ZKRampABIDispatcher, ZKRampABIDispatcherTrait, LiquidityKey}; use zkramp::tests::constants; +use zkramp::tests::utils; -fn deploy_revolut_ramp() -> ZKRampABIDispatcher { - let contract = declare("RevolutRamp").unwrap().contract_class(); +fn setup_revolut_ramp(erc20_contract_address: ContractAddress) -> ZKRampABIDispatcher { + // declare Revolut Ramp contract + let revolut_ramp_contract_class = declare("RevolutRamp").unwrap().contract_class(); + // deploy revolut ramp let mut calldata = array![]; calldata.append_serde(constants::OWNER()); - // TODO: give a relevant token address for better tests - calldata.append_serde(constants::OWNER()); + calldata.append_serde(erc20_contract_address); - let (contract_address, _) = contract.deploy(@calldata).unwrap(); + let (contract_address, _) = revolut_ramp_contract_class.deploy(@calldata).unwrap(); ZKRampABIDispatcher { contract_address } } -#[test] -#[should_panic(expected: 'Liquidity is unlocked')] -fn test_retrieve_uninitialized_liquidity_should_panic() { - let test_address: ContractAddress = test_address(); +fn setup() -> (ZKRampABIDispatcher, ERC20UpgradeableABIDispatcher) { + // deploy an ERC20 + let erc20 = utils::setup_erc20(constants::OWNER()); + + // deploy revolut ramp + let revolut_ramp = setup_revolut_ramp(erc20.contract_address); - start_cheat_caller_address(test_address, constants::CALLER()); + (revolut_ramp, erc20) +} + +// +// add_liquidity +// + +// #[test] +// #[should_panic(expected: 'Caller is not registered')] +fn test_add_liquidity_with_unregistered_offchain_id() { + panic!("Not implemented yet"); +} - let revolut_ramp = deploy_revolut_ramp(); +// #[test] +// #[should_panic(expected: 'Amount cannot be null')] +fn test_add_zero_liquidity() { + panic!("Not implemented yet"); +} + +// #[test] +fn test_add_liquidity() { + panic!("Not implemented yet"); +} + +// #[test] +fn test_add_liquidity_twice() { + panic!("Not implemented yet"); +} + +// #[test] +fn test_add_liquidity_to_locked_liquidity() { + panic!("Not implemented yet"); +} +// +// initiate_liquidity_retrival & retrieve_liquidity +// + +// #[test] +// #[should_panic(expected: 'Amount cannot be null')] +fn test_initiate_empty_liquidity_retrieval() { + panic!("Not implemented yet"); +} + +// #[test] +// #[should_panic(expected: 'Caller is not the owner')] +fn test_initiate_liquidity_retrieval_not_owner() { + panic!("Not implemented yet"); +} + +// #[test] +fn test_initiate_liquidity_retrieval() { + panic!("Not implemented yet"); +} + +// #[test] +fn test_initiate_liquidity_retrieval_twice() { + panic!("Not implemented yet"); +} + +#[test] +#[should_panic(expected: 'Liquidity is unlocked')] +fn test_retrieve_unlocked_liquidity() { + let (revolut_ramp, _) = setup(); + + // create liquidity key let liquidity_key = LiquidityKey { owner: get_caller_address(), offchain_id: constants::REVOLUT_ID() }; + // try to retrieve liquidity revolut_ramp.retrieve_liquidity(:liquidity_key); } + +// #[test] +// #[should_panic(expected: 'Caller is not the owner')] +fn test_retrieve_liquidity_not_owner() { + panic!("Not implemented yet"); +} + +// #[test] +fn test_retrieve_liquidity() { + panic!("Not implemented yet"); +} + +// #[test] +fn test_retrieve_liquidity_twice() { + panic!("Not implemented yet"); +} + +// #[test] +fn test_retrieve_liquidity_with_pending_requests() { + panic!("Not implemented yet"); +} + +// #[test] +fn test_retrieve_liquidity_with_expired_requests() { + panic!("Not implemented yet"); +} + +// +// initiate_liquidity_withdraw & withdraw_liquidity +// + +// #[test] +// #[should_panic(expected: 'Caller is the owner')] +fn test_initiate_liquidity_withdraw_from_owner() { + panic!("Not implemented yet"); +} + +// #[test] +// #[should_panic(expected: 'Amount cannot be null')] +fn test_initiate_liquidity_withdraw_zero_amount() { + panic!("Not implemented yet"); +} + +// #[test] +// #[should_panic(expected: 'Liquidity is not available')] +fn test_initiate_liquidity_withdraw_locked() { + panic!("Not implemented yet"); +} + +// #[test] +// #[should_panic(expected: 'Caller is not registered')] +fn test_initiate_liquidity_withdraw_with_unregistered_offchain_id() { + panic!("Not implemented yet"); +} + +// #[test] +// #[should_panic(expected: 'Not enough liquidity')] +fn test_initiate_liquidity_withdraw_without_enough_liquidity() { + panic!("Not implemented yet"); +} + +// #[test] +// #[should_panic(expected: 'Not enough liquidity')] +fn test_initiate_liquidity_withdraw_without_enough_available_liquidity() { + panic!("Not implemented yet"); +} + +// #[test] +fn test_initiate_liquidity_withdraw() { + panic!("Not implemented yet"); +} + +// #[test] +// #[should_panic(expected: 'This offchainID is busy')] +fn test_initiate_liquidity_withdraw_twice() { + panic!("Not implemented yet"); +} + +// #[test] +// #[should_panic(expected: 'Liquidity share not available')] +fn test_withdraw_liquidity_without_request() { + panic!("Not implemented yet"); +} + +// #[test] +// #[should_panic(expected: 'Liquidity share not available')] +fn test_withdraw_liquidity_after_expiration() { + panic!("Not implemented yet"); +} + +// #[test] +// #[should_panic(expected: 'Liquidity share not available')] +fn test_withdraw_liquidity_from_another_caller() { + panic!("Not implemented yet"); +} + +// #[test] +// #[should_panic(expected: 'Liquidity share not available')] +fn test_withdraw_liquidity_from_another_offchain_id() { + panic!("Not implemented yet"); +} + +// #[test] +fn test_withdraw_liquidity() { + panic!("Not implemented yet"); +} + +// #[test] +// #[should_panic(expected: 'Liquidity share not available')] +fn test_withdraw_liquidity_twice() { + panic!("Not implemented yet"); +} + +// +// _get_next_timestamp_key +// + +// #[test] +fn test__get_next_timestamp_key_basic() { + panic!("Not implemented yet"); +} + +// #[test] +fn test__get_next_timestamp_key_for_timestamp_key() { + panic!("Not implemented yet"); +} + +// #[test] +fn test__get_next_timestamp_key_from_zero() { + panic!("Not implemented yet"); +} + +// #[test] +fn test_get_available_liquidity_basic() { + panic!("Not implemented yet"); +} + +// +// available_liquidity & _get_available_liquidity +// + +// #[test] +fn test_available_liquidity_empty() { + panic!("Not implemented yet"); +} + +// #[test] +fn test_available_liquidity_without_requests() { + panic!("Not implemented yet"); +} + +// #[test] +fn test_available_liquidity_locked() { + panic!("Not implemented yet"); +} + +// #[test] +fn test_available_liquidity_with_expired_requests() { + panic!("Not implemented yet"); +} + +// #[test] +fn test_available_liquidity_with_pending_requests() { + panic!("Not implemented yet"); +} + +// #[test] +fn test_available_liquidity_with_withdrawn_requests() { + panic!("Not implemented yet"); +} + +// #[test] +fn test__get_available_liquidity_empty() { + panic!("Not implemented yet"); +} + +// #[test] +fn test__get_available_liquidity_without_requests() { + panic!("Not implemented yet"); +} + +// #[test] +fn test__get_available_liquidity_with_expired_requests() { + panic!("Not implemented yet"); +} + +// #[test] +fn test__get_available_liquidity_with_pending_requests() { + panic!("Not implemented yet"); +} + +// #[test] +fn test__get_available_liquidity_with_withdrawn_requests() { + panic!("Not implemented yet"); +} + +// +// all_liquidity +// + +// #[test] +fn test_all_liquidity_empty() { + panic!("Not implemented yet"); +} + +// #[test] +fn test_all_liquidity() { + panic!("Not implemented yet"); +} + +// #[test] +fn test_all_liquidity_locked() { + panic!("Not implemented yet"); +} + +// #[test] +fn test_all_liquidity_with_requests() { + panic!("Not implemented yet"); +} + +// +// liquidity_share_request +// + +// #[test] +fn test_liquidity_share_request_empty() { + panic!("Not implemented yet"); +} + +// #[test] +fn test_liquidity_share_request_expired() { + panic!("Not implemented yet"); +} + +// #[test] +fn test_liquidity_share_request_valid() { + panic!("Not implemented yet"); +} + +// #[test] +fn test_liquidity_share_request_withdrawn() { + panic!("Not implemented yet"); +} diff --git a/contracts/src/tests/constants.cairo b/contracts/src/tests/constants.cairo index b967bb0..e7e9335 100644 --- a/contracts/src/tests/constants.cairo +++ b/contracts/src/tests/constants.cairo @@ -23,6 +23,10 @@ pub fn OWNER() -> ContractAddress { contract_address_const::<'owner'>() } +pub fn OTHER() -> ContractAddress { + contract_address_const::<'other'>() +} + pub const SUPPLY: u256 = 1_000_000_000_000_000_000; // 1 ETH