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
16 changes: 12 additions & 4 deletions .github/actions/install-sbpf/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,18 @@ runs:
path: '~/.cargo/bin/sbpf'
- if: 'steps.cache-sbpf.outputs.cache-hit != ''true'''
name: 'Install sbpf CLI'
run: >-
cargo install
--git https://github.com/blueshift-gg/sbpf.git
--rev ${{ inputs.sbpf-cli-revision }}
# Clone, generate a lock file with blake3 <1.8.4 to avoid
# digest 0.10/0.11 conflict, then install from the local path.
run: |
git init /tmp/sbpf
cd /tmp/sbpf
git remote add origin https://github.com/blueshift-gg/sbpf.git
git fetch --depth 1 origin ${{ inputs.sbpf-cli-revision }}
git checkout FETCH_HEAD
cargo generate-lockfile
cargo update blake3 --precise 1.8.3
cargo install --path . --locked
rm -rf /tmp/sbpf
shell: 'sh'
- if: 'steps.cache-sbpf.outputs.cache-hit != ''true'''
name: 'Save sbpf CLI cache'
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ make test # Assemble + run Mollusk tests
make asm # Assemble program to SBPF binary
make lint # Run all lints (pre-commit + prettier)
make clean # Clean all build artifacts
make docs-dev # Serve docs locally
make docs # Serve docs locally
make docs-build # Production docs build
make docs-links # Check for broken links
```
Expand Down
26 changes: 26 additions & 0 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ bindgen = "0.72.1"
heck = "0.5"
mollusk-svm = "0.11.0"
pinocchio = "0.10.2"
pinocchio-token = "0.5.0"
pinocchio-token-2022 = "0.2.0"
proc-macro2 = "1.0.106"
quote = "1.0.45"
solana-account = "3.2"
Expand Down
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.PHONY: all
.PHONY: asm
.PHONY: docs
.PHONY: clean
.PHONY: lint
.PHONY: test
Expand Down Expand Up @@ -35,7 +36,7 @@ docs-links: docs-build
--root-dir docs/.vitepress/dist 'docs/.vitepress/dist/**/*.html'

# Build and serve docs locally for development.
docs-dev:
docs:
cd docs && npm install \
&& rm -rf .vitepress/cache .vitepress/dist node_modules/.vite \
&& npx vitepress dev --open
Expand Down
12 changes: 12 additions & 0 deletions docs/.vitepress/components/Algorithm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,17 @@ onMounted(async () => {
);
container.value.insertBefore(rendered, container.value.firstChild);

// Indent comments that precede a block so they align with the
// block's first line rather than the parent control keyword.
rendered.querySelectorAll(".ps-comment").forEach((span) => {
const line = span.closest(".ps-line");
if (!line) return;
const block = line.nextElementSibling;
if (block?.classList.contains("ps-block")) {
span.style.paddingLeft = block.style.marginLeft;
}
});

// Add a class to \texttt{} spans for styling.
rendered
.querySelectorAll('span[style*="KaTeX_Typewriter"]')
Expand Down Expand Up @@ -392,6 +403,7 @@ onMounted(async () => {
.pseudocode-container :deep(.ps-linenum) {
color: var(--vp-c-text-3);
user-select: none;
width: 2.4em;
}

/* Dependency links above/below the algorithm. */
Expand Down
50 changes: 49 additions & 1 deletion docs/algorithms/REGISTER-MARKET.tex
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
\INPUT $r_4$ = insn\_len
\REQUIRE insn.discriminant == \texttt{Discriminant::RegisterMarket}
\PROCEDURE{REGISTER-MARKET}{input, insn, n\_accounts, insn\_len}
\COMMENT{Check number of accounts and instruction data length.}
\IF{n\_accounts $<$ \texttt{RegisterMarketAccounts.LEN}}
\RETURN \texttt{ErrorCode::InvalidNumberOfAccounts}
\ENDIF
Expand All @@ -35,6 +36,7 @@
\STATE frame.pda\_seeds.base.len = \texttt{Address.size}
\COMMENT{Increment input buffer by base mint padded data length for quote offsets.}
\STATE input\_shifted = input + input.base\_mint.padded\_data\_len
\STATE frame.input\_shifted = input\_shifted
\COMMENT{Check quote mint account, create signer seed.}
\IF{input\_shifted.quote\_mint.duplicate $\neq$ \texttt{account.NON\_DUP\_MARKER}}
\RETURN \texttt{ErrorCode::QuoteMintIsDuplicate}
Expand All @@ -46,14 +48,15 @@
\STATE acct = \&input\_shifted.quote\_mint
\STATE acct += quote\_mint\_padded\_data\_len + \texttt{EmptyAccount.size}
\COMMENT{Derive market PDA.}
\STATE \CALL{Store}{input}
\STATE frame.input = input
\STATE syscall.seeds = \&frame.pda\_seeds
\STATE syscall.program\_id = \&insn.program\_id
\STATE syscall.seeds\_len = \texttt{register\_misc.TRY\_FIND\_PDA\_SEEDS\_LEN}
\STATE syscall.program\_address = \&frame.pda
\STATE syscall.bump\_seed = \&frame.bump
\STATE \CALL{sol-try-find-program-address}{}
\COMMENT{Verify derived market PDA matches market account pubkey.}
\STATE input = frame.input
\IF{input.market.pubkey $\neq$ frame.market\_pda}
\RETURN \texttt{ErrorCode::InvalidMarketPubkey}
\ENDIF
Expand Down Expand Up @@ -86,6 +89,9 @@
\STATE acct\_size = \texttt{MarketHeader.size} + \texttt{account.STORAGE\_OVERHEAD}
\STATE lamports\_per\_byte = acct.data.lamports\_per\_byte
\STATE frame.create\_account\_data.lamports = acct\_size $\times$ lamports\_per\_byte
\COMMENT{Advance to base token program account.}
\STATE rent\_sysvar\_padded\_data\_len = acct.padded\_data\_len
\STATE acct += rent\_sysvar\_padded\_data\_len + \texttt{EmptyAccount.size}
\COMMENT{Assign CPI account fields via immediates.}
\STATE frame.cpi.user\_info.is\_signer = \texttt{true}
\STATE frame.cpi.user\_info.is\_writable = \texttt{true}
Expand Down Expand Up @@ -123,6 +129,48 @@
\STATE syscall.seeds = \&frame.signers\_seeds
\STATE syscall.seeds\_len = \texttt{register\_misc.N\_PDA\_SIGNERS}
\STATE \CALL{sol-invoke-signed-c}{}
\COMMENT{Retrieve input buffer pointers.}
\STATE input = frame.input
\STATE input\_shifted = frame.input\_shifted
\COMMENT{Check base token program account.}
\IF{acct.duplicate $\neq$ \texttt{account.NON\_DUP\_MARKER}}
\RETURN \texttt{ErrorCode::BaseTokenProgramIsDuplicate}
\ENDIF
\COMMENT{Verify base token program owns the base mint.}
\IF{acct.pubkey $\neq$ input.base\_mint.owner}
\RETURN \texttt{ErrorCode::BaseTokenProgramNotBaseMintOwner}
\ENDIF
\COMMENT{Verify base token program is Token Program or Token 2022.}
\IF{acct.pubkey $\neq$ \texttt{pubkey.TOKEN\_PROGRAM}}
\IF{acct.pubkey $\neq$ \texttt{pubkey.TOKEN\_2022\_PROGRAM}}
\RETURN \texttt{ErrorCode::BaseTokenProgramNotTokenProgram}
\ENDIF
\ENDIF
\COMMENT{Advance to quote token program account.}
\STATE base\_token\_program\_padded\_data\_len = acct.padded\_data\_len
\STATE acct += base\_token\_program\_padded\_data\_len + \texttt{EmptyAccount.size}
\COMMENT{Check quote token program account.}
\IF{acct.duplicate == \texttt{account.NON\_DUP\_MARKER}}
\COMMENT{Non-duplicate; verify quote token program owns the quote mint.}
\IF{acct.pubkey $\neq$ input\_shifted.quote\_mint.owner}
\RETURN \texttt{ErrorCode::NonDupQuoteTokenProgramNotQuoteMintOwner}
\ENDIF
\COMMENT{Verify quote token program is Token Program or Token 2022.}
\IF{acct.pubkey $\neq$ \texttt{pubkey.TOKEN\_PROGRAM}}
\IF{acct.pubkey $\neq$ \texttt{pubkey.TOKEN\_2022\_PROGRAM}}
\RETURN \texttt{ErrorCode::QuoteTokenProgramNotTokenProgram}
\ENDIF
\ENDIF
\ELSE
\COMMENT{Duplicate; verify position is base token program.}
\IF{acct.duplicate $\neq$ \texttt{RegisterMarketAccounts::BaseTokenProgram}}
\RETURN \texttt{ErrorCode::InvalidQuoteTokenProgramDuplicate}
\ENDIF
\COMMENT{Verify duplicate owns the quote mint.}
\IF{input.base\_mint.owner $\neq$ input\_shifted.quote\_mint.owner}
\RETURN \texttt{ErrorCode::DupQuoteTokenProgramNotQuoteMintOwner}
\ENDIF
\ENDIF
\ENDPROCEDURE
\end{algorithmic}
\end{algorithm}
11 changes: 6 additions & 5 deletions docs/src/development/build-scaffolding.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ syntax forms (parsed within the proc macro, not standalone macros):
- `signer_seeds!(field)`: expands a [`signer_seeds!`](#signer_seeds) field into
an `_OFF` offset to the struct, an `N_SEEDS` count, and per-seed `_ADDR_OFF`
and `_LEN_OFF` constants (requires `#[frame(Type)]`, see below)
- `address!(expr)`: splits a 32-byte address into four 8-byte chunks, emitting
- `pubkey!(expr)`: splits a 32-byte pubkey into four 8-byte chunks, emitting
full 64-bit `_CHUNK_{0..3}` `i64` constants (for `lddw`) plus
`_CHUNK_{0..3}_LO` and `_CHUNK_{0..3}_HI` `i32` immediates (twelve constants
total)
Expand Down Expand Up @@ -133,10 +133,11 @@ The length is accessible in Rust as `RegisterMarketData::LEN`.

