diff --git a/contracts/src/contracts/ramps/revolut/interface.cairo b/contracts/src/contracts/ramps/revolut/interface.cairo index f0eb455..6cb51b9 100644 --- a/contracts/src/contracts/ramps/revolut/interface.cairo +++ b/contracts/src/contracts/ramps/revolut/interface.cairo @@ -10,13 +10,13 @@ pub struct Proof { pub foo: felt252 } -#[derive(Drop, Copy, Hash, Serde, starknet::Store)] +#[derive(Drop, Copy, Hash, Serde, starknet::Store, Debug, PartialEq)] pub struct LiquidityKey { pub owner: ContractAddress, pub offchain_id: OffchainId, } -#[derive(Drop, Copy, Serde, starknet::Store)] +#[derive(Drop, Copy, Serde, starknet::Store, Debug, PartialEq)] pub struct LiquidityShareRequest { pub requestor: ContractAddress, pub liquidity_key: LiquidityKey, diff --git a/contracts/src/contracts/ramps/revolut/revolut.cairo b/contracts/src/contracts/ramps/revolut/revolut.cairo index d9b5f39..95d0f45 100644 --- a/contracts/src/contracts/ramps/revolut/revolut.cairo +++ b/contracts/src/contracts/ramps/revolut/revolut.cairo @@ -362,8 +362,14 @@ pub mod RevolutRamp { } fn _get_next_timestamp_key(self: @ContractState, after: u64) -> u64 { - // minus 1 in order to return `after` if it's already a valid key timestamp. - after - 1 + LOCK_DURATION_STEP - ((after - 1) % LOCK_DURATION_STEP) + if after.is_zero() { + 0 + } else { + // minus 1 in order to return `after` if it's already a valid key timestamp. + let increment_step = (after - 1) / LOCK_DURATION_STEP + 1; + // returns a multiple of LOCK_DURATION_STEP + LOCK_DURATION_STEP * increment_step + } } } } diff --git a/contracts/src/contracts/ramps/revolut/revolut_test.cairo b/contracts/src/contracts/ramps/revolut/revolut_test.cairo index 6066316..2c4e8ea 100644 --- a/contracts/src/contracts/ramps/revolut/revolut_test.cairo +++ b/contracts/src/contracts/ramps/revolut/revolut_test.cairo @@ -6,13 +6,13 @@ use snforge_std::{ EventSpyAssertionsTrait, spy_events, declare, DeclareResultTrait, ContractClassTrait, start_cheat_caller_address, stop_cheat_caller_address, test_address, start_cheat_block_timestamp_global }; -use zkramp::contracts::ramps::revolut::interface::{ZKRampABIDispatcher, ZKRampABIDispatcherTrait, LiquidityShareRequest, LiquidityKey}; +use starknet::storage::{StorageMapWriteAccess}; +use zkramp::contracts::ramps::revolut::interface::{ + ZKRampABIDispatcher, ZKRampABIDispatcherTrait, LiquidityKey, LiquidityShareRequest +}; use zkramp::contracts::ramps::revolut::revolut::RevolutRamp::{ Event, LiquidityAdded, LiquidityRetrieved, LiquidityLocked, LiquidityShareRequested, LiquidityShareWithdrawn, - InternalImpl as RevolutRampInternalImpl, MINIMUM_LOCK_DURATION -}; -use starknet::storage::{ - StorageMapWriteAccess + InternalImpl as RevolutRampInternalImpl, MINIMUM_LOCK_DURATION, LOCK_DURATION_STEP }; use zkramp::contracts::ramps::revolut::revolut::RevolutRamp; use zkramp::tests::constants; @@ -1171,53 +1171,236 @@ fn test_withdraw_liquidity_twice() { // _get_next_timestamp_key // -// #[test] +#[test] fn test__get_next_timestamp_key_basic() { - panic!("Not implemented yet"); + // setup + let state = RevolutRamp::contract_state_for_testing(); + + // test a value between 0 and LOCK_DURATION_STEP + let after = 42; + + // should be rounded to the next threshold + assert_eq!(state._get_next_timestamp_key(:after), LOCK_DURATION_STEP); } -// #[test] +#[test] fn test__get_next_timestamp_key_for_timestamp_key() { - panic!("Not implemented yet"); + // setup + let state = RevolutRamp::contract_state_for_testing(); + + // test a multiple of LOCK_DURATION_STEP + let after = LOCK_DURATION_STEP * 42; + + // should return the same value + assert_eq!(state._get_next_timestamp_key(:after), after); } -// #[test] +#[test] fn test__get_next_timestamp_key_from_zero() { - panic!("Not implemented yet"); + // setup + let state = RevolutRamp::contract_state_for_testing(); + + // test with 0 + let after = 0; + + // should return the same value + assert_eq!(state._get_next_timestamp_key(:after), 0); } // // available_liquidity & _get_available_liquidity // -// #[test] +#[test] fn test_available_liquidity_empty() { - panic!("Not implemented yet"); + let (revolut_ramp, _) = setup(); + let liquidity_owner = constants::CALLER(); + let offchain_id = constants::REVOLUT_ID(); + let liquidity_key = LiquidityKey { owner: liquidity_owner, offchain_id }; + + // assert no liquidity available + assert_eq!(revolut_ramp.available_liquidity(:liquidity_key), 0); } -// #[test] +#[test] fn test_available_liquidity_without_requests() { - panic!("Not implemented yet"); + let (revolut_ramp, erc20) = setup(); + let liquidity_owner = constants::CALLER(); + let offchain_id = constants::REVOLUT_ID(); + let amount = 42; + let liquidity_key = LiquidityKey { owner: liquidity_owner, offchain_id }; + + // fund the account + fund_and_approve(token: erc20, recipient: liquidity_owner, spender: revolut_ramp.contract_address, :amount); + + // register offchain ID + start_cheat_caller_address(revolut_ramp.contract_address, liquidity_owner); + revolut_ramp.register(:offchain_id); + + // add liquidity + revolut_ramp.add_liquidity(:amount, :offchain_id); + + // assert liquidity is available + assert_eq!(revolut_ramp.available_liquidity(:liquidity_key), amount); } -// #[test] +#[test] fn test_available_liquidity_locked() { - panic!("Not implemented yet"); + let (revolut_ramp, erc20) = setup(); + let liquidity_owner = constants::CALLER(); + let offchain_id = constants::REVOLUT_ID(); + let amount = 42; + let liquidity_key = LiquidityKey { owner: liquidity_owner, offchain_id }; + + // fund the account + fund_and_approve(token: erc20, recipient: liquidity_owner, spender: revolut_ramp.contract_address, :amount); + + // register offchain ID + start_cheat_caller_address(revolut_ramp.contract_address, liquidity_owner); + revolut_ramp.register(:offchain_id); + + // add liquidity + revolut_ramp.add_liquidity(:amount, :offchain_id); + + // initiate retrieval, locking liquidity + revolut_ramp.initiate_liquidity_retrieval(:liquidity_key); + + // assert liquidity is not available + assert_eq!(revolut_ramp.available_liquidity(:liquidity_key), 0); } -// #[test] +#[test] fn test_available_liquidity_with_expired_requests() { - panic!("Not implemented yet"); + let (revolut_ramp, erc20) = setup(); + + // off-ramper + let liquidity_owner = constants::CALLER(); + let offchain_id = constants::REVOLUT_ID(); + let liquidity_key = LiquidityKey { owner: liquidity_owner, offchain_id }; + let amount = 100; + + // on-ramper + let withdrawer = constants::OTHER(); + let withdrawer_offchain_id = constants::REVOLUT_ID2(); + let requested_amount = 42; + + // fund the account + fund_and_approve(token: erc20, recipient: liquidity_owner, spender: revolut_ramp.contract_address, amount: amount); + + // register offchain ID + start_cheat_caller_address(revolut_ramp.contract_address, liquidity_owner); + revolut_ramp.register(:offchain_id); + + // add liquidity + revolut_ramp.add_liquidity(:amount, :offchain_id); + + // withdrawer initiates withdrawal + start_cheat_caller_address(revolut_ramp.contract_address, withdrawer); + revolut_ramp.register(offchain_id: withdrawer_offchain_id); + revolut_ramp + .initiate_liquidity_withdrawal(:liquidity_key, amount: requested_amount, offchain_id: withdrawer_offchain_id); + + // assert state before expiration + assert_eq!(revolut_ramp.all_liquidity(:liquidity_key), amount); + assert_eq!(revolut_ramp.available_liquidity(:liquidity_key), amount - requested_amount); + + // offer expires + start_cheat_block_timestamp_global(MINIMUM_LOCK_DURATION); + + // assert state after expiration + assert_eq!(revolut_ramp.all_liquidity(:liquidity_key), amount); + assert_eq!(revolut_ramp.available_liquidity(:liquidity_key), amount); } -// #[test] +#[test] fn test_available_liquidity_with_pending_requests() { - panic!("Not implemented yet"); + let (revolut_ramp, erc20) = setup(); + + // off-ramper + let liquidity_owner = constants::CALLER(); + let offchain_id = constants::REVOLUT_ID(); + let liquidity_key = LiquidityKey { owner: liquidity_owner, offchain_id }; + let amount = 100; + + // on-ramper + let withdrawer = constants::OTHER(); + let withdrawer_offchain_id = constants::REVOLUT_ID2(); + let requested_amount = 42; + + // fund the account + fund_and_approve(token: erc20, recipient: liquidity_owner, spender: revolut_ramp.contract_address, amount: amount); + + // register offchain ID + start_cheat_caller_address(revolut_ramp.contract_address, liquidity_owner); + revolut_ramp.register(:offchain_id); + + // add liquidity + revolut_ramp.add_liquidity(:amount, :offchain_id); + + // assert state before withdrawal initiated + assert_eq!(revolut_ramp.all_liquidity(:liquidity_key), amount); + assert_eq!(revolut_ramp.available_liquidity(:liquidity_key), amount); + + // withdrawer initiates withdrawal + start_cheat_caller_address(revolut_ramp.contract_address, withdrawer); + revolut_ramp.register(offchain_id: withdrawer_offchain_id); + revolut_ramp + .initiate_liquidity_withdrawal(:liquidity_key, amount: requested_amount, offchain_id: withdrawer_offchain_id); + + // assert state after withdrawal initiated + assert_eq!(revolut_ramp.all_liquidity(:liquidity_key), amount); + assert_eq!(revolut_ramp.available_liquidity(:liquidity_key), amount - requested_amount); + + // offer almost expires + start_cheat_block_timestamp_global(MINIMUM_LOCK_DURATION - 1); + + // assert state when offer is close to expiration + assert_eq!(revolut_ramp.all_liquidity(:liquidity_key), amount); + assert_eq!(revolut_ramp.available_liquidity(:liquidity_key), amount - requested_amount); } -// #[test] +#[test] fn test_available_liquidity_with_withdrawn_requests() { - panic!("Not implemented yet"); + let (revolut_ramp, erc20) = setup(); + + // off-ramper + let liquidity_owner = constants::CALLER(); + let offchain_id = constants::REVOLUT_ID(); + let liquidity_key = LiquidityKey { owner: liquidity_owner, offchain_id }; + let amount = 100; + + // on-ramper + let withdrawer = constants::OTHER(); + let withdrawer_offchain_id = constants::REVOLUT_ID2(); + let requested_amount = 42; + let proof = constants::PROOF(); + + // fund the account + fund_and_approve(token: erc20, recipient: liquidity_owner, spender: revolut_ramp.contract_address, amount: amount); + + // register offchain ID + start_cheat_caller_address(revolut_ramp.contract_address, liquidity_owner); + revolut_ramp.register(:offchain_id); + + // add liquidity + revolut_ramp.add_liquidity(:amount, :offchain_id); + + // withdrawer initiates withdrawal + start_cheat_caller_address(revolut_ramp.contract_address, withdrawer); + revolut_ramp.register(offchain_id: withdrawer_offchain_id); + revolut_ramp + .initiate_liquidity_withdrawal(:liquidity_key, amount: requested_amount, offchain_id: withdrawer_offchain_id); + + // assert state before withdrawal + assert_eq!(revolut_ramp.all_liquidity(:liquidity_key), amount); + assert_eq!(revolut_ramp.available_liquidity(:liquidity_key), amount - requested_amount); + + // withdrawers withdraws liquidity + revolut_ramp.withdraw_liquidity(:liquidity_key, offchain_id: withdrawer_offchain_id, :proof); + + // assert state after withdrawal + assert_eq!(revolut_ramp.all_liquidity(:liquidity_key), amount - requested_amount); + assert_eq!(revolut_ramp.available_liquidity(:liquidity_key), amount - requested_amount); } // #[test] @@ -1282,7 +1465,12 @@ fn test__get_available_liquidity_with_expired_requests() { revolut_ramp.locked_liquidity_shares.write((liquidity_key, expiration_date), requested_amount); // save share request - revolut_ramp.liquidity_share_request.write(withdrawer_offchain_id, LiquidityShareRequest { requestor: withdrawer, liquidity_key, amount: requested_amount, expiration_date }); + revolut_ramp + .liquidity_share_request + .write( + withdrawer_offchain_id, + LiquidityShareRequest { requestor: withdrawer, liquidity_key, amount: requested_amount, expiration_date } + ); // assert state before expiration assert_eq!(revolut_ramp._get_available_liquidity(:liquidity_key), amount - requested_amount); @@ -1325,7 +1513,12 @@ fn test__get_available_liquidity_with_pending_requests() { revolut_ramp.locked_liquidity_shares.write((liquidity_key, expiration_date), requested_amount); // save share request - revolut_ramp.liquidity_share_request.write(withdrawer_offchain_id, LiquidityShareRequest { requestor: withdrawer, liquidity_key, amount: requested_amount, expiration_date }); + revolut_ramp + .liquidity_share_request + .write( + withdrawer_offchain_id, + LiquidityShareRequest { requestor: withdrawer, liquidity_key, amount: requested_amount, expiration_date } + ); // assert state before expiration assert_eq!(revolut_ramp._get_available_liquidity(:liquidity_key), amount - requested_amount); @@ -1362,7 +1555,8 @@ fn test__get_available_liquidity_with_withdrawn_requests() { // let requested_amount = 42; // // // fund the account -// fund_and_approve(token: erc20, recipient: liquidity_owner, spender: revolut_ramp.contract_address, amount: amount); +// fund_and_approve(token: erc20, recipient: liquidity_owner, spender: revolut_ramp.contract_address, amount: +// amount); // // // register offchain ID // start_cheat_caller_address(revolut_ramp.contract_address, liquidity_owner); @@ -1379,7 +1573,8 @@ fn test__get_available_liquidity_with_withdrawn_requests() { // start_cheat_caller_address(revolut_ramp.contract_address, withdrawer); // revolut_ramp.register(offchain_id: withdrawer_offchain_id); // revolut_ramp -// .initiate_liquidity_withdrawal(:liquidity_key, amount: requested_amount, offchain_id: withdrawer_offchain_id); +// .initiate_liquidity_withdrawal(:liquidity_key, amount: requested_amount, offchain_id: +// withdrawer_offchain_id); // // // assert state after withdrawal initiated // assert_eq!(revolut_ramp.all_liquidity(:liquidity_key), amount); @@ -1410,7 +1605,8 @@ fn test__get_available_liquidity_with_withdrawn_requests() { // let proof = constants::PROOF(); // // // fund the account -// fund_and_approve(token: erc20, recipient: liquidity_owner, spender: revolut_ramp.contract_address, amount: amount); +// fund_and_approve(token: erc20, recipient: liquidity_owner, spender: revolut_ramp.contract_address, amount: +// amount); // // // register offchain ID // start_cheat_caller_address(revolut_ramp.contract_address, liquidity_owner); @@ -1423,7 +1619,8 @@ fn test__get_available_liquidity_with_withdrawn_requests() { // start_cheat_caller_address(revolut_ramp.contract_address, withdrawer); // revolut_ramp.register(offchain_id: withdrawer_offchain_id); // revolut_ramp -// .initiate_liquidity_withdrawal(:liquidity_key, amount: requested_amount, offchain_id: withdrawer_offchain_id); +// .initiate_liquidity_withdrawal(:liquidity_key, amount: requested_amount, offchain_id: +// withdrawer_offchain_id); // // // assert state before withdrawal // assert_eq!(revolut_ramp.all_liquidity(:liquidity_key), amount); @@ -1441,48 +1638,263 @@ fn test__get_available_liquidity_with_withdrawn_requests() { // all_liquidity // -// #[test] +#[test] fn test_all_liquidity_empty() { - panic!("Not implemented yet"); + let (revolut_ramp, _) = setup(); + + // off-ramper + let liquidity_owner = constants::CALLER(); + let offchain_id = constants::REVOLUT_ID(); + + let liquidity_key = LiquidityKey { owner: liquidity_owner, offchain_id }; + + // register offchain ID + start_cheat_caller_address(revolut_ramp.contract_address, liquidity_owner); + revolut_ramp.register(:offchain_id); + + // assert all_liquidity is empty + assert_eq!(revolut_ramp.all_liquidity(:liquidity_key), 0); } -// #[test] +#[test] fn test_all_liquidity() { - panic!("Not implemented yet"); + let (revolut_ramp, erc20) = setup(); + + // off-ramper + let liquidity_owner = constants::CALLER(); + let offchain_id = constants::REVOLUT_ID(); + let amount = 42; + + let liquidity_key = LiquidityKey { owner: liquidity_owner, offchain_id }; + + // fund the account + fund_and_approve(token: erc20, recipient: liquidity_owner, spender: revolut_ramp.contract_address, amount: amount); + + // register offchain ID + start_cheat_caller_address(revolut_ramp.contract_address, liquidity_owner); + revolut_ramp.register(:offchain_id); + + // add liquidity + revolut_ramp.add_liquidity(:amount, :offchain_id); + + // assert all_liquidity is amount + assert_eq!(revolut_ramp.all_liquidity(:liquidity_key), amount); } -// #[test] +#[test] fn test_all_liquidity_locked() { - panic!("Not implemented yet"); + let (revolut_ramp, erc20) = setup(); + + // off-ramper + let liquidity_owner = constants::CALLER(); + let offchain_id = constants::REVOLUT_ID(); + let amount = 42; + + let liquidity_key = LiquidityKey { owner: liquidity_owner, offchain_id }; + + // fund the account + fund_and_approve(token: erc20, recipient: liquidity_owner, spender: revolut_ramp.contract_address, :amount); + + // register offchain ID + start_cheat_caller_address(revolut_ramp.contract_address, liquidity_owner); + revolut_ramp.register(:offchain_id); + + // add liquidity + revolut_ramp.add_liquidity(amount, offchain_id); + + // locks liquidity + revolut_ramp.initiate_liquidity_retrieval(:liquidity_key); + + // assert all_liquidity is amount + assert_eq!(revolut_ramp.all_liquidity(:liquidity_key), amount); } -// #[test] +#[test] fn test_all_liquidity_with_requests() { - panic!("Not implemented yet"); + let (revolut_ramp, erc20) = setup(); + + // off-ramper + let liquidity_owner = constants::CALLER(); + let offchain_id = constants::REVOLUT_ID(); + let amount = 42; + + // on-ramper + let withdrawer = constants::OTHER(); + let withdrawer_offchain_id = constants::REVOLUT_ID2(); + + let liquidity_key = LiquidityKey { owner: liquidity_owner, offchain_id }; + + // fund the account + fund_and_approve(token: erc20, recipient: liquidity_owner, spender: revolut_ramp.contract_address, amount: amount); + + // register offchain ID + start_cheat_caller_address(revolut_ramp.contract_address, liquidity_owner); + revolut_ramp.register(:offchain_id); + + // add liquidity + revolut_ramp.add_liquidity(:amount, :offchain_id); + + // withdrawer initiates withdrawal + start_cheat_caller_address(revolut_ramp.contract_address, withdrawer); + revolut_ramp.register(offchain_id: withdrawer_offchain_id); + revolut_ramp.initiate_liquidity_withdrawal(:liquidity_key, :amount, offchain_id: withdrawer_offchain_id); + + // assert all_liquidity is amount + assert_eq!(revolut_ramp.all_liquidity(:liquidity_key), amount); } // // liquidity_share_request // -// #[test] +#[test] fn test_liquidity_share_request_empty() { - panic!("Not implemented yet"); + let (revolut_ramp, erc20) = setup(); + + // off-ramper + let liquidity_owner = constants::CALLER(); + let offchain_id = constants::REVOLUT_ID(); + let amount = 42; + + // on-ramper + let withdrawer = constants::OTHER(); + let withdrawer_offchain_id = constants::REVOLUT_ID2(); + + // fund the account + fund_and_approve(token: erc20, recipient: liquidity_owner, spender: revolut_ramp.contract_address, amount: amount); + + // register offchain ID + start_cheat_caller_address(revolut_ramp.contract_address, liquidity_owner); + revolut_ramp.register(:offchain_id); + + // add liquidity + revolut_ramp.add_liquidity(:amount, :offchain_id); + + // withdrawer initiates withdrawal + start_cheat_caller_address(revolut_ramp.contract_address, withdrawer); + revolut_ramp.register(offchain_id: withdrawer_offchain_id); + + // assert empty liquidity_share_request is None + let maybe_liquidity_share_request = revolut_ramp.liquidity_share_request(offchain_id: withdrawer_offchain_id); + assert!(maybe_liquidity_share_request.is_none()); } -// #[test] +#[test] fn test_liquidity_share_request_expired() { - panic!("Not implemented yet"); + let (revolut_ramp, erc20) = setup(); + + // off-ramper + let liquidity_owner = constants::CALLER(); + let offchain_id = constants::REVOLUT_ID(); + let amount = 42; + + // on-ramper + let withdrawer = constants::OTHER(); + let withdrawer_offchain_id = constants::REVOLUT_ID2(); + + let liquidity_key = LiquidityKey { owner: liquidity_owner, offchain_id }; + + // fund the account + fund_and_approve(token: erc20, recipient: liquidity_owner, spender: revolut_ramp.contract_address, amount: amount); + + // register offchain ID + start_cheat_caller_address(revolut_ramp.contract_address, liquidity_owner); + revolut_ramp.register(:offchain_id); + + // add liquidity + revolut_ramp.add_liquidity(:amount, :offchain_id); + + // withdrawer initiates withdrawal + start_cheat_caller_address(revolut_ramp.contract_address, withdrawer); + revolut_ramp.register(offchain_id: withdrawer_offchain_id); + revolut_ramp.initiate_liquidity_withdrawal(:liquidity_key, :amount, offchain_id: withdrawer_offchain_id); + + // offer expires + start_cheat_block_timestamp_global(MINIMUM_LOCK_DURATION); + + // assert expired liquidity_share_request is None + let maybe_liquidity_share_request = revolut_ramp.liquidity_share_request(offchain_id: withdrawer_offchain_id); + assert!(maybe_liquidity_share_request.is_none()); } -// #[test] +#[test] fn test_liquidity_share_request_valid() { - panic!("Not implemented yet"); + let (revolut_ramp, erc20) = setup(); + + // off-ramper + let liquidity_owner = constants::CALLER(); + let offchain_id = constants::REVOLUT_ID(); + let amount = 42; + + // on-ramper + let withdrawer = constants::OTHER(); + let withdrawer_offchain_id = constants::REVOLUT_ID2(); + + let liquidity_key = LiquidityKey { owner: liquidity_owner, offchain_id }; + let expected_liquidity_share_request = LiquidityShareRequest { + requestor: withdrawer, amount, liquidity_key, expiration_date: MINIMUM_LOCK_DURATION, + }; + + // fund the account + fund_and_approve(token: erc20, recipient: liquidity_owner, spender: revolut_ramp.contract_address, amount: amount); + + // register offchain ID + start_cheat_caller_address(revolut_ramp.contract_address, liquidity_owner); + revolut_ramp.register(:offchain_id); + + // add liquidity + revolut_ramp.add_liquidity(:amount, :offchain_id); + + // withdrawer initiates withdrawal + start_cheat_caller_address(revolut_ramp.contract_address, withdrawer); + revolut_ramp.register(offchain_id: withdrawer_offchain_id); + revolut_ramp.initiate_liquidity_withdrawal(:liquidity_key, :amount, offchain_id: withdrawer_offchain_id); + + // assert liquidity_share_request + let liquidity_share_request = revolut_ramp + .liquidity_share_request(withdrawer_offchain_id) + .expect('liquidity_share_req expected'); + + assert_eq!(liquidity_share_request, expected_liquidity_share_request); } -// #[test] +#[test] fn test_liquidity_share_request_withdrawn() { - panic!("Not implemented yet"); + let (revolut_ramp, erc20) = setup(); + + // off-ramper + let liquidity_owner = constants::CALLER(); + let offchain_id = constants::REVOLUT_ID(); + let amount = 42; + + // on-ramper + let withdrawer = constants::OTHER(); + let withdrawer_offchain_id = constants::REVOLUT_ID2(); + + let liquidity_key = LiquidityKey { owner: liquidity_owner, offchain_id }; + let proof = constants::PROOF(); + + // fund the account + fund_and_approve(token: erc20, recipient: liquidity_owner, spender: revolut_ramp.contract_address, amount: amount); + + // register offchain ID + start_cheat_caller_address(revolut_ramp.contract_address, liquidity_owner); + revolut_ramp.register(:offchain_id); + + // add liquidity + revolut_ramp.add_liquidity(:amount, :offchain_id); + + // withdrawer initiates withdrawal + start_cheat_caller_address(revolut_ramp.contract_address, withdrawer); + revolut_ramp.register(offchain_id: withdrawer_offchain_id); + revolut_ramp.initiate_liquidity_withdrawal(:liquidity_key, :amount, offchain_id: withdrawer_offchain_id); + + // withdrawer withdraws + revolut_ramp.withdraw_liquidity(:liquidity_key, offchain_id: withdrawer_offchain_id, :proof); + + // assert withdrawned liquidity_share_request is None + let maybe_liquidity_share_request = revolut_ramp.liquidity_share_request(withdrawer_offchain_id); + assert!(maybe_liquidity_share_request.is_none()); } //