Skip to content

Commit

Permalink
Added simple AMM to defi crate (#261)
Browse files Browse the repository at this point in the history
* Added simple amm to defi crate

* rename to constant_product_market

* remove debug import
  • Loading branch information
broody authored Apr 28, 2023
1 parent 2819ed7 commit 5851f5a
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 0 deletions.
2 changes: 2 additions & 0 deletions crates/dojo-defi/src/constant_product_market.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
mod components;
mod systems;
79 changes: 79 additions & 0 deletions crates/dojo-defi/src/constant_product_market/components.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
use traits::Into;
use traits::TryInto;
use option::OptionTrait;

const SCALING_FACTOR: u128 = 10000_u128;

#[derive(Component)]
struct Cash {
amount: u128,
}

#[derive(Component)]
struct Item {
quantity: usize,
}

#[derive(Component)]
struct Market {
cash_amount: u128,
item_quantity: usize,
}

trait MarketTrait {
fn buy(self: @Market, quantity: usize) -> u128;
fn sell(self: @Market, quantity: usize) -> u128;
}

impl MarketImpl of MarketTrait {
fn buy(self: @Market, quantity: usize) -> u128 {
assert(quantity < *self.item_quantity, 'not enough liquidity');
let (quantity, available, cash) = normalize(quantity, self);
let k = cash * available;
let cost = (k / (available - quantity)) - cash;
cost
}

fn sell(self: @Market, quantity: usize) -> u128 {
let (quantity, available, cash) = normalize(quantity, self);
let k = cash * available;
let payout = cash - (k / (available + quantity));
payout
}
}

fn normalize(quantity: usize, market: @Market) -> (u128, u128, u128) {
let quantity: u128 = quantity.into().try_into().unwrap() * SCALING_FACTOR;
let available: u128 = (*market.item_quantity).into().try_into().unwrap() * SCALING_FACTOR;
(quantity, available, *market.cash_amount)
}


#[test]
#[should_panic(expected: ('not enough liquidity', ))]
fn test_not_enough_quantity() {
let market = Market {
cash_amount: SCALING_FACTOR * 1_u128, item_quantity: 1_usize
}; // pool 1:1
let cost = market.buy(10_usize);
}

#[test]
#[available_gas(100000)]
fn test_market_buy() {
let market = Market {
cash_amount: SCALING_FACTOR * 1_u128, item_quantity: 10_usize
}; // pool 1:10
let cost = market.buy(5_usize);
assert(cost == SCALING_FACTOR * 1_u128, 'wrong cost');
}

#[test]
#[available_gas(100000)]
fn test_market_sell() {
let market = Market {
cash_amount: SCALING_FACTOR * 1_u128, item_quantity: 10_usize
}; // pool 1:10
let payout = market.sell(5_usize);
assert(payout == 3334_u128, 'wrong payout');
}
87 changes: 87 additions & 0 deletions crates/dojo-defi/src/constant_product_market/systems.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#[system]
mod Buy {
use traits::Into;
use array::ArrayTrait;
use dojo_defi::constant_product_market::components::Item;
use dojo_defi::constant_product_market::components::Cash;
use dojo_defi::constant_product_market::components::Market;
use dojo_defi::constant_product_market::components::MarketTrait;

fn execute(game_id: felt252, item_id: felt252, quantity: usize) {
let player: felt252 = starknet::get_caller_address().into();

let cash_sk: Query = (game_id, (player)).into();
let player_cash = commands::<Cash>::entity(cash_sk);

let market_sk: Query = (game_id, (item_id)).into();
let market = commands::<Market>::entity(market_sk);

let cost = market.buy(quantity);
assert(cost < player_cash.amount, 'not enough cash');

// update market
commands::set_entity(
market_sk,
(Market {
cash_amount: market.cash_amount + cost,
item_quantity: market.item_quantity - quantity,
})
);

// update player cash
commands::set_entity(cash_sk, (Cash { amount: player_cash.amount - cost }));

// update player item
let item_sk: Query = (game_id, (player, item_id)).into();
let maybe_item = commands::<Item>::try_entity(item_sk);
let player_quantity = match maybe_item {
Option::Some(item) => item.quantity + quantity,
Option::None(_) => quantity,
};
commands::set_entity(item_sk, (Item { quantity: player_quantity }));
}
}

#[system]
mod Sell {
use traits::Into;
use array::ArrayTrait;
use dojo_defi::constant_product_market::components::Item;
use dojo_defi::constant_product_market::components::Cash;
use dojo_defi::constant_product_market::components::Market;
use dojo_defi::constant_product_market::components::MarketTrait;

fn execute(game_id: felt252, item_id: felt252, quantity: usize) {
let player: felt252 = starknet::get_caller_address().into();

let item_sk: Query = (game_id, (player, item_id)).into();
let maybe_item = commands::<Item>::try_entity(item_sk);
let player_quantity = match maybe_item {
Option::Some(item) => item.quantity,
Option::None(_) => 0_u32,
};
assert(player_quantity >= quantity, 'not enough items');

let cash_sk: Query = (game_id, (player)).into();
let player_cash = commands::<Cash>::entity(cash_sk);

let market_sk: Query = (game_id, (item_id)).into();
let market = commands::<Market>::entity(market_sk);
let payout = market.sell(quantity);

// update market
commands::set_entity(
market_sk,
(Market {
cash_amount: market.cash_amount - payout,
item_quantity: market.item_quantity + quantity
})
);

// update player cash
commands::set_entity(cash_sk, (Cash { amount: player_cash.amount + payout }));

// update player item
commands::set_entity(item_sk, (Item { quantity: player_quantity - quantity }));
}
}
1 change: 1 addition & 0 deletions crates/dojo-defi/src/lib.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mod constant_product_market;

0 comments on commit 5851f5a

Please sign in to comment.