### `#[instruction_accounts("target")]`

Attribute macro for instruction accounts enums. Automatically generates a
`LEN` associated constant (`u64`) from the number of enum variants, and a hidden
module with a `_LEN` suffixed assembly constant and `GROUP` for build-time
injection.
Attribute macro for instruction accounts enums. Generates a `LEN` associated
constant (`u64`) from the number of enum variants, plus a per-variant `_POS`
position constant (`i32`) for each variant. A hidden module with assembly
constants and `GROUP` is emitted for build-time injection. Assembly comments
are auto-generated from the variant names.

The count is accessible in Rust as `RegisterMarketAccounts::LEN`.

Expand Down
2 changes: 1 addition & 1 deletion docs/src/program/layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ Each 32-byte pubkey is accessed as four 8-byte (`u64`) chunks at offsets 0, 8,
Known addresses (such as the rent sysvar ID) are split into full 64-bit
`_CHUNK_{0..3}` constants (loadable with a single `lddw`) and `_CHUNK_{0..3}_LO`
/ `_CHUNK_{0..3}_HI` `i32` immediates (loadable with `mov32` / `lsh64` pairs)
using [`address!`][bs-constant-group]. The `lddw` form costs 2 CUs but uses one
using [`pubkey!`][bs-constant-group]. The `lddw` form costs 2 CUs but uses one
instruction; the `mov32` / `lsh64` pair also costs 2 CUs but can be optimized
to 1 CU with `mov32` alone when the high bits are zero.

