Skip to content
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
19 changes: 16 additions & 3 deletions .claude/skills/docs-review/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,25 @@ gaps, outdated content, and stale comments.
should be valid.
- Cross-page markdown links must use reference-style
definitions (`[text][ref]` or `[text]` with a
`[text]: url` at the bottom of the file). Inline
links (`[text](url)`) are only acceptable for
same-page anchors (e.g. `[label](#anchor)`).
`[text]: url` at the bottom of the file).
Reference definitions must be placed at the end
of the file, not inline near their first usage.
Inline links (`[text](url)`) are only acceptable
for same-page anchors (e.g. `[label](#anchor)`).
- `<Include>` and `<Algorithm>` component
attributes should resolve correctly.

1. Verify that sector layout diagrams in
`docs/src/program/sectors.md` are consistent with
their data structures. For each node type (Order,
Seat, StackNode), compare the fields shown in the
ASCII diagram against the fields in the corresponding
struct definition (in `interface/src/order/mod.rs`,
`interface/src/seat/mod.rs`,
`interface/src/stack/mod.rs`). Flag any field that
is missing from the diagram, present in the diagram
but not the struct, or in the wrong order.

1. Verify that all directory trees in the docs are
current. For each tree, list the actual files on
disk and compare against the rendered tree. Flag
Expand Down
3 changes: 3 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ development topics:
- `docs/src/program/layout.md` program memory layout
- `docs/src/program/inputs.md` input format specs
- `docs/src/program/markets.md` market structure
- `docs/src/program/sectors.md` market memory sectors
- `docs/src/program/seats.md` seat data structure
- `docs/src/program/orders.md` order data structure
- `docs/src/program/algorithm-index.md` algorithm
documentation

Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions docs/.vitepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ export default {
{ text: "Layout", link: "/program/layout" },
{ text: "Inputs", link: "/program/inputs" },
{ text: "Markets", link: "/program/markets" },
{ text: "Sectors", link: "/program/sectors" },
{ text: "Seats", link: "/program/seats" },
{ text: "Orders", link: "/program/orders" },
{ text: "Algorithm Index", link: "/program/algorithm-index" },
],
},
Expand Down
4 changes: 2 additions & 2 deletions docs/algorithms/market/INIT-BASE-VAULT.tex
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
\ENSURE $r_9$ = acct
\ENSURE frame.token\_program\_id
\ENSURE frame.token\_program\_is\_2022
\ENSURE input.market.data.base\_vault\_bump = frame.bump
\ENSURE input.market\_header.base\_vault\_bump = frame.bump
\PROCEDURE{INIT-BASE-VAULT}{acct, frame}
\COMMENT{Retrieve input buffer pointers and advance to base token program account.}
\STATE input = frame.input
Expand Down Expand Up @@ -64,7 +64,7 @@
\RETURN result
\ENDIF
\COMMENT{Store derived bump in market account data.}
\STATE input.market.data.base\_vault\_bump = frame.bump
\STATE input.market\_header.base\_vault\_bump = frame.bump
\COMMENT{Advance to quote token program account.}
\STATE acct += \texttt{EmptyAccount.size}
\ENDPROCEDURE
Expand Down
4 changes: 2 additions & 2 deletions docs/algorithms/market/INIT-QUOTE-VAULT.tex
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
\REQUIRE frame.pda\_seeds[2].addr = \&frame.bump
\REQUIRE frame.pda\_seeds[2].len = \texttt{u8.size}
\REQUIRE frame.rent
\ENSURE input.market.data.quote\_vault\_bump = frame.bump
\ENSURE input.market\_header.quote\_vault\_bump = frame.bump
\PROCEDURE{INIT-QUOTE-VAULT}{acct, frame, input, input\_shifted}
\COMMENT{Check quote token program account.}
\IF{acct.duplicate == \texttt{common::account::NON\_DUP\_MARKER}}
Expand Down Expand Up @@ -62,7 +62,7 @@
\STATE frame.mint = \&input\_shifted.quote\_mint
\STATE \CALL{INIT-VAULT}{acct, frame}
\COMMENT{Store derived bump in market account data.}
\STATE input.market.data.quote\_vault\_bump = frame.bump
\STATE input.market\_header.quote\_vault\_bump = frame.bump
\ENDPROCEDURE
\end{algorithmic}
\end{algorithm}
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
\REQUIRE frame.cpi[1].info.data\_len = \texttt{common::memory::LEN\_ZERO}
\ENSURE frame.signers\_seeds.addr = \&frame.pda\_seeds
\ENSURE frame.signers\_seeds.len = \texttt{frame.pda\_seeds.n\_seeds}
\ENSURE input.market.data.next = input + \texttt{entrypoint::input\_buffer::MARKET\_DATA\_BYTES}
\ENSURE input.market.data.bump = frame.bump
\ENSURE input.market\_header.next = \texttt{\&entrypoint::input\_buffer::MARKET\_SECTORS\_START}
\ENSURE input.market\_header.bump = frame.bump
\PROCEDURE{CREATE-MARKET-ACCOUNT}{frame}
\COMMENT{Assign CPI account fields via immediates.}
\STATE frame.cpi[0].info.is\_signer = \texttt{true}
Expand Down Expand Up @@ -55,8 +55,8 @@
\STATE syscall.seeds\_len = \texttt{market::register::N\_PDA\_SIGNERS}
\STATE \CALL{sol-invoke-signed-c}{system-program::CreateAccount}
\COMMENT{Initialize market header next pointer and store derived bump.}
\STATE input.market.data.next = input + \texttt{entrypoint::input\_buffer::MARKET\_DATA\_BYTES}
\STATE input.market.data.bump = frame.bump
\STATE input.market\_header.next = \texttt{\&entrypoint::input\_buffer::MARKET\_SECTORS\_START}
\STATE input.market\_header.bump = frame.bump
\ENDPROCEDURE
\end{algorithmic}
\end{algorithm}
31 changes: 27 additions & 4 deletions docs/src/program/markets.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,32 @@ All relevant information is derived from the accounts:

