Skip to content

Commit

Permalink
create a lite router example
Browse files Browse the repository at this point in the history
  • Loading branch information
moodysalem committed Feb 17, 2024
1 parent b16678a commit a7a729a
Show file tree
Hide file tree
Showing 3 changed files with 216 additions and 0 deletions.
28 changes: 28 additions & 0 deletions src/components/clear.cairo
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use ekubo::interfaces::erc20::{IERC20Dispatcher, IERC20DispatcherTrait};
use core::num::traits::{Zero};
use starknet::{ContractAddress, get_contract_address, get_caller_address};

// This component is embedded in the Router and Positions contracts
Expand All @@ -13,3 +14,30 @@ pub trait IClear<TContractState> {
self: @TContractState, token: IERC20Dispatcher, minimum: u256, recipient: ContractAddress
) -> u256;
}

#[starknet::embeddable]
pub impl ClearImpl<TContractState> of IClear<TContractState> {
#[inline(always)]
fn clear(self: @TContractState, token: IERC20Dispatcher) -> u256 {
self.clear_minimum_to_recipient(token, 0, get_caller_address())
}

#[inline(always)]
fn clear_minimum(self: @TContractState, token: IERC20Dispatcher, minimum: u256) -> u256 {
self.clear_minimum_to_recipient(token, minimum, get_caller_address())
}

#[inline(always)]
fn clear_minimum_to_recipient(
self: @TContractState, token: IERC20Dispatcher, minimum: u256, recipient: ContractAddress
) -> u256 {
let balance = token.balanceOf(get_contract_address());
if minimum.is_non_zero() {
assert(balance >= minimum, 'CLEAR_AT_LEAST_MINIMUM');
}
if balance.is_non_zero() {
token.transfer(recipient, balance);
}
balance
}
}
1 change: 1 addition & 0 deletions src/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ pub mod types {
pub mod position;
}

pub mod router_lite;
187 changes: 187 additions & 0 deletions src/router_lite.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
use ekubo::types::delta::{Delta};
use ekubo::types::i129::{i129};
use ekubo::types::keys::{PoolKey};
use starknet::{ContractAddress};

#[derive(Serde, Copy, Drop)]
pub struct RouteNode {
pub pool_key: PoolKey,
pub sqrt_ratio_limit: u256,
pub skip_ahead: u128,
}

#[derive(Serde, Copy, Drop)]
pub struct TokenAmount {
pub token: ContractAddress,
pub amount: i129,
}

#[derive(Serde, Drop)]
pub struct Swap {
pub route: Array<RouteNode>,
pub token_amount: TokenAmount,
}

#[starknet::interface]
pub trait IRouterLite<TContractState> {
// Does a single swap against a single node using tokens held by this contract, and receives the output to this contract
fn swap(ref self: TContractState, node: RouteNode, token_amount: TokenAmount) -> Delta;

// Does a multihop swap, where the output/input of each hop is passed as input/output of the next swap
// Note to do exact output swaps, the route must be given in reverse
fn multihop_swap(
ref self: TContractState, route: Array<RouteNode>, token_amount: TokenAmount
) -> Array<Delta>;

// Does multiple multihop swaps
fn multi_multihop_swap(ref self: TContractState, swaps: Array<Swap>) -> Array<Array<Delta>>;
}

#[starknet::contract]
pub mod RouterLite {
use core::array::{Array, ArrayTrait, SpanTrait};
use core::cmp::{min, max};
use core::integer::{u256_sqrt};
use core::num::traits::{Zero};
use core::option::{OptionTrait};
use core::result::{ResultTrait};
use core::traits::{Into};
use ekubo::components::clear::{ClearImpl};
use ekubo::components::shared_locker::{
consume_callback_data, handle_delta, call_core_with_callback
};
use ekubo::interfaces::core::{ICoreDispatcher, ICoreDispatcherTrait, ILocker, SwapParameters};
use ekubo::interfaces::erc20::{IERC20Dispatcher, IERC20DispatcherTrait};
use ekubo::types::i129::{i129, i129Trait};
use starknet::syscalls::{call_contract_syscall};

use starknet::{get_caller_address, get_contract_address};

use super::{ContractAddress, PoolKey, Delta, IRouterLite, RouteNode, TokenAmount, Swap};

#[abi(embed_v0)]
impl Clear = ekubo::components::clear::ClearImpl<ContractState>;

#[storage]
struct Storage {
core: ICoreDispatcher,
}

#[constructor]
fn constructor(ref self: ContractState, core: ICoreDispatcher) {
self.core.write(core);
}

#[abi(embed_v0)]
impl LockerImpl of ILocker<ContractState> {
fn locked(ref self: ContractState, id: u32, data: Span<felt252>) -> Span<felt252> {
let core = self.core.read();

let mut swaps = consume_callback_data::<Array<Swap>>(core, data);
let mut outputs: Array<Array<Delta>> = ArrayTrait::new();

loop {
match swaps.pop_front() {
Option::Some(swap) => {
let mut route = swap.route;
let mut token_amount = swap.token_amount;

let mut deltas: Array<Delta> = ArrayTrait::new();
// we track this to know how much to pay in the case of exact input and how much to pull in the case of exact output
let mut first_swap_amount: Option<TokenAmount> = Option::None;

loop {
match route.pop_front() {
Option::Some(node) => {
let is_token1 = token_amount.token == node.pool_key.token1;

let delta = core
.swap(
node.pool_key,
SwapParameters {
amount: token_amount.amount,
is_token1: is_token1,
sqrt_ratio_limit: node.sqrt_ratio_limit,
skip_ahead: node.skip_ahead,
}
);

deltas.append(delta);

if first_swap_amount.is_none() {
first_swap_amount =
if is_token1 {
Option::Some(
TokenAmount {
token: node.pool_key.token1,
amount: delta.amount1
}
)
} else {
Option::Some(
TokenAmount {
token: node.pool_key.token0,
amount: delta.amount0
}
)
}
}

token_amount =
if (is_token1) {
TokenAmount {
amount: -delta.amount0, token: node.pool_key.token0
}
} else {
TokenAmount {
amount: -delta.amount1, token: node.pool_key.token1
}
};
},
Option::None => { break (); }
};
};

let recipient = get_contract_address();

outputs.append(deltas);

let first = first_swap_amount.unwrap();
handle_delta(core, token_amount.token, -token_amount.amount, recipient);
handle_delta(core, first.token, first.amount, recipient);
},
Option::None => { break (); }
};
};

let mut serialized: Array<felt252> = array![];

Serde::serialize(@outputs, ref serialized);

serialized.span()
}
}


#[abi(embed_v0)]
impl RouterLiteImpl of IRouterLite<ContractState> {
fn swap(ref self: ContractState, node: RouteNode, token_amount: TokenAmount) -> Delta {
let mut deltas: Array<Delta> = self.multihop_swap(array![node], token_amount);
deltas.pop_front().unwrap()
}

#[inline(always)]
fn multihop_swap(
ref self: ContractState, route: Array<RouteNode>, token_amount: TokenAmount
) -> Array<Delta> {
let mut result = self.multi_multihop_swap(array![Swap { route, token_amount }]);

result.pop_front().unwrap()
}

#[inline(always)]
fn multi_multihop_swap(ref self: ContractState, swaps: Array<Swap>) -> Array<Array<Delta>> {
call_core_with_callback(self.core.read(), @swaps)
}
}
}

0 comments on commit a7a729a

Please sign in to comment.