Expand Down
2 changes: 2 additions & 0 deletions interface/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
dropset-build = {path = "../build"}
dropset-macros = {path = "../macros"}
pinocchio = {workspace = true}
pinocchio-token = {workspace = true}
pinocchio-token-2022 = {workspace = true}

[package]
name = "dropset-interface"
Expand Down
14 changes: 14 additions & 0 deletions interface/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,20 @@ pub enum ErrorCode {
RentSysvarIsDuplicate,
/// The Rent sysvar account pubkey is invalid.
InvalidRentSysvarPubkey,
/// The base token program account is a duplicate.
BaseTokenProgramIsDuplicate,
/// The base token program does not own the base mint.
BaseTokenProgramNotBaseMintOwner,
/// The base token program is not Token Program or Token 2022.
BaseTokenProgramNotTokenProgram,
/// The quote token program duplicate position is invalid.
InvalidQuoteTokenProgramDuplicate,
/// The duplicate quote token program does not own the quote mint.
DupQuoteTokenProgramNotQuoteMintOwner,
/// The non-duplicate quote token program does not own the quote mint.
NonDupQuoteTokenProgramNotQuoteMintOwner,
/// The quote token program is not Token Program or Token 2022.
QuoteTokenProgramNotTokenProgram,
}
// endregion: error_enum

