sol.bend source
sol.bend on the hub · documented module
import Baseimport ./types.bend as Ty# Shared Solana view. Import this file as S.# Accounts (up to 256), instruction data, and the CPI# queue are lists in the book. List<&2, U32> is the byte view: the checker# sees a linked list; the compiler emits O(1) slice ops (zero-copy).def key(a: Ty.Account) -> Ty.Pubkey: match a: case Ty.Acc{+k, own, lams, bytes, signer, writable}: kdef owner(a: Ty.Account) -> Ty.Pubkey: match a: case Ty.Acc{k, +own, lams, bytes, signer, writable}: owndef is_signer(a: Ty.Account) -> Bool: match a: case Ty.Acc{k, own, lams, bytes, +signer, writable}: signerdef is_writable(a: Ty.Account) -> Bool: match a: case Ty.Acc{k, own, lams, bytes, signer, +writable}: writabledef lamports(a: Ty.Account) -> Ty.u64: match a: case Ty.Acc{k, own, +lams, bytes, signer, writable}: lamsdef data(a: Ty.Account) -> List<&2, U32>: match a: case Ty.Acc{k, own, lams, +bytes, signer, writable}: bytesdef set_data(a: Ty.Account, bytes: List<&2, U32>) -> Ty.Account: match a: case Ty.Acc{k, own, lams, old, signer, writable}: Ty.Acc{k, own, lams, bytes, signer, writable}def set_lamports(a: Ty.Account, lams: Ty.u64) -> Ty.Account: match a: case Ty.Acc{k, own, old, bytes, signer, writable}: Ty.Acc{k, own, lams, bytes, signer, writable}def add_lamports(+a: Ty.Account, amt: Ty.u64) -> Ty.Account: set_lamports(a, Ty.u64.add(lamports(a), amt))def sub_lamports(+a: Ty.Account, amt: Ty.u64) -> Ty.Account: set_lamports(a, Ty.u64.sub(lamports(a), amt))def is_owned_by(+a: Ty.Account, +program_id: Ty.Pubkey) -> Bool: Ty.Pubkey.eq(owner(a), program_id)# The all-zero pubkey is the System program.def system_id() -> Ty.Pubkey: Ty.Pubkey.zero()# Key is the System program.def is_system(+a: Ty.Account) -> Bool: Ty.Pubkey.eq(key(a), system_id())# Signer flag and key comparison both hold.def is_signer_of(is_signer: Bool, same: Bool) -> Bool: Bool.and(is_signer, same)# Placeholder for an account index past the end of the list.def empty_account() -> Ty.Account: Ty.Acc{ Ty.Pubkey.zero(), Ty.Pubkey.zero(), Ty.u64.zero(), Nil{}, False{}, False{}, }# Ty.Account and instruction bytes are List<&2, U32> in the book.# The compiler emits O(1) slice ops (zero-copy).# Word i, or 0 when i is past the end.def get_u32(xs: List<&2, U32>, i: Nat) -> U32: match xs i: case Nil{} _: 0 case h <> t 0n: h case h <> t 1n+p: get_u32(t, p)# Replace word i. An index past the end leaves the list unchanged.def set_u32(xs: List<&2, U32>, i: Nat, v: U32) -> List<&2, U32>: match xs i: case Nil{} _: Nil{} case h <> t 0n: v <> t case h <> t 1n+p: h <> set_u32(t, p, v)def data_len(xs: List<&2, U32>) -> Nat: match xs: case Nil{}: 0n case h <> t: 1n+data_len(t)def has_words(xs: List<&2, U32>, n: Nat) -> Bool: Nat.is_ge(data_len(xs), n)# A byte slice into a word buffer. `off` and `len` are bytes.# Ty.Account fields use this so a bump is one byte, not a u32 word.def as_bytes(xs: List<&2, U32>) -> Ty.Slice: Ty.Slice{xs, 0, 0}def slice(s: Ty.Slice, off: Nat, len: Nat) -> Ty.Slice: match s: case Ty.Slice{words, +base, +n}: Ty.Slice{words, U32.add(base, U32.from_nat(off)), U32.from_nat(len)}def slice_len(s: Ty.Slice) -> U32: match s: case Ty.Slice{words, off, +n}: ndef byte_index(+off: U32, +i: Nat) -> U32: U32.add(off, U32.from_nat(i))def word_index(+i: U32) -> Nat: U32.to_nat(U32.div(i, 4))def byte_shift(+i: U32) -> U32: U32.mul(U32.sub(i, U32.mul(U32.div(i, 4), 4)), 8)def get_u8(s: Ty.Slice, +i: Nat) -> U32: match s: case Ty.Slice{+words, +off, n}: U32.and( U32.shrn( get_u32(words, word_index(byte_index(off, i))), U32.to_nat(byte_shift(byte_index(off, i))), ), 255, )# Byte 0 equals d. An empty buffer compares as 0.def has_discriminator(xs: List<&2, U32>, d: U32) -> Bool: U32.is_eq(get_u8(as_bytes(xs), 0n), d)def set_u8(+xs: List<&2, U32>, +i: Nat, +v: U32) -> List<&2, U32>: set_u32( xs, word_index(U32.from_nat(i)), U32.or( U32.and( get_u32(xs, word_index(U32.from_nat(i))), U32.not(U32.shln(255, U32.to_nat(byte_shift(U32.from_nat(i))))), ), U32.shln(U32.and(v, 255), U32.to_nat(byte_shift(U32.from_nat(i)))), ), )def le_u32(+s: Ty.Slice, +i: Nat) -> U32: U32.or( U32.or(get_u8(s, i), U32.shln(get_u8(s, 1n+i), 8n)), U32.or(U32.shln(get_u8(s, 2n+i), 16n), U32.shln(get_u8(s, 3n+i), 24n)), )def set_le_u32(+xs: List<&2, U32>, +i: Nat, +v: U32) -> List<&2, U32>: set_u8( set_u8( set_u8(set_u8(xs, i, v), 1n+i, U32.shrn(v, 8n)), 2n+i, U32.shrn(v, 16n), ), 3n+i, U32.shrn(v, 24n), )def le_u64(+s: Ty.Slice, +i: Nat) -> Ty.u64: Ty.u64.pack(le_u32(s, i), le_u32(s, 4n+i))def as_word(v: Ty.u32) -> U32: U32.from_nat(Ty.u32.to_nat(v))def set_le_u64(+xs: List<&2, U32>, +i: Nat, +v: Ty.u64) -> List<&2, U32>: set_le_u32( set_le_u32(xs, i, as_word(Ty.u64.lo(v))), 4n+i, as_word(Ty.u64.hi(v)), )def pubkey_at(+s: Ty.Slice, +i: Nat) -> Ty.Pubkey: Ty.Pk{ le_u32(s, i), le_u32(s, 4n+i), le_u32(s, 8n+i), le_u32(s, 12n+i), le_u32(s, 16n+i), le_u32(s, 20n+i), le_u32(s, 24n+i), le_u32(s, 28n+i), }def set_pubkey_at(+xs: List<&2, U32>, +i: Nat, +k: Ty.Pubkey) -> List<&2, U32>: match k: case Ty.Pk{+a, +b, +c, +d, +e, +f, +g, +h}: set_le_u32( set_le_u32( set_le_u32( set_le_u32( set_le_u32( set_le_u32( set_le_u32(set_le_u32(xs, i, a), 4n+i, b), 8n+i, c, ), 12n+i, d, ), 16n+i, e, ), 20n+i, f, ), 24n+i, g, ), 28n+i, h, )# Stored pubkey at byte `off` equals `k`.def eq_pubkey(+xs: List<&2, U32>, +off: Nat, +k: Ty.Pubkey) -> Bool: Ty.Pubkey.eq(pubkey_at(as_bytes(xs), off), k)# Same as get_u32.def get_u32_word(xs: List<&2, U32>, i: Nat) -> U32: get_u32(xs, i)# Same as set_u32.def set_u32_word(xs: List<&2, U32>, i: Nat, v: U32) -> List<&2, U32>: set_u32(xs, i, v)# Little-endian u64 at words i and i+1.def get_u64(+xs: List<&2, U32>, +i: Nat) -> Ty.u64: Ty.u64.pack(get_u32(xs, i), get_u32(xs, 1n+i))# Write a little-endian u64 at words i and i+1.def set_u64(+xs: List<&2, U32>, +i: Nat, +v: Ty.u64) -> List<&2, U32>: set_u32(set_u32(xs, i, as_word(Ty.u64.lo(v))), 1n+i, as_word(Ty.u64.hi(v)))# n zero words. Used to pad a short account before the first write.def zeros(n: Nat) -> List<&2, U32>: match n: case 0n: Nil{} case 1n+p: 0 <> zeros(p)# Keep xs when it is already long enough; otherwise n zero words.def ensure_if(ok: Bool, xs: List<&2, U32>, n: Nat) -> List<&2, U32>: match ok: case True{}: xs case False{}: zeros(n)# Pad with zeros when xs is shorter than n words.def ensure_words(+xs: List<&2, U32>, +n: Nat) -> List<&2, U32>: ensure_if(Nat.is_ge(data_len(xs), n), xs, n)# Lengthen a word view by at most 10240 bytes. The compiler emits list_grow.def resize(+xs: List<&2, U32>, +n: Nat) -> List<&2, U32>: ensure_words(xs, n)# Ty.Pubkey at word offset off (eight words).def get_pubkey(+xs: List<&2, U32>, +off: Nat) -> Ty.Pubkey: Ty.Pk{ get_u32(xs, off), get_u32(xs, 1n+off), get_u32(xs, 2n+off), get_u32(xs, 3n+off), get_u32(xs, 4n+off), get_u32(xs, 5n+off), get_u32(xs, 6n+off), get_u32(xs, 7n+off), }# Write eight pubkey words at off.def set_pubkey(xs: List<&2, U32>, +off: Nat, p: Ty.Pubkey) -> List<&2, U32>: match p: case Ty.Pk{+w0, +w1, +w2, +w3, +w4, +w5, +w6, +w7}: set_u32( set_u32( set_u32( set_u32( set_u32( set_u32( set_u32(set_u32(xs, off, w0), 1n+off, w1), 2n+off, w2, ), 3n+off, w3, ), 4n+off, w4, ), 5n+off, w5, ), 6n+off, w6, ), 7n+off, w7, )# Ty.Pubkey at word offset off equals k.def keys_eq(+xs: List<&2, U32>, +off: Nat, +k: Ty.Pubkey) -> Bool: Ty.Pubkey.eq(get_pubkey(xs, off), k)# The pubkey at ao in a equals the pubkey at bo in b.def keys_eq_words( +a: List<&2, U32>, +ao: Nat, +b: List<&2, U32>, +bo: Nat,) -> Bool: Ty.Pubkey.eq(get_pubkey(a, ao), get_pubkey(b, bo))# Byte equality. Address derivation is is_program_address, not this.def pda_matches(+got: Ty.Pubkey, +want: Ty.Pubkey) -> Bool: Ty.Pubkey.eq(got, want)# Ordered seeds for find_program_address. At most 16, each 1..=32 bytes.# The book mark is not the hash. is_program_address / find_program_address are the natives.def seeds_nil() -> Ty.Seeds: Ty.Seeds{0}# Append `nbytes` of `bytes` (little-endian words). The chain copies those# bytes; this body only keeps the checker from dropping the arguments.def seeds_push(+ss: Ty.Seeds, +bytes: List<&2, U32>, nbytes: U32) -> Ty.Seeds: match ss bytes nbytes: case Ty.Seeds{mark} xs n: Ty.Seeds{(mark + (get_u32(xs, 0n) + n : U32) : U32)}# Append the bytes of a constant string. The book mark ignores the text.# The compiler native copies those bytes.def seed_str(+ss: Ty.Seeds, +text: String) -> Ty.Seeds: match ss text: case Ty.Seeds{mark} _: Ty.Seeds{mark}# Thirty-two bytes of a pubkey.def seed_pk(+ss: Ty.Seeds, +k: Ty.Pubkey) -> Ty.Seeds: seeds_push(ss, set_pubkey(zeros(8n), 0n, k), 32)# Checker dummy. Compiler native is find_program_address(seeds, program_id).def is_program_address( +got: Ty.Pubkey, +program_id: Ty.Pubkey, +seeds: Ty.Seeds,) -> Bool: match got program_id seeds: case Ty.Pk{+g0, +g1, +g2, +g3, +g4, +g5, +g6, +g7} Ty.Pk{+p0, +p1, +p2, +p3, +p4, +p5, +p6, +p7} Ty.Seeds{mark}: Bool.and( Ty.Pubkey.eq( Ty.Pk{g0, g1, g2, g3, g4, g5, g6, g7}, Ty.Pk{p0, p1, p2, p3, p4, p5, p6, p7}, ), U32.is_eq(mark, mark), )# Address is the PDA whose only seed is `owner`. find_program_address.def is_program_address_pk( +got: Ty.Pubkey, +program_id: Ty.Pubkey, +owner: Ty.Pubkey,) -> Bool: is_program_address(got, program_id, seed_pk(seeds_nil(), owner))# Checker dummy. Compiler native is create_program_address(seeds, bump).def is_program_address_bump( +got: Ty.Pubkey, +program_id: Ty.Pubkey, +seeds: Ty.Seeds, +bump: U32,) -> Bool: match got program_id seeds bump: case Ty.Pk{+g0, +g1, +g2, +g3, +g4, +g5, +g6, +g7} Ty.Pk{+p0, +p1, +p2, +p3, +p4, +p5, +p6, +p7} Ty.Seeds{mark} +b: Bool.and( Ty.Pubkey.eq( Ty.Pk{g0, g1, g2, g3, g4, g5, g6, g7}, Ty.Pk{p0, p1, p2, p3, p4, p5, p6, p7}, ), Bool.and(U32.is_eq(mark, mark), U32.is_eq(b, b)), )# `got` is create_program_address([owner, bump]).def is_program_address_pk_bump( +got: Ty.Pubkey, +program_id: Ty.Pubkey, +owner: Ty.Pubkey, +bump: U32,) -> Bool: is_program_address_bump(got, program_id, seed_pk(seeds_nil(), owner), bump)# Checker dummy. The compiler native reads the Rent sysvar:# (bytes + 128) * lamports_per_byte_year * exemption_threshold.def rent_exempt(+bytes: Ty.u64) -> Ty.u64: bytesdef rent_due_go(fits: Bool, +have: Ty.u64, +need: Ty.u64) -> Ty.u64: match fits: case True{}: Ty.u64.zero() case False{}: Ty.u64.sub(rent_exempt(need), rent_exempt(have))# Extra lamports to raise an account from `have` bytes to `need` bytes.def rent_due(+have: Ty.u64, +need: Ty.u64) -> Ty.u64: rent_due_go(Ty.u64.is_le(need, have), have, need)# Checker dummy bump 255. Compiler native is the try_find bump.def find_program_address(+program_id: Ty.Pubkey, +seeds: Ty.Seeds) -> U32: match program_id seeds: case _ _: 255# find_program_address bump for an owner-only PDA.def find_program_address_pk(+program_id: Ty.Pubkey, +owner: Ty.Pubkey) -> U32: find_program_address(program_id, seed_pk(seeds_nil(), owner))# A CPI is one queued invoke. Indices are Context account slots.# The runtime walks the whole queue after process returns Ok.type Cpi is Data: CpiNone{} CpiTransfer{ from: U32, # source account index to: U32, # destination account index lamports: Ty.u64, # lamports to transfer sign: Bool, # true when the source PDA signs bump: U32, # PDA bump; unused when sign is false seed: U32 # unused book word; the signer list is Ty.Seeds } CpiTokenTransfer{ from: U32, # source token-account index to: U32, # destination token-account index mint: U32, # mint account index auth: U32, # authority account index amount: Ty.u64, # token amount decimals: U32, # mint decimals sign: Bool, # true when the authority PDA signs bump: U32, # PDA bump; unused when sign is false seed: U32 # unused book word; the signer list is Ty.Seeds } # System CreateAccount. `from` pays, `to` is the new account. # `space` is the byte length. When `sign`, `Ty.Seeds` plus `bump` sign # the new account. Owner is always this program. The `seed` word is unused. CpiCreate{ from: U32, to: U32, lamports: Ty.u64, space: Ty.u64, sign: Bool, bump: U32, seed: U32, } # System Assign. `from` is the account; `owner` is the new owner program. # When `sign`, the signer is `Ty.Seeds` plus `bump`. The `seed` word is unused. CpiAssign{ from: U32, to: U32, owner: Ty.Pubkey, sign: Bool, bump: U32, seed: U32, } # System Allocate. `from` is the account; `space` is the byte length. # When `sign`, the signer is `Ty.Seeds` plus `bump`. The `seed` word is unused. CpiAllocate{ from: U32, to: U32, space: Ty.u64, sign: Bool, bump: U32, seed: U32, } # SPL Token instruction other than TransferChecked. `kind` is the Tokenkeg # discriminant: 4 Approve, 5 Revoke, 7 MintTo, 10 Freeze, 11 Thaw, # 18 InitializeAccount3, 20 InitializeMint2. CpiTokenIx{ kind: U32, from: U32, to: U32, auth: U32, amount: Ty.u64, decimals: U32, owner: Ty.Pubkey, sign: Bool, bump: U32, seed: U32, } # Associated Token Ty.Account program Create (0) / CreateIdempotent (1). CpiAta{ kind: U32, payer: U32, ata: U32, wallet: U32, mint: U32, system: U32, token: U32, } # Invoke `program` (an account index) with `accs` (account indexes) # and `ix` (instruction words, little-endian on the wire). # `auth` is the PDA signer index when `sign` is true. CpiInvoke{ program: U32, auth: U32, accs: List<&2, U32>, ix: List<&2, U32>, sign: Bool, bump: U32, seed: U32, }def cpi_none() -> Cpi: CpiNone{}def cpi_is_none(c: Cpi) -> Bool: match c: case CpiNone{}: True{} case CpiTransfer{_, _, _, _, _, _}: False{} case CpiTokenTransfer{ from, to, mint, auth, amount, decimals, sign, bump, seed, }: False{} case CpiCreate{_, _, _, _, _, _, _}: False{} case CpiAssign{_, _, _, _, _, _}: False{} case CpiAllocate{from, to, space, sign, bump, seed}: False{} case CpiTokenIx{_, _, _, _, _, _, _, _, _, _}: False{} case CpiAta{kind, payer, ata, wallet, mint, system, token}: False{} case CpiInvoke{program, auth, accs, ix, sign, bump, seed}: False{}# First queued CPI, or CpiNone when the queue is empty.def cpi_head(xs: List<&2, Cpi>) -> Cpi: match xs: case Nil{}: CpiNone{} case h <> t: h# CPI at index i, or CpiNone when i is past the end.def cpi_at(xs: List<&2, Cpi>, i: Nat) -> Cpi: match xs i: case Nil{} _: CpiNone{} case h <> t 0n: h case h <> t 1n+p: cpi_at(t, p)# Replace a leading CpiNone; otherwise append. Empty (Nil) becomes a singleton.def cpi_put(xs: List<&2, Cpi>, c: Cpi) -> List<&2, Cpi>: match xs: case Nil{}: c <> Nil{} case h <> t: match h: case CpiNone{}: c <> t case CpiTransfer{+from, +to, +lams, +sign, +bump, +seed}: CpiTransfer{from, to, lams, sign, bump, seed} <> cpi_put(t, c) case CpiTokenTransfer{ +from, +to, +mint, +auth, +amount, +decimals, +sign, +bump, +seed, }: CpiTokenTransfer{ from, to, mint, auth, amount, decimals, sign, bump, seed, } <> cpi_put(t, c) case CpiCreate{+from, +to, +lams, +space, +sign, +bump, +seed}: CpiCreate{from, to, lams, space, sign, bump, seed} <> cpi_put(t, c) case CpiAssign{+from, +to, +own, +sign, +bump, +seed}: CpiAssign{from, to, own, sign, bump, seed} <> cpi_put(t, c) case CpiAllocate{+from, +to, +space, +sign, +bump, +seed}: CpiAllocate{from, to, space, sign, bump, seed} <> cpi_put(t, c) case CpiTokenIx{ +kind, +from, +to, +auth, +amount, +decimals, +own, +sign, +bump, +seed, }: CpiTokenIx{ kind, from, to, auth, amount, decimals, own, sign, bump, seed, } <> cpi_put(t, c) case CpiAta{+kind, +payer, +ata, +wallet, +mint, +system, +token}: CpiAta{kind, payer, ata, wallet, mint, system, token} <> cpi_put(t, c) case CpiInvoke{+program, +auth, +accs, +ix, +sign, +bump, +seed}: CpiInvoke{program, auth, accs, ix, sign, bump, seed} <> cpi_put(t, c)# Context is the transaction the runtime hands the program.# Accounts, instruction data, and CPIs are lists. The book does not cap# them. The runtime caps accounts at 256. Instruction data is the# bytes in the input. CPIs are limited by the heap.# Runtime cap. The book list is unbounded; the runtime rejects more.def MAX_ACCOUNTS() -> U32: 256type Context is Data: Context{ n: U32, # account count program_id: Ty.Pubkey, # this program data: List<&2, U32>, # instruction data as words accounts: List<&2, Ty.Account>, # accounts in transaction order cpis: List<&2, Cpi> # cross-program invocations queued by the handler }type Outcome is Data: Ok{context: Context} # success; the runtime applies this context Err{code: Ty.u64} # failure; a Solana program-error returndef ok(+c: Context) -> Outcome: Ok{c}def fail(+code: Ty.u64) -> Outcome: Err{code}# The outcome is Err{want}. An Ok outcome is False.def err_is(outcome: Outcome, want: Ty.u64) -> Bool: match outcome: case Ok{+c}: False{} case Err{code}: Ty.u64.is_eq(code, want)# The outcome is Ok. An Err outcome is False.def is_ok(outcome: Outcome) -> Bool: match outcome: case Ok{+c}: True{} case Err{code}: False{}# Ok{want}, field for field. Any other outcome is False.# The two contexts must be the same term: same accounts, instruction, and queue.def wrote(outcome: Outcome, want: Context) -> Bool: match outcome: case Ok{+c}: match c want: case Context{n, pid, ix, accs, queue} Context{n, pid, ix, accs, queue}: True{} case Context{n, pid, ix, accs, queue} Context{n2, pid2, ix2, accs2, queue2}: False{} case Err{code}: False{}# One require in a `do Try<>:` block. No carries the error; Yes carries the value.type Attempt<a, -A: Kind(a)> is Kind(a): No{code: Ty.u64} Yes{v: A}# `do Try<>:` runs each require in order. False, or None, is Err{code}.def Try.bind(-A: Data, m: Attempt<&2, A>, next: A -> Outcome) -> Outcome: match m: case No{code}: Err{code} case Yes{v}: next(v)# `return` in a `do Try<>:` block.def Try.pure(o: Outcome) -> Outcome: o# False is No{code}. True is Yes{Unit}, so the next line runs.def require(ok: Bool, +code: Ty.u64) -> Attempt<&2, Unit>: match ok: case False{}: No{code} case True{}: Yes{Unit{}}# None is No{code}. Some is Yes of the U32, bound by `name: U32 <-`.def require_some(m: Maybe<&2, U32>, +code: Ty.u64) -> Attempt<&2, U32>: match m: case None{}: No{code} case Some{v}: Yes{v}# None is No{code}. Some is Yes of the Ty.u64, bound by `name: Ty.u64 <-`.def require_u64(m: Maybe<&2, Ty.u64>, +code: Ty.u64) -> Attempt<&2, Ty.u64>: match m: case None{}: No{code} case Some{v}: Yes{v}# Solana program-error returns. Builtin variants are `hi << 32`.# `custom` is ProgramError::Custom: the low word, for a code with no# standard variant. Custom(0) is not a failure; pass a non-zero code.def Err.ok() -> Ty.u64: Ty.u64.zero()def Err.not_enough_account_keys() -> Ty.u64: Ty.u64.pack(0, 11)def Err.invalid_instruction_data() -> Ty.u64: Ty.u64.pack(0, 3)def Err.invalid_account_data() -> Ty.u64: Ty.u64.pack(0, 4)def Err.insufficient_funds() -> Ty.u64: Ty.u64.pack(0, 6)def Err.account_already_initialized() -> Ty.u64: Ty.u64.pack(0, 9)def Err.uninitialized_account() -> Ty.u64: Ty.u64.pack(0, 10)def Err.invalid_seeds() -> Ty.u64: Ty.u64.pack(0, 14)def Err.illegal_owner() -> Ty.u64: Ty.u64.pack(0, 18)def Err.arithmetic_overflow() -> Ty.u64: Ty.u64.pack(0, 24)def Err.incorrect_authority() -> Ty.u64: Ty.u64.pack(0, 26)def Err.custom(code: U32) -> Ty.u64: Ty.u64.from_u32(code)# Ty.Account i, or the empty account when i is past the end.def account_at(xs: List<&2, Ty.Account>, i: Nat) -> Ty.Account: match xs i: case Nil{} _: empty_account() case h <> t 0n: h case h <> t 1n+p: account_at(t, p)# Replace account i. An index past the end leaves the list unchanged.def set_accounts( xs: List<&2, Ty.Account>, i: Nat, a: Ty.Account,) -> List<&2, Ty.Account>: match xs i: case Nil{} _: Nil{} case h <> t 0n: a <> t case h <> t 1n+p: h <> set_accounts(t, p, a)def instruction_data(c: Context) -> List<&2, U32>: match c: case Context{n, pid, +ix, accs, queue}: ixdef ix_u32(c: Context, i: Nat) -> U32: match c: case Context{n, pid, +ix, accs, queue}: get_u32(ix, i)# Instruction discriminator. Byte `i` of the instruction data, widened to U32.def ix_u8(c: Context, i: Nat) -> U32: match c: case Context{n, pid, +ix, accs, queue}: get_u8(as_bytes(ix), i)# Little-endian u64 at byte offset `i`. Byte 0 is the discriminator.def ix_u64(c: Context, i: Nat) -> Ty.u64: match c: case Context{n, pid, +ix, accs, queue}: le_u64(as_bytes(ix), i)def accounts(c: Context) -> List<&2, Ty.Account>: match c: case Context{n, pid, ix, +accs, queue}: accsdef account(c: Context, i: Nat) -> Ty.Account: match c: case Context{n, pid, ix, +accs, queue}: account_at(accs, i)def cpi_queue(c: Context) -> List<&2, Cpi>: match c: case Context{n, pid, ix, accs, +queue}: queuedef accounts_len(c: Context) -> U32: match c: case Context{+n, pid, ix, accs, queue}: ndef program_id(c: Context) -> Ty.Pubkey: match c: case Context{n, +pid, ix, accs, queue}: pid# Head of the CPI queue.def cpi(c: Context) -> Cpi: match c: case Context{n, pid, ix, accs, +queue}: cpi_head(queue)# Second queued CPI.def cpi1(c: Context) -> Cpi: match c: case Context{n, pid, ix, accs, +queue}: cpi_at(queue, 1n)def set_account(c: Context, i: Nat, a: Ty.Account) -> Context: match c: case Context{n, pid, ix, accs, queue}: Context{n, pid, ix, set_accounts(accs, i, a), queue}def context_set_cpis(c: Context, cpis: List<&2, Cpi>) -> Context: match c: case Context{n, pid, ix, accs, old}: Context{n, pid, ix, accs, cpis}# Append a CPI, replacing a leading CpiNone.def cpi_push(+c: Context, cpi: Cpi) -> Context: context_set_cpis(c, cpi_put(cpi_queue(c), cpi))# Append. Does not overwrite a fixed slot.def set_cpi(c: Context, cpi: Cpi) -> Context: cpi_push(c, cpi)# Append, same as set_cpi.def set_cpi1(c: Context, cpi1: Cpi) -> Context: cpi_push(c, cpi1)# Queue an unsigned invoke. `program` and `accs` are account indexes.# `ix` is instruction data as u32 words. Signer and writable flags are# the ones on those accounts. The callee runs after this handler returns Ok.def invoke( +c: Context, +program: U32, +accs: List<&2, U32>, +ix: List<&2, U32>,) -> Context: cpi_push(c, CpiInvoke{program, 0, accs, ix, False{}, 0, 0})# Queue an invoke signed by `seeds` plus `bump`. The book seed word stays 0.def invoke_signed_seeds( +c: Context, +program: U32, +accs: List<&2, U32>, +ix: List<&2, U32>, +auth: U32, +bump: U32, +seeds: Ty.Seeds,) -> Context: match seeds: case Ty.Seeds{mark}: cpi_push( c, CpiInvoke{program, auth, accs, ix, True{}, bump, U32.and(mark, 0)}, )# Queue an unsigned System transfer. from and to are account indexes.def Sys.transfer( +c: Context, +from: U32, +to: U32, +lamports: Ty.u64,) -> Context: cpi_push(c, CpiTransfer{from, to, lamports, False{}, 0, 0})# Queue a System transfer signed by `seeds` plus `bump`.# The chain signer is that list. The book Cpi seed word stays 0.def Sys.transfer_signed_seeds( +c: Context, +from: U32, +to: U32, +lamports: Ty.u64, +bump: U32, +seeds: Ty.Seeds,) -> Context: match seeds: case Ty.Seeds{mark}: cpi_push( c, CpiTransfer{from, to, lamports, True{}, bump, U32.and(mark, 0)}, )# Queue an unsigned SPL Token TransferChecked. Indexes are account slots.def Token.transfer( +c: Context, +from: U32, +to: U32, +mint: U32, +auth: U32, +amount: Ty.u64, +decimals: U32,) -> Context: cpi_push( c, CpiTokenTransfer{from, to, mint, auth, amount, decimals, False{}, 0, 0}, )# Queue a TransferChecked signed by `seeds` plus `bump`.def Token.transfer_signed_seeds( +c: Context, +from: U32, +to: U32, +mint: U32, +auth: U32, +amount: Ty.u64, +decimals: U32, +bump: U32, +seeds: Ty.Seeds,) -> Context: match seeds: case Ty.Seeds{mark}: cpi_push( c, CpiTokenTransfer{ from, to, mint, auth, amount, decimals, True{}, bump, U32.and(mark, 0), }, )# Queue a System CreateAccount. `payer` funds `new`.# When `sign`, `seeds` plus `bump` sign the new account.# Owner is this program.def Sys.create_account( +c: Context, +payer: U32, +new: U32, +lamports: Ty.u64, +space: Ty.u64, +sign: Bool, +bump: U32, +seeds: Ty.Seeds,) -> Context: match seeds: case Ty.Seeds{mark}: cpi_push( c, CpiCreate{payer, new, lamports, space, sign, bump, U32.and(mark, 0)}, )# Queue a System Assign. `account` is the index; `owner` is the new owner.def Sys.assign(+c: Context, +account: U32, +owner: Ty.Pubkey) -> Context: cpi_push(c, CpiAssign{account, 0, owner, False{}, 0, 0})# Queue a System Assign signed by `seeds` plus `bump`.def Sys.assign_signed_seeds( +c: Context, +account: U32, +owner: Ty.Pubkey, +bump: U32, +seeds: Ty.Seeds,) -> Context: match seeds: case Ty.Seeds{mark}: cpi_push(c, CpiAssign{account, 0, owner, True{}, bump, U32.and(mark, 0)})# Queue a System Allocate. `account` is the index; `space` is the byte length.def Sys.allocate(+c: Context, +account: U32, +space: Ty.u64) -> Context: cpi_push(c, CpiAllocate{account, 0, space, False{}, 0, 0})# Queue a System Allocate signed by `seeds` plus `bump`.def Sys.allocate_signed_seeds( +c: Context, +account: U32, +space: Ty.u64, +bump: U32, +seeds: Ty.Seeds,) -> Context: match seeds: case Ty.Seeds{mark}: cpi_push( c, CpiAllocate{account, 0, space, True{}, bump, U32.and(mark, 0)}, )# SPL Token Approve (disc 4). `from` source, `to` delegate, `auth` owner.def Token.approve( +c: Context, +from: U32, +to: U32, +auth: U32, +amount: Ty.u64,) -> Context: cpi_push( c, CpiTokenIx{4, from, to, auth, amount, 0, Ty.Pubkey.zero(), False{}, 0, 0}, )# SPL Token Revoke (disc 5).def Token.revoke(+c: Context, +from: U32, +auth: U32) -> Context: cpi_push( c, CpiTokenIx{ 5, from, 0, auth, Ty.u64.zero(), 0, Ty.Pubkey.zero(), False{}, 0, 0, }, )# SPL Token MintTo (disc 7). `from` mint, `to` destination, `auth` mint authority.def Token.mint_to( +c: Context, +from: U32, +to: U32, +auth: U32, +amount: Ty.u64,) -> Context: cpi_push( c, CpiTokenIx{7, from, to, auth, amount, 0, Ty.Pubkey.zero(), False{}, 0, 0}, )# SPL Token FreezeAccount (disc 10). `from` account, `to` mint, `auth` freeze authority.def Token.freeze(+c: Context, +from: U32, +to: U32, +auth: U32) -> Context: cpi_push( c, CpiTokenIx{ 10, from, to, auth, Ty.u64.zero(), 0, Ty.Pubkey.zero(), False{}, 0, 0, }, )# SPL Token ThawAccount (disc 11).def Token.thaw(+c: Context, +from: U32, +to: U32, +auth: U32) -> Context: cpi_push( c, CpiTokenIx{ 11, from, to, auth, Ty.u64.zero(), 0, Ty.Pubkey.zero(), False{}, 0, 0, }, )# SPL Token InitializeMint2 (disc 20). `from` is the mint. `owner` is mint+freeze authority.def Token.initialize_mint( +c: Context, +from: U32, +decimals: U32, +owner: Ty.Pubkey,) -> Context: cpi_push( c, CpiTokenIx{20, from, 0, 0, Ty.u64.zero(), decimals, owner, False{}, 0, 0}, )# SPL Token InitializeAccount3 (disc 18). `from` account, `to` mint, `owner` token owner.def Token.initialize_account( +c: Context, +from: U32, +to: U32, +owner: Ty.Pubkey,) -> Context: cpi_push( c, CpiTokenIx{18, from, to, 0, Ty.u64.zero(), 0, owner, False{}, 0, 0}, )# Associated Token Ty.Account program id (ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL).def ATA.id() -> Ty.Pubkey: Ty.Pk{ 2401605516, 4052296782, 688930235, 2198703636, 2568182283, 2215706586, 3631975940, 1509485019, }# Derive the ATA address for `wallet` + `mint` (Tokenkeg). Native on chain.def ATA.address(+wallet: Ty.Pubkey, +mint: Ty.Pubkey) -> Ty.Pubkey: wallet# Queue ATA Create (disc 0).def ATA.create( +c: Context, +payer: U32, +ata: U32, +wallet: U32, +mint: U32, +system: U32, +token: U32,) -> Context: cpi_push(c, CpiAta{0, payer, ata, wallet, mint, system, token})# Queue ATA CreateIdempotent (disc 1).def ATA.create_idempotent( +c: Context, +payer: U32, +ata: U32, +wallet: U32, +mint: U32, +system: U32, +token: U32,) -> Context: cpi_push(c, CpiAta{1, payer, ata, wallet, mint, system, token})# accounts_len is at least k.def require_accounts(c: Context, k: U32) -> Bool: U32.is_ge(accounts_len(c), k)# Checked arithmetic and small masks used by the examples.# counter_inc wraps; the checked ones return None on failure.def counter_inc(+value: Ty.u64) -> Ty.u64: Ty.u64.add(value, Ty.u64.from_u32(1))# None when z (the value is already 0).def dec_if(z: Bool, value: Ty.u64) -> Maybe<&2, Ty.u64>: match z: case True{}: None{} case False{}: Some{Ty.u64.sub(value, Ty.u64.from_u32(1))}# value - 1, or None at 0.def counter_dec(+value: Ty.u64) -> Maybe<&2, Ty.u64>: dec_if(Ty.u64.is_zero(value), value)# None when lt (bal < amt).def withdraw_if(lt: Bool, bal: Ty.u64, amt: Ty.u64) -> Maybe<&2, Ty.u64>: match lt: case True{}: None{} case False{}: Some{Ty.u64.sub(bal, amt)}# bal - amt, or None when amt exceeds bal.def try_withdraw(+bal: Ty.u64, +amt: Ty.u64) -> Maybe<&2, Ty.u64>: withdraw_if(Ty.u64.is_lt(bal, amt), bal, amt)# None when ovf (the add would wrap).def deposit_if(ovf: Bool, bal: Ty.u64, amt: Ty.u64) -> Maybe<&2, Ty.u64>: match ovf: case True{}: None{} case False{}: Some{Ty.u64.add(bal, amt)}# bal + amt, or None on overflow.def try_deposit(+bal: Ty.u64, +amt: Ty.u64) -> Maybe<&2, Ty.u64>: deposit_if(Ty.u64.would_ovf(bal, amt), bal, amt)# Count set bits, one shift per fuel step. fuel 0 returns acc.def popcount.go(fuel: Nat, +mask: U32, acc: U32) -> U32: match fuel: case 0n: acc case 1n+p: popcount.go(p, U32.shr(mask), (acc + U32.and(mask, 1) : U32))# Number of set bits in mask.def popcount(mask: U32) -> U32: popcount.go(32n, mask, 0)# Set bit i in one U32 half of a mask.def bit_set(mask: U32, i: U32) -> U32: U32.or(mask, U32.shln(1, U32.to_nat(i)))# Set bit i of a u64 mask.def bit_set_u64(+mask: Ty.u64, +i: U32) -> Ty.u64: Ty.u64.or(mask, Ty.u64.shln(Ty.u64.from_nat(1n), U32.to_nat(i)))def popcount_u64.go(fuel: Nat, +mask: Ty.u64, acc: Ty.u64) -> Ty.u64: match fuel: case 0n: acc case 1n+p: popcount_u64.go( p, Ty.u64.shr(mask), Ty.u64.add(acc, Ty.u64.and(mask, Ty.u64.from_nat(1n))), )# Number of set bits in a u64 mask.def popcount_u64(+mask: Ty.u64) -> Ty.u64: popcount_u64.go(64n, mask, Ty.u64.from_nat(0n))# At least `threshold` bits are set in the approval mask.def can_execute(+approvals: Ty.u64, +threshold: Ty.u64) -> Bool: Ty.u64.is_ge(popcount_u64(approvals), threshold)# t when b, otherwise f.def pick_u32(b: Bool, t: U32, f: U32) -> U32: match b: case True{}: t case False{}: f# Some{v} when b, otherwise None.def maybe_u32(b: Bool, v: U32) -> Maybe<&2, U32>: match b: case True{}: Some{v} case False{}: None{}# Some{0} if is0, else Some{1} if is1, else None.def owner_pick(is0: Bool, is1: Bool) -> Maybe<&2, U32>: match is0: case True{}: Some{0} case False{}: maybe_u32(is1, 1)# Index of key among the first n owners, or None.def owner_index( +pk: Ty.Pubkey, +o0: Ty.Pubkey, +o1: Ty.Pubkey, +n: Ty.u64,) -> Maybe<&2, U32>: owner_pick( Bool.and(Ty.u64.is_gt(n, Ty.u64.zero()), Ty.Pubkey.eq(pk, o0)), Bool.and(Ty.u64.is_gt(n, Ty.u64.from_u32(1)), Ty.Pubkey.eq(pk, o1)), )