Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

At/account #47

Merged
merged 6 commits into from
Apr 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
): 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);
}
}
Loading
Loading