Expand Down
14 changes: 13 additions & 1 deletion interface/src/market.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,16 @@ constant_group! {
BASE_DATA_LEN = offset!(RegisterMarketInputBuffer.base_mint.header.data_len),
/// From input buffer to base mint address.
BASE_ADDR = offset!(RegisterMarketInputBuffer.base_mint.header.address),
/// From input buffer to base mint owner.
BASE_OWNER = pubkey_offsets!(RegisterMarketInputBuffer.base_mint.header.owner),
/// From input buffer to quote mint.
QUOTE = offset!(RegisterMarketInputBuffer.quote_mint),
/// From input buffer to quote mint duplicate flag.
QUOTE_DUPLICATE = offset!(RegisterMarketInputBuffer.quote_mint.header.borrow_state),
/// From input buffer to quote mint address.
QUOTE_ADDR = offset!(RegisterMarketInputBuffer.quote_mint.header.address),
/// From input buffer to quote mint owner.
QUOTE_OWNER = pubkey_offsets!(RegisterMarketInputBuffer.quote_mint.header.owner),
/// From input buffer to quote mint data length.
QUOTE_DATA_LEN = offset!(RegisterMarketInputBuffer.quote_mint.header.data_len),
/// Number of seeds for market PDA derivation (base, quote).
Expand Down Expand Up @@ -140,6 +144,10 @@ signer_seeds! {
#[frame]
/// Stack frame for REGISTER-MARKET.
pub struct RegisterMarketFrame {
/// Saved input buffer pointer.
pub input: u64,
/// Saved input_shifted pointer.
pub input_shifted: u64,
/// For CreateAccount CPI.
pub pda_seeds: PDASignerSeeds,
/// From `sol_try_find_program_address`.
Expand All @@ -164,6 +172,10 @@ constant_group! {
#[inject("market/register")]
#[frame(RegisterMarketFrame)]
frame {
/// Saved input buffer pointer.
INPUT = offset!(input),
/// Saved input_shifted pointer.
INPUT_SHIFTED = offset!(input_shifted),
/// PDA signer seeds.
PDA_SEEDS = signer_seeds!(pda_seeds),
/// PDA address.
Expand Down Expand Up @@ -192,7 +204,7 @@ constant_group! {
PDA_SEEDS_TO_SOL_INSN = relative_offset!(pda_seeds, sol_instruction),
/// From pda to signers_seeds.
PDA_TO_SIGNERS_SEEDS = relative_offset!(pda, signers_seeds),
/// From create_account_data to cpi account metas.
/// From create_account_data to CPI account metas.
CREATE_ACCT_DATA_TO_CPI_ACCT_METAS = relative_offset!(
create_account_data, cpi_accounts.user_meta
),
Expand Down
14 changes: 10 additions & 4 deletions interface/src/pubkey.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use dropset_macros::constant_group;
use pinocchio::sysvars::rent::RENT_ID;
pub use pinocchio_token::ID as TOKEN_PROGRAM_ID;
pub use pinocchio_token_2022::ID as TOKEN_2022_PROGRAM_ID;

// region: pubkey_constants
constant_group! {
Expand All @@ -10,13 +12,17 @@ constant_group! {
/// Offset for the first 8 bytes.
CHUNK_0_OFF = immediate!(0),
/// Offset for the second 8 bytes.
CHUNK_1_OFF = immediate!(8),
CHUNK_1_OFF = immediate!(size_of::<u64>()),
/// Offset for the third 8 bytes.
CHUNK_2_OFF = immediate!(16),
CHUNK_2_OFF = immediate!(2 * size_of::<u64>()),
/// Offset for the fourth 8 bytes.
CHUNK_3_OFF = immediate!(24),
CHUNK_3_OFF = immediate!(3 * size_of::<u64>()),
/// Rent sysvar ID.
RENT = address!(RENT_ID),
RENT = pubkey!(RENT_ID),
/// SPL Token Program ID.
TOKEN_PROGRAM = pubkey!(TOKEN_PROGRAM_ID),
/// SPL Token 2022 Program ID.
TOKEN_2022_PROGRAM = pubkey!(TOKEN_2022_PROGRAM_ID),
}
}
// endregion: pubkey_constants
2 changes: 1 addition & 1 deletion macros/src/constant_group/expand/address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ fn chunk_expr(chunk: usize) -> proc_macro2::TokenStream {
}
}

/// Expand `address!(expr)` into eight i32 constants: four chunk lo/hi pairs.
/// Expand `pubkey!(expr)` into eight i32 constants: four chunk lo/hi pairs.
///
/// For a pubkey named `RENT` with prefix `PUBKEY`, this emits:
/// - `PUBKEY_RENT_CHUNK_0_LO` / `PUBKEY_RENT_CHUNK_0_HI`
Expand Down
2 changes: 1 addition & 1 deletion macros/src/constant_group/expand/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ pub fn expand(input: &ConstantGroupInput) -> proc_macro2::TokenStream {
const_defs.push(def);
meta_idents.push(meta);
}
ConstantKind::Address { expr } => {
ConstantKind::Pubkey { expr } => {
address::expand_address(&asm_name, doc, expr, &mut const_defs, &mut meta_idents);
}
ConstantKind::PubkeyOffsets { expr } => {
Expand Down
4 changes: 2 additions & 2 deletions macros/src/constant_group/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ pub(crate) enum ConstantKind {
},
/// `immediate!(expr)`: value must fit i32, exposed as i32 in Rust.
Immediate { expr: Expr },
/// `address!(expr)`: splits an `Address` into four 8-byte chunks, emitting
/// `pubkey!(expr)`: splits a pubkey into four 8-byte chunks, emitting
/// `_CHUNK_{0..3}_LO` and `_CHUNK_{0..3}_HI` i32 immediates.
Address { expr: Expr },
Pubkey { expr: Expr },
/// `pubkey_offsets!(Type.field.path)`: emits `_OFF` plus four
/// `_CHUNK_{0..3}_OFF` offset constants for the 32-byte field.
PubkeyOffsets { expr: Expr },
Expand Down
Loading
Loading