Skip to content

Commit

Permalink
Merge branch 'main' into tlee/order-matching
Browse files Browse the repository at this point in the history
  • Loading branch information
leecchh authored Apr 26, 2024
2 parents 223c77c + 213cbcb commit 0c635a1
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 44 deletions.
10 changes: 7 additions & 3 deletions deepbook/sources/deepbook.move
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ module deepbook::deepbook {
use deepbook::{
state::State,
pool::{Order, Pool, DEEP},
account::Account,
account::{Account, TradeProof},
};

// POOL MANAGEMENT
Expand Down Expand Up @@ -125,6 +125,7 @@ module deepbook::deepbook {
public fun place_limit_order<BaseAsset, QuoteAsset>(
pool: &mut Pool<BaseAsset, QuoteAsset>,
account: &mut Account,
proof: &TradeProof,
client_order_id: u64,
price: u64,
quantity: u64,
Expand All @@ -135,6 +136,7 @@ module deepbook::deepbook {
): (u64, u64, u128) {
pool.place_limit_order(
account,
proof,
client_order_id,
price,
quantity,
Expand Down Expand Up @@ -167,19 +169,21 @@ module deepbook::deepbook {
public fun cancel_order<BaseAsset, QuoteAsset>(
pool: &mut Pool<BaseAsset, QuoteAsset>,
account: &mut Account,
proof: &TradeProof,
client_order_id: u128,
ctx: &mut TxContext,
): Order {
pool.cancel_order(account, client_order_id, ctx)
pool.cancel_order(account, proof, client_order_id, ctx)
}

/// Public facing function to cancel all orders.
public fun cancel_all_orders<BaseAsset, QuoteAsset>(
pool: &mut Pool<BaseAsset, QuoteAsset>,
account: &mut Account,
proof: &TradeProof,
ctx: &mut TxContext,
): vector<Order> {
pool.cancel_all(account, ctx)
pool.cancel_all(account, proof, ctx)
}

/// Public facing function to get open orders for a user.
Expand Down
149 changes: 121 additions & 28 deletions deepbook/sources/pool/account.move
Original file line number Diff line number Diff line change
@@ -1,57 +1,126 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

// TODO: I think it might make more sense to represent ownership by a "Capability",
// instead of by address. That allows for flexible access control (someone could wrap their AccountCap)
// and pass it to others.
// Is this intented to be a shared object or an owned object? Important: You cannot promote owned to shared!

/// The Account is a shared object and holds all of the balances for a user.
/// It is passed into Pools for placing orders. All Pools can desposit and withdraw from the account.
/// When performing security checks, we need to ensure owned objects such as a Capability are not used.
/// Owned objects cause wallets to be locked when trading at a high frequency.
/// The Account is a shared object that holds all of the balances for a user. A combination of `Account` and
/// `TradeProof` are passed into a pool to perform trades. A `TradeProof` can be generated in two ways: by the
/// owner directly, or by any `TradeCap` owner. The owner can generate a `TradeProof` without the risk of
/// equivocation. The `TradeCap` owner, due to it being an owned object, risks equivocation when generating
/// a `TradeProof`. Generally, a high frequency trading engine will trade as the default owner.
module deepbook::account {
use sui::{
bag::{Self, Bag},
balance::Balance,
coin::Coin,
};

//// The account doesn't have enough funds to be withdrawn.
const EAccountBalanceTooLow: u64 = 0;
/// The account doesn't have the balance.
const ENoBalance: u64 = 1;
const EInvalidOwner: u64 = 0;
const EInvalidTrader: u64 = 1;
const EInvalidProof: u64 = 2;
const EAccountBalanceTooLow: u64 = 3;
const ENoBalance: u64 = 4;
const EMaxTradeCapsReached: u64 = 5;
const ETradeCapNotInList: u64 = 6;

// TODO: use Bag instead of direct dynamic fields
/// Owned by user, this is what's passed into pools
public struct Account has key, store {
const MAX_TRADE_CAPS: u64 = 1000;

/// A shared object that is passed into pools for placing orders.
public struct Account has key {
id: UID,
/// The owner of the account.
owner: address,
/// Stores the Coin Balances for this account.
balances: Bag,
allow_listed: vector<ID>,
}

/// Identifier for balance
/// Balance identifier.
public struct BalanceKey<phantom T> has store, copy, drop {}

/// Create an individual account
/// Owners of a `TradeCap` need to get a `TradeProof` to trade across pools in a single PTB (drops after).
public struct TradeCap has key, store {
id: UID,
account_id: ID,
}

/// Account owner and `TradeCap` owners can generate a `TradeProof`.
/// `TradeProof` is used to validate the account when trading on DeepBook.
public struct TradeProof has drop {
account_id: ID,
}

public fun new(ctx: &mut TxContext): Account {
// validate that this user hasn't reached account limit
Account {
id: object::new(ctx),
owner: ctx.sender(),
balances: bag::new(ctx),
allow_listed: vector[],
}
}

public fun share(account: Account) {
transfer::share_object(account);
}

/// Mint a `TradeCap`, only owner can mint a `TradeCap`.
public fun mint_trade_cap(account: &mut Account, ctx: &mut TxContext): TradeCap {
account.validate_owner(ctx);
assert!(account.allow_listed.length() < MAX_TRADE_CAPS, EMaxTradeCapsReached);

let id = object::new(ctx);
account.allow_listed.push_back(id.to_inner());

TradeCap {
id,
account_id: object::id(account),
}
}

/// Revoke a `TradeCap`. Only the owner can revoke a `TradeCap`.
public fun revoke_trade_cap(account: &mut Account, trade_cap_id: &ID, ctx: &TxContext) {
account.validate_owner(ctx);

let (exists, idx) = account.allow_listed.index_of(trade_cap_id);
assert!(exists, ETradeCapNotInList);
account.allow_listed.swap_remove(idx);
}

/// Generate a `TradeProof` by the owner. The owner does not require a capability
/// and can generate TradeProofs without the risk of equivocation.
public fun generate_proof_as_owner(account: &mut Account, ctx: &TxContext): TradeProof {
account.validate_owner(ctx);

TradeProof {
account_id: object::id(account),
}
}

/// Generate a `TradeProof` with a `TradeCap`.
/// Risk of equivocation since `TradeCap` is an owned object.
public fun generate_proof_as_trader(account: &mut Account, trade_cap: &TradeCap): TradeProof {
account.validate_trader(trade_cap);

TradeProof {
account_id: object::id(account),
}
}

/// Deposit funds to an account.
/// TODO: security checks.
/// TODO: Pool can deposit.
/// Deposit funds to an account. Only owner can call this directly.
public fun deposit<T>(
account: &mut Account,
coin: Coin<T>,
ctx: &mut TxContext,
) {
let proof = generate_proof_as_owner(account, ctx);

account.deposit_with_proof(&proof, coin);
}

/// Deposit funds to an account. Pool will call this to deposit funds.
public(package) fun deposit_with_proof<T>(
account: &mut Account,
proof: &TradeProof,
coin: Coin<T>,
) {
proof.validate_proof(account);

let key = BalanceKey<T> {};
let to_deposit = coin.into_balance();

Expand All @@ -63,14 +132,26 @@ module deepbook::account {
}
}

/// Withdraw funds from an account.
/// TODO: security checks.
/// TODO: Pool can withdraw.
/// Withdraw funds from an account. Only owner can call this directly.
public fun withdraw<T>(
account: &mut Account,
amount: u64,
ctx: &mut TxContext,
): Coin<T> {
let proof = generate_proof_as_owner(account, ctx);

account.withdraw_with_proof(&proof, amount, ctx)
}

/// Withdraw funds from an account. Pool will call this to withdraw funds.
public(package) fun withdraw_with_proof<T>(
account: &mut Account,
proof: &TradeProof,
amount: u64,
ctx: &mut TxContext,
): Coin<T> {
proof.validate_proof(account);

let key = BalanceKey<T> {};
assert!(account.balances.contains(key), ENoBalance);
let acc_balance: &mut Balance<T> = &mut account.balances[key];
Expand All @@ -79,8 +160,20 @@ module deepbook::account {
acc_balance.split(amount).into_coin(ctx)
}

/// Returns the owner of the account
/// Returns the owner of the account.
public fun owner(account: &Account): address {
account.owner
}

fun validate_owner(account: &Account, ctx: &TxContext) {
assert!(ctx.sender() == account.owner(), EInvalidOwner);
}

fun validate_trader(account: &Account, trade_cap: &TradeCap) {
assert!(account.allow_listed.contains(object::borrow_id(trade_cap)), EInvalidTrader);
}

fun validate_proof(proof: &TradeProof, account: &Account) {
assert!(object::id(account) == proof.account_id, EInvalidProof);
}
}
35 changes: 22 additions & 13 deletions deepbook/sources/pool/pool.move
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ module deepbook::pool {
pool_state::{Self, PoolState, PoolEpochState},
deep_price::{Self, DeepPrice},
big_vector::{Self, BigVector},
account::Account,
account::{Account, TradeProof},
user::User,
utils::{Self, encode_order_id},
math,
Expand Down Expand Up @@ -184,6 +184,7 @@ module deepbook::pool {
public(package) fun place_limit_order<BaseAsset, QuoteAsset>(
self: &mut Pool<BaseAsset, QuoteAsset>,
account: &mut Account,
proof: &TradeProof,
client_order_id: u64,
price: u64,
quantity: u64, // in base asset
Expand Down Expand Up @@ -599,6 +600,7 @@ module deepbook::pool {
public(package) fun cancel_order<BaseAsset, QuoteAsset>(
self: &mut Pool<BaseAsset, QuoteAsset>,
account: &mut Account,
proof: &TradeProof,
order_id: u128,
ctx: &mut TxContext,
): Order {
Expand All @@ -613,25 +615,25 @@ module deepbook::pool {
if (order_cancelled.is_bid) {
// deposit quote asset back into user account
let quote_asset_quantity = math::mul(order_cancelled.quantity, order_cancelled.price);
self.withdraw_quote(account, quote_asset_quantity, ctx)
self.withdraw_quote(account, proof, quote_asset_quantity, ctx)
} else {
// deposit base asset back into user account
self.withdraw_base(account, order_cancelled.quantity, ctx)
self.withdraw_base(account, proof, order_cancelled.quantity, ctx)
};

// withdraw fees into user account
// if pool is verified at the time of order placement, fees are in deepbook tokens
if (order_cancelled.fee_is_deep) {
// withdraw deepbook fees
self.withdraw_deep(account, order_cancelled.fee_quantity, ctx)
self.withdraw_deep(account, proof, order_cancelled.fee_quantity, ctx)
} else if (order_cancelled.is_bid) {
// withdraw quote asset fees
// can be combined with withdrawal above, separate now for clarity
self.withdraw_quote(account, order_cancelled.fee_quantity, ctx)
self.withdraw_quote(account, proof, order_cancelled.fee_quantity, ctx)
} else {
// withdraw base asset fees
// can be combined with withdrawal above, separate now for clarity
self.withdraw_base(account, order_cancelled.fee_quantity, ctx)
self.withdraw_base(account, proof, order_cancelled.fee_quantity, ctx)
};

// Emit order cancelled event
Expand Down Expand Up @@ -665,6 +667,7 @@ module deepbook::pool {
public(package) fun cancel_all<BaseAsset, QuoteAsset>(
self: &mut Pool<BaseAsset, QuoteAsset>,
account: &mut Account,
proof: &TradeProof,
ctx: &mut TxContext,
): vector<Order>{
let mut cancelled_orders = vector[];
Expand All @@ -675,7 +678,7 @@ module deepbook::pool {
let mut i = 0;
while (i < len) {
let key = orders_vector[i];
let cancelled_order = cancel_order(self, account, key, ctx);
let cancelled_order = cancel_order(self, account, proof, key, ctx);
cancelled_orders.push_back(cancelled_order);
i = i + 1;
};
Expand Down Expand Up @@ -869,61 +872,67 @@ module deepbook::pool {
fun deposit_base<BaseAsset, QuoteAsset>(
self: &mut Pool<BaseAsset, QuoteAsset>,
user_account: &mut Account,
proof: &TradeProof,
amount: u64,
ctx: &mut TxContext,
) {
let base = user_account.withdraw(amount, ctx);
let base = user_account.withdraw_with_proof(proof, amount, ctx);
self.base_balances.join(base.into_balance());
}

fun deposit_quote<BaseAsset, QuoteAsset>(
self: &mut Pool<BaseAsset, QuoteAsset>,
user_account: &mut Account,
proof: &TradeProof,
amount: u64,
ctx: &mut TxContext,
) {
let quote = user_account.withdraw(amount, ctx);
let quote = user_account.withdraw_with_proof(proof, amount, ctx);
self.quote_balances.join(quote.into_balance());
}

fun deposit_deep<BaseAsset, QuoteAsset>(
self: &mut Pool<BaseAsset, QuoteAsset>,
user_account: &mut Account,
proof: &TradeProof,
amount: u64,
ctx: &mut TxContext,
) {
let coin = user_account.withdraw(amount, ctx);
let coin = user_account.withdraw_with_proof(proof, amount, ctx);
self.deepbook_balance.join(coin.into_balance());
}

fun withdraw_base<BaseAsset, QuoteAsset>(
self: &mut Pool<BaseAsset, QuoteAsset>,
user_account: &mut Account,
proof: &TradeProof,
amount: u64,
ctx: &mut TxContext,
) {
let coin = self.base_balances.split(amount).into_coin(ctx);
user_account.deposit(coin);
user_account.deposit_with_proof(proof, coin);
}

fun withdraw_quote<BaseAsset, QuoteAsset>(
self: &mut Pool<BaseAsset, QuoteAsset>,
user_account: &mut Account,
proof: &TradeProof,
amount: u64,
ctx: &mut TxContext,
) {
let coin = self.quote_balances.split(amount).into_coin(ctx);
user_account.deposit(coin);
user_account.deposit_with_proof(proof, coin);
}

fun withdraw_deep<BaseAsset, QuoteAsset>(
self: &mut Pool<BaseAsset, QuoteAsset>,
user_account: &mut Account,
proof: &TradeProof,
amount: u64,
ctx: &mut TxContext,
) {
let coin = self.deepbook_balance.split(amount).into_coin(ctx);
user_account.deposit(coin);
user_account.deposit_with_proof(proof, coin);
}

#[allow(unused_function)]
Expand Down

0 comments on commit 0c635a1

Please sign in to comment.