<Include rs="interface::market::register#register_market_accounts"/>

The entrypoint dispatches to
[REGISTER-MARKET](#register-market), which validates the provided accounts,
derives and creates the market PDA, then initializes the base and quote
token vaults.
### Input buffer

The registration `InputBuffer` extends the base
[input buffer] header with the base and quote mint
accounts:

<Include rs="interface::market::register#register_input_buffer"/>

All fields through `Market` sit at compile-time-known
offsets from `r1` (see [`InputBufferHeader`]).
`BaseMint` and `QuoteMint` have variable-length data,
so their positions cannot be determined statically.
[MARKET-PDA-PRELUDE](#market-pda-prelude) handles this
with a dynamic offset: after reading
`input.base_mint.data_len`, it computes

```rs
frame.input_shifted = input + padded(input.base_mint.data_len)
```

From `frame.input_shifted`, all `QuoteMint` fields are
accessible at static offsets. Accounts after `QuoteMint`
(`SystemProgram`, `RentSysvar`, `BaseTokenProgram`,
`BaseVault`, `QuoteTokenProgram`, `QuoteVault`) are located
by walking forward from `frame.input_shifted` using each
account's padded data length.

## REGISTER-MARKET

Expand Down Expand Up @@ -119,6 +141,7 @@ account for the given mint, with the market as the account owner.
<Algorithm id="INIT-VAULT-TOKEN-ACCOUNT"/>

[input buffer]: inputs#input-buffer
[`InputBufferHeader`]: inputs#input-buffer
[System Program]: https://solana.com/docs/core/programs/builtin-programs#the-system-program
[Rent]: https://docs.rs/pinocchio/0.11.0/pinocchio/sysvars/rent/struct.Rent.html
[Token Program]: https://github.com/solana-program/token
Expand Down
5 changes: 5 additions & 0 deletions docs/src/program/orders.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Orders

An `Order` represents an entry on the order book.

<Include rs="interface::order#order"/>
8 changes: 8 additions & 0 deletions docs/src/program/seats.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Seats

A `Seat` is a market maker's trading account within a
market.
It holds tree pointers for lookup, the user's address,
token balances, and per-side order arrays.

<Include rs="interface::seat#seat"/>
81 changes: 81 additions & 0 deletions docs/src/program/sectors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Sectors

Market account data begins with a [`MarketHeader`]
followed by a contiguous array of fixed-size sectors. Each
sector holds one of three node types ([`Order`],
[`Seat`], or [`StackNode`]), but a node does not
necessarily occupy the entire sector.

Because the market account is at a [fixed position] in the
input buffer, its data offsets are persisted across
transactions. `MarketHeader` stores absolute SBPF pointers
into the sector array (`seats`, `asks`, `bids`, `top`,
`next`) that remain valid without recomputation.

```txt
+----------------+----------+----------+----------+-----+
| MarketHeader | Sector 0 | Sector 1 | Sector 2 | ... |
+----------------+----------+----------+----------+-----+
```

A `Sector` is a byte buffer sized to the largest node type:

<Include rs="interface::common::memory#sector"/>

The first byte of every sector is a `NodeTag` discriminant
that identifies its contents:

<Include rs="interface::common::memory#node_tag"/>

## Order

A sector holding an [`Order`] is an active node in
one of the market's order trees.

```txt
+-----+--------------------------------------------------+
| tag | (unused) |
+-----+--------------------------------------------------+
```

<Include rs="interface::order#order" collapsed/>

## Seat

A sector holding a [`Seat`] is an active node in
the market's seat tree.

```txt
+-----+--------+------+-------+------+-----+------+------+
| tag | parent | left | right | user | ... | asks | bids |
+-----+--------+------+-------+------+-----+------+------+
```

<Include rs="interface::seat#seat" collapsed/>

## StackNode

A sector holding a `StackNode` is a freed sector on the
free sector stack.

```txt
+-----+------+-------------------------------------------+
| tag | next | (unused) |
+-----+------+-------------------------------------------+
```

<Include rs="interface::stack#stack_node" collapsed/>

## Allocation

`MarketHeader.next` points to the next unallocated sector
in the memory map. `MarketHeader.top` points to the top of
the free sector stack. When a sector is freed, it becomes a
`StackNode` pushed onto the stack. New allocations pop from
the stack first; when the stack is empty, `next` advances.

[`MarketHeader`]: markets
[`Order`]: orders
[`Seat`]: seats
[`StackNode`]: #stacknode
[fixed position]: inputs#input-buffer
1 change: 1 addition & 0 deletions interface/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ dropset-macros = {path = "../macros"}
pinocchio = {workspace = true}
pinocchio-token = {workspace = true}
pinocchio-token-2022 = {workspace = true}
solana-sbpf = {workspace = true}

[package]
name = "dropset-interface"
Expand Down
49 changes: 47 additions & 2 deletions interface/src/common/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,25 @@ use crate::common::account::EmptyAccount;
use crate::common::token::InitializeAccount2;
use crate::market::MarketHeader;
use crate::market::register::CreateAccountData;
use dropset_macros::{constant_group, size_of_group};
use crate::order::Order;
use crate::seat::Seat;
use crate::stack::StackNode;
use dropset_macros::{constant_group, discriminant_enum, size_of_group, svm_data};
use pinocchio::Address as Pubkey;

// region: node_tag
/// Discriminant tag for nodes in the market memory map.
#[discriminant_enum("common/memory", "NODE_TAG")]
pub enum NodeTag {
/// Seat node.
Seat,
/// Order node.
Order,
/// Stack node.
Stack,
}
// endregion: node_tag

constant_group! {
#[prefix("DATA")]
#[inject("common/memory")]
Expand All @@ -23,12 +39,41 @@ constant_group! {
BOOL_FALSE = immediate!(0),
/// Boolean true value.
BOOL_TRUE = immediate!(1),

}
}

// region: sector
/// Sector-sized byte buffer (largest of Order, Seat, StackNode).
#[svm_data]
pub struct Sector(
[u8; {
const ORDER: usize = core::mem::size_of::<Order>();
const SEAT: usize = core::mem::size_of::<Seat>();
const STACK: usize = core::mem::size_of::<StackNode>();
if ORDER >= SEAT && ORDER >= STACK {
ORDER
} else if SEAT >= STACK {
SEAT
} else {
STACK
}
}],
);
// endregion: sector

// region: size_of_group_example
size_of_group! {
#[inject("common/memory")]
[u8, u64, Pubkey, EmptyAccount, MarketHeader, CreateAccountData, InitializeAccount2]
[
u8,
u64,
Pubkey,
EmptyAccount,
MarketHeader,
CreateAccountData,
InitializeAccount2,
Sector,
]
}
// endregion: size_of_group_example
36 changes: 21 additions & 15 deletions interface/src/entrypoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::common::account::EmptyAccount;
use crate::market::MarketHeader;
use dropset_macros::{constant_group, discriminant_enum, svm_data};
use pinocchio::account::RuntimeAccount;
use solana_sbpf::ebpf::MM_INPUT_START;

// region: discriminant_enum
/// Instruction discriminants.
Expand Down Expand Up @@ -34,9 +35,9 @@ pub struct InputBufferHeader {
pub n_accounts: u64,
pub user: EmptyAccount,
pub market: RuntimeAccount,
pub market_data_header: MarketHeader,
/// MarketHeader.next initializes to this offset.
pub market_data_bytes: u8,
pub market_header: MarketHeader,
/// MarketHeader.next initializes to an absolute pointer to this byte.
pub market_sectors_start: u8,
}
// endregion: input_buffer_header

Expand Down Expand Up @@ -66,20 +67,25 @@ constant_group! {
USER_DATA_TO_MARKET_ADDRESS = relative_offset!(
InputBufferHeader, user.data, market.address
),
/// From input buffer to market data next pointer.
MARKET_DATA_NEXT = offset!(InputBufferHeader.market_data_header.next),
/// From input buffer to market data bump.
MARKET_DATA_BUMP = offset!(InputBufferHeader.market_data_header.bump),
/// From input buffer to market data base vault bump.
MARKET_DATA_BASE_VAULT_BUMP = offset!(
InputBufferHeader.market_data_header.base_vault_bump
/// From input buffer to market header next pointer.
MARKET_HEADER_NEXT = offset!(InputBufferHeader.market_header.next),
/// From input buffer to market header bump.
MARKET_HEADER_BUMP = offset!(InputBufferHeader.market_header.bump),
/// From input buffer to market header base vault bump.
MARKET_HEADER_BASE_VAULT_BUMP = offset!(
InputBufferHeader.market_header.base_vault_bump
),
/// From input buffer to market data quote vault bump.
MARKET_DATA_QUOTE_VAULT_BUMP = offset!(
InputBufferHeader.market_data_header.quote_vault_bump
/// From input buffer to market header quote vault bump.
MARKET_HEADER_QUOTE_VAULT_BUMP = offset!(
InputBufferHeader.market_header.quote_vault_bump
),
/// From input buffer to first sector in market memory map.
MARKET_SECTORS_START = offset!(InputBufferHeader.market_sectors_start),
/// Absolute SBPF pointer to first sector in market memory map.
MARKET_SECTORS_START_PTR = wide!(
MM_INPUT_START as i64
+ core::mem::offset_of!(InputBufferHeader, market_sectors_start) as i64
),
/// From input buffer to first byte after market data header.
MARKET_DATA_BYTES = offset!(InputBufferHeader.market_data_bytes),
}
}
// endregion: constant_group_example
Loading
Loading