Skip to content

Commit

Permalink
liquidy retreival: initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
chachaleo authored and 0xChqrles committed Sep 17, 2024
1 parent 7c9a046 commit 99f0b79
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 60 deletions.
40 changes: 2 additions & 38 deletions contracts/src/components/escrow/escrow.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -24,35 +24,6 @@ pub mod EscrowComponent {
pub const INSUFFICIENT_BALANCE: felt252 = 'Insufficient deposit balance';
}

//
// EVENTS
//
#[event]
#[derive(Drop, starknet::Event)]
pub enum Event {
Locked: Locked,
UnLocked: UnLocked
}

/// Emitted when the escrow is locked
#[derive(Drop, starknet::Event)]
pub struct Locked {
#[key]
pub token: ContractAddress,
pub from: ContractAddress,
pub amount: u256,
}

/// Emitted when the escrow is unlocked
#[derive(Drop, starknet::Event)]
pub struct UnLocked {
#[key]
pub token: ContractAddress,
pub from: ContractAddress,
pub to: ContractAddress,
pub amount: u256,
}

//
// Escrow impl
//
Expand All @@ -61,9 +32,7 @@ pub mod EscrowComponent {
impl Escrow<
TContractState, +HasComponent<TContractState>, +Drop<TContractState>,
> of interface::IEscrow<ComponentState<TContractState>> {
fn lock_from(
ref self: ComponentState<TContractState>, from: ContractAddress, token: ContractAddress, amount: u256
) {
fn lock(ref self: ComponentState<TContractState>, from: ContractAddress, token: ContractAddress, amount: u256) {
let locked_amount = self.deposits.read((from, token));

// transfers funds to escrow
Expand All @@ -72,12 +41,9 @@ pub mod EscrowComponent {
erc20_dispatcher.transfer_from(from, get_contract_address(), amount);

self.deposits.write((from, token), amount + locked_amount);

// emit event
self.emit(Locked { token, from, amount });
}

fn unlock_to(
fn unlock(
ref self: ComponentState<TContractState>,
from: ContractAddress,
to: ContractAddress,
Expand All @@ -100,8 +66,6 @@ pub mod EscrowComponent {

// update locked amount
self.deposits.write((from, token), locked_amount - amount);
// emit event
self.emit(UnLocked { token, from, to, amount });
}
}
}
14 changes: 7 additions & 7 deletions contracts/src/components/escrow/escrow_test.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ fn test_lock() {

let mut escrow: ComponentState = Default::default();

escrow.lock_from(constants::SPENDER(), token_dispatcher.contract_address, 42);
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);
Expand Down Expand Up @@ -62,14 +62,14 @@ fn test_lock_unlock() {

let mut escrow: ComponentState = Default::default();

escrow.lock_from(constants::SPENDER(), token_dispatcher.contract_address, 42);
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_to(constants::SPENDER(), constants::RECIPIENT(), token_dispatcher.contract_address, 42);
escrow.unlock(constants::SPENDER(), constants::RECIPIENT(), token_dispatcher.contract_address, 42);

assert_eq!(token_dispatcher.balance_of(constants::SPENDER()), 58);
assert_eq!(token_dispatcher.balance_of(constants::RECIPIENT()), 42);
Expand Down Expand Up @@ -123,19 +123,19 @@ fn test_lock_unlock_greater_than_balance() {

let mut escrow: ComponentState = Default::default();

escrow.lock_from(constants::SPENDER(), token_dispatcher.contract_address, 42);
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_to(constants::SPENDER(), constants::RECIPIENT(), token_dispatcher.contract_address, 420);
escrow.unlock(constants::SPENDER(), constants::RECIPIENT(), token_dispatcher.contract_address, 420);
}

#[test]
#[should_panic(expected: 'ERC20: insufficient allowance')]
fn test_lock_from_unallowed_caller() {
fn test_lock_unallowed_caller() {
let token_dispatcher = utils::setup_erc20(recipient: constants::OWNER());
start_cheat_caller_address(token_dispatcher.contract_address, constants::OWNER());

Expand All @@ -149,5 +149,5 @@ fn test_lock_from_unallowed_caller() {

let mut escrow: ComponentState = Default::default();

escrow.lock_from(constants::SPENDER(), token_dispatcher.contract_address, 42);
escrow.lock(constants::SPENDER(), token_dispatcher.contract_address, 42);
}
4 changes: 2 additions & 2 deletions contracts/src/components/escrow/interface.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ use starknet::ContractAddress;

#[starknet::interface]
pub trait IEscrow<TState> {
fn lock_from(ref self: TState, from: ContractAddress, token: ContractAddress, amount: u256);
fn unlock_to(ref self: TState, from: ContractAddress, to: ContractAddress, token: ContractAddress, amount: u256);
fn lock(ref self: TState, from: ContractAddress, token: ContractAddress, amount: u256);
fn unlock(ref self: TState, from: ContractAddress, to: ContractAddress, token: ContractAddress, amount: u256);
}
3 changes: 3 additions & 0 deletions contracts/src/contracts/ramps/revolut.cairo
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
pub mod interface;
pub mod revolut;

#[cfg(test)]
pub mod revolut_test;
6 changes: 4 additions & 2 deletions contracts/src/contracts/ramps/revolut/interface.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ use starknet::ContractAddress;
use zkramp::components::registry::interface::OffchainId;

#[derive(Drop, Serde)]
struct Proof {
pub struct Proof {
foo: felt252
}

#[derive(Drop, Copy, Hash)]
#[derive(Drop, Copy, Hash, Serde)]
pub struct LiquidityKey {
pub owner: ContractAddress,
pub offchain_id: OffchainId,
Expand All @@ -15,4 +15,6 @@ pub struct LiquidityKey {
#[starknet::interface]
pub trait IZKRampLiquidity<TState> {
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);
}
87 changes: 76 additions & 11 deletions contracts/src/contracts/ramps/revolut/revolut.cairo
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
#[starknet::contract]
pub mod RevolutRamp {
use core::num::traits::Zero;
use core::starknet::storage::{StoragePointerReadAccess};
use openzeppelin::access::ownable::OwnableComponent;
use starknet::storage::Map;
use starknet::{ContractAddress, get_caller_address};
use zkramp::components::escrow::escrow::EscrowComponent;
use zkramp::components::registry::interface::{OffchainId, IRegistry};
use zkramp::components::registry::registry::RegistryComponent;
use zkramp::contracts::ramps::revolut::interface::{LiquidityKey, IZKRampLiquidity};

component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);
component!(path: RegistryComponent, storage: registry, event: RegistryEvent);
component!(path: EscrowComponent, storage: escrow, event: EscrowEvent);

// Ownable
#[abi(embed_v0)]
Expand All @@ -20,6 +23,9 @@ pub mod RevolutRamp {
#[abi(embed_v0)]
impl RegistryImpl = RegistryComponent::RegistryImpl<ContractState>;

// Escrow
impl EscrowImplImpl = EscrowComponent::EscrowImpl<ContractState>;

//
// Storage
//
Expand All @@ -30,9 +36,13 @@ pub mod RevolutRamp {
ownable: OwnableComponent::Storage,
#[substorage(v0)]
registry: RegistryComponent::Storage,
#[substorage(v0)]
escrow: EscrowComponent::Storage,
token: ContractAddress,
// liquidity_key -> amount
liquidity: Map::<LiquidityKey, u256>,
// liquidity_key -> is_locked
locked_liquidity: Map::<LiquidityKey, bool>,
}

//
Expand All @@ -42,30 +52,50 @@ pub mod RevolutRamp {
pub mod Errors {
pub const NOT_REGISTERED: felt252 = 'Caller is not registered';
pub const INVALID_AMOUNT: felt252 = 'Invalid amount';
pub const WRONG_CALLER_ADDRESS: felt252 = 'Wrong caller address';
pub const EMPTY_LIQUIDITY_retrievAL: felt252 = 'Empty liquidity retrieval';
pub const LOCKED_LIQUIDITY_retrievAL: felt252 = 'Locked liquidity retrieval';
}

//
// Events
//

// Emitted when liquidity is added
#[derive(Drop, starknet::Event)]
pub struct LiquidityAdded {
#[key]
pub owner: ContractAddress,
#[key]
pub offchain_id: OffchainId,
pub amount: u256,
}

#[event]
#[derive(Drop, starknet::Event)]
enum Event {
#[flat]
OwnableEvent: OwnableComponent::Event,
#[flat]
RegistryEvent: RegistryComponent::Event,
#[flat]
EscrowEvent: EscrowComponent::Event,
LiquidityAdded: LiquidityAdded,
LiquidityLocked: LiquidityLocked,
LiquidityRetrieved: LiquidityRetrieved,
}

// Emitted when liquidity is added
#[derive(Drop, starknet::Event)]
pub struct LiquidityAdded {
#[key]
pub liquidity_key: LiquidityKey,
pub amount: u256,
}

// Emitted when liquidity is locked
#[derive(Drop, starknet::Event)]
pub struct LiquidityLocked {
#[key]
pub liquidity_key: LiquidityKey,
}

// Emitted when liquidity is retrieved
#[derive(Drop, starknet::Event)]
pub struct LiquidityRetrieved {
#[key]
pub liquidity_key: LiquidityKey,
pub amount: u256,
}

//
Expand All @@ -89,6 +119,7 @@ pub mod RevolutRamp {
/// just increase the locked amount.
fn add_liquidity(ref self: ContractState, amount: u256, offchain_id: OffchainId) {
let caller = get_caller_address();
let token = self.token.read();

// assert caller registered the offchain ID
assert(self.registry.is_registered(contract_address: caller, :offchain_id), Errors::NOT_REGISTERED);
Expand All @@ -101,8 +132,42 @@ pub mod RevolutRamp {
let existing_amount = self.liquidity.read(liquidity_key);
self.liquidity.write(liquidity_key, existing_amount + amount);

// unlocks liquidity
self.locked_liquidity.write(liquidity_key, true);

// use the escrow to lock the funds
self.escrow.lock(from: caller, :token, :amount);

// Emit LiquidityAdded event
self.emit(LiquidityAdded { owner: caller, offchain_id, amount });
self.emit(LiquidityAdded { liquidity_key, amount });
}

fn initiate_liquidity_retrieval(ref self: ContractState, liquidity_key: LiquidityKey) {
let caller = get_caller_address();

// asserts liquidity amount is non null
assert(self.liquidity.read(liquidity_key).is_non_zero(), Errors::EMPTY_LIQUIDITY_retrievAL);
// asserts caller is the liquidity owner
assert(liquidity_key.owner == caller, Errors::WRONG_CALLER_ADDRESS);

// locks liquidity
self.locked_liquidity.write(liquidity_key, true);

// emits LiquidityLocked event
self.emit(LiquidityLocked { liquidity_key });
}

fn retrieve_liquidity(ref self: ContractState, liquidity_key: LiquidityKey) {
let caller = get_caller_address();

let token = self.token.read();
let amount = self.liquidity.read(liquidity_key);

// use the escrow to unlock the funds
self.escrow.unlock(from: caller, to: caller, :token, :amount);

// emits Liquidityretrieved event
self.emit(LiquidityRetrieved { liquidity_key, amount });
}
}
}
41 changes: 41 additions & 0 deletions contracts/src/contracts/ramps/revolut/revolut_test.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use core::hash::{HashStateTrait, HashStateExTrait};
use core::poseidon::PoseidonTrait;

use core::starknet::{ContractAddress, contract_address_const, get_caller_address};

use snforge_std::{declare, ContractClassTrait, start_cheat_caller_address, test_address};
use zkramp::contracts::ramps::revolut::interface::{IzkRampABIDispatcher, IzkRampABIDispatcherTrait};
use zkramp::contracts::ramps::revolut::revolut::RevolutRamp::RevolutImpl;

use zkramp::tests::constants;

fn deploy_revolut_ramp() -> (IzkRampABIDispatcher, ContractAddress) {
let contract = declare("RevolutRamp").unwrap();
let owner: ContractAddress = contract_address_const::<'owner'>();
let escrow: ContractAddress = contract_address_const::<'escrow'>();

let mut constructor_calldata = array![owner.into(), escrow.into()];

let (contract_address, _) = contract.deploy(@constructor_calldata).unwrap();

let dispatcher = IzkRampABIDispatcher { contract_address };

(dispatcher, contract_address)
}

#[test]
#[should_panic(expected: 'Empty liquidity retrieval')]
fn test_retrieve_uninitialized_liquidity_should_panic() {
let test_address: ContractAddress = test_address();

start_cheat_caller_address(test_address, constants::CALLER());

let (revolut_ramp, _) = deploy_revolut_ramp();

let liquidity_id = PoseidonTrait::new()
.update_with(get_caller_address())
.update_with(constants::REVOLUT_ID())
.finalize();

revolut_ramp.retrieve_liquidity(liquidity_id);
}

0 comments on commit 99f0b79

Please sign in to comment.