Skip to content

Commit c0e28e2

Browse files
authored
feat(ENG-279): Add vault account creation to INIT-VAULT (#50)
# Changes 1. Extend INIT-VAULT to create vault token accounts via CreateAccount CPI, with Token 2022 support via a GetAccountDataSize CPI to determine account size at runtime (falls back to the fixed 165-byte SPL Token account size for non-2022 programs) 2. Add `sol_get_return_data` syscall for reading the token account size returned by GetAccountDataSize 3. Add duplicate and data-length checks for base and quote vault accounts before initialization, with four new error codes (`BaseVaultIsDuplicate`, `BaseVaultHasData`, `QuoteVaultIsDuplicate`, `QuoteVaultHasData`) 4. Store `system_program_id` pointer on the frame and use it in both INIT-MARKET-PDA and INIT-VAULT for CreateAccount CPI program ID (previously set directly on `sol_instruction`) 5. Add `program_id`, `lamports_per_byte`, `mint`, `token_account_data_size`, `get_return_data_program_id`, and `get_account_data_size_data` fields to `RegisterMarketFrame` for vault creation state 6. Derive vault PDAs from the program ID instead of the token program ID 7. Add `interface/src/token.rs` module with SPL Token constants (`ACCOUNT_SIZE`, `GET_ACCOUNT_DATA_SIZE_DISC`) 8. Add `common/token.s` assembly file with injected token constants 9. Update INIT-MARKET-PDA to save `lamports_per_byte` on the frame and populate `signers_seeds` in its ensure contract 10. Update all three algorithm specs (REGISTER-MARKET, INIT-MARKET-PDA, INIT-VAULT) with new require/ensure annotations and vault creation pseudocode 11. Add `check_vault!` macro to integration tests for verifying vault owner, data length, and rent exemption after successful registration 12. Add six new test cases for vault duplicate and data-length error paths (`BaseVaultIsDuplicate`, `BaseVaultHasData`, `QuoteVaultIsDuplicate{Dup,NonDup}`, `QuoteVaultHasData{Dup,NonDup}`) 13. Wire `mollusk-svm-programs-token` and `spl-token-interface` into the test harness for real token program execution 14. Update `layout.md` directory tree and add Token constants section
1 parent 5568181 commit c0e28e2

21 files changed

Lines changed: 866 additions & 86 deletions

File tree

Cargo.lock

Lines changed: 95 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,18 @@ resolver = "3"
1414
[workspace.dependencies]
1515
bindgen = "0.72.1"
1616
heck = "0.5"
17-
mollusk-svm = "0.11.0"
17+
mollusk-svm = "0.12.0"
18+
mollusk-svm-programs-token = "0.12.0"
1819
pinocchio = "0.10.2"
1920
pinocchio-token = "0.5.0"
20-
pinocchio-token-2022 = "0.2.0"
21+
# "0.2.0" does not export GetAccountDataSize.
22+
pinocchio-token-2022 = {git = "https://github.com/anza-xyz/pinocchio", branch = "main"}
2123
proc-macro2 = "1.0.106"
2224
quote = "1.0.45"
2325
solana-account = "3.2"
2426
solana-sbpf = "0.16.0"
2527
solana-sdk = "4.0.1"
28+
spl-token-interface = "2.0.0"
2629
syn = {version = "2.0.117", features = ["full"]}
2730
ureq = "3.3.0"
2831

docs/algorithms/INIT-MARKET-PDA.tex

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,20 @@
1111
\INPUT $r_{10}$ = frame
1212
\COMMENT{Pointer to shifted input buffer for quote mint offsets.}
1313
\REQUIRE frame.input\_shifted
14-
\COMMENT{Pointer to System Program ID in input buffer.}
15-
\REQUIRE frame.sol\_instruction.program\_id
14+
\REQUIRE *frame.system\_program\_id = input.system\_program.address
15+
\REQUIRE frame.cpi[0].info.data\_len = \texttt{data.LEN\_ZERO}
16+
\REQUIRE frame.cpi[1].info.data\_len = \texttt{data.LEN\_ZERO}
1617
\ENSURE $r_9$ = acct
1718
\ENSURE frame.input = input
19+
\ENSURE frame.lamports\_per\_byte = acct.data.lamports\_per\_byte
20+
\ENSURE frame.signers\_seeds.addr = \&frame.pda\_seeds
21+
\ENSURE frame.signers\_seeds.len = \texttt{RegisterMarketFrame.PDA\_SEEDS\_N\_SEEDS}
1822
\PROCEDURE{INIT-MARKET-PDA}{input, insn, acct, frame}
1923
\COMMENT{Prepare CreateAccount instruction lamports, space fields.}
2024
\STATE frame.create\_account\_data.space = \texttt{MarketHeader.size}
2125
\STATE acct\_size = \texttt{MarketHeader.size} + \texttt{account.STORAGE\_OVERHEAD}
2226
\STATE lamports\_per\_byte = acct.data.lamports\_per\_byte
27+
\STATE frame.lamports\_per\_byte = lamports\_per\_byte
2328
\STATE frame.create\_account\_data.lamports = acct\_size $\times$ lamports\_per\_byte
2429
\COMMENT{Initialize market PDA signer seeds.}
2530
\STATE frame.pda\_seeds[0].addr = input.base\_mint.address
@@ -68,6 +73,7 @@
6873
\STATE frame.signers\_seeds.addr = \&frame.pda\_seeds
6974
\STATE frame.signers\_seeds.len = \texttt{RegisterMarketFrame.PDA\_SEEDS\_N\_SEEDS}
7075
\COMMENT{Populate SolInstruction for CreateAccount CPI.}
76+
\STATE frame.sol\_instruction.program\_id = frame.system\_program\_id
7177
\STATE frame.sol\_instruction.accounts = \&frame.cpi.account\_metas
7278
\STATE frame.sol\_instruction.account\_len =
7379
\texttt{register\_misc.CREATE\_ACCOUNT\_N\_ACCOUNTS}

docs/algorithms/INIT-VAULT.tex

Lines changed: 98 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,23 @@
1313
\texttt{true},
1414
\texttt{false}
1515
\}
16-
\COMMENT{Pointer to owning token program address.}
16+
\REQUIRE *frame.mint $\in$ \{
17+
input.base\_mint,
18+
input\_shifted.quote\_mint
19+
\}
1720
\REQUIRE frame.token\_program\_id
18-
\COMMENT{Pointer to input buffer.}
21+
\REQUIRE frame.program\_id
1922
\REQUIRE frame.input
23+
\REQUIRE frame.lamports\_per\_byte
2024
\REQUIRE frame.pda\_seeds[0].len = \texttt{Address.size}
25+
\REQUIRE frame.pda\_seeds[2].addr = \&frame.bump
26+
\REQUIRE frame.pda\_seeds[2].len = \texttt{u8.size}
27+
\REQUIRE *frame.system\_program\_id = input.system\_program.address
28+
\REQUIRE frame.signers\_seeds.addr = \&frame.pda\_seeds
29+
\REQUIRE frame.signers\_seeds.len = \texttt{RegisterMarketFrame.PDA\_SEEDS\_N\_SEEDS}
30+
\REQUIRE input.user.data\_len = \texttt{data.LEN\_ZERO}
31+
\REQUIRE acct.data\_len = \texttt{data.LEN\_ZERO}
32+
\REQUIRE frame.cpi[0].info.executable = \texttt{false}
2133
\FUNCTION{INIT-VAULT}{acct, frame}
2234
\STATE \CALL{Store}{frame}
2335
\STATE \CALL{Store}{acct}
@@ -27,7 +39,7 @@
2739
\STATE frame.pda\_seeds[1].len = \texttt{u8.size}
2840
\STATE syscall.seeds = \&frame.pda\_seeds
2941
\STATE syscall.seeds\_len = \texttt{register\_misc.TRY\_FIND\_VAULT\_PDA\_SEEDS\_LEN}
30-
\STATE syscall.program\_id = frame.token\_program\_id
42+
\STATE syscall.program\_id = frame.program\_id
3143
\STATE syscall.program\_address = \&frame.pda
3244
\STATE syscall.bump\_seed = \&frame.bump
3345
\STATE \CALL{sol-try-find-program-address}{}
@@ -39,6 +51,89 @@
3951
\RETURN \texttt{ErrorCode::InvalidQuoteVaultPubkey}
4052
\ENDIF
4153
\ENDIF
54+
\COMMENT{Determine token account size.}
55+
\IF{frame.token\_program\_is\_2022}
56+
\COMMENT{Set up mint as CPI account.}
57+
\STATE frame.cpi[0].info.is\_signer = \texttt{false}
58+
\STATE frame.cpi[0].info.is\_writable = \texttt{false}
59+
\STATE frame.cpi[0].meta.is\_signer = \texttt{false}
60+
\STATE frame.cpi[0].meta.is\_writable = \texttt{false}
61+
\STATE frame.cpi[0].meta.pubkey = \&mint.address
62+
\STATE frame.cpi[0].info.key = \&mint.address
63+
\STATE frame.cpi[0].info.owner = \&mint.owner
64+
\STATE frame.cpi[0].info.lamports = \&mint.lamports
65+
\STATE frame.cpi[0].info.data\_len = mint.data\_len
66+
\STATE frame.cpi[0].info.data = \&mint.data
67+
\COMMENT{Populate SolInstruction for GetAccountDataSize CPI.}
68+
\STATE frame.sol\_instruction.program\_id = frame.token\_program\_id
69+
\STATE frame.sol\_instruction.accounts = \&frame.cpi[0].meta
70+
\STATE frame.sol\_instruction.account\_len =
71+
\texttt{token.GET\_ACCOUNT\_DATA\_SIZE\_N\_ACCOUNTS}
72+
\STATE frame.get\_account\_data\_size\_data =
73+
\texttt{token.GET\_ACCOUNT\_DATA\_SIZE\_DISC}
74+
\STATE frame.sol\_instruction.data = \&frame.get\_account\_data\_size\_data
75+
\STATE frame.sol\_instruction.data\_len = \texttt{u8.size}
76+
\COMMENT{Invoke GetAccountDataSize CPI (no signers).}
77+
\STATE syscall.instruction = \&frame.sol\_instruction
78+
\STATE syscall.account\_infos = \&frame.cpi[0].info
79+
\STATE syscall.account\_infos\_len =
80+
\texttt{token.GET\_ACCOUNT\_DATA\_SIZE\_N\_ACCOUNTS}
81+
\STATE syscall.seeds\_len = \texttt{token.GET\_ACCOUNT\_DATA\_SIZE\_N\_SEEDS}
82+
\STATE \CALL{sol-invoke-signed-c}{}
83+
\COMMENT{Get return data.}
84+
\STATE syscall.bytes = \&frame.token\_account\_data\_size
85+
\STATE syscall.bytes\_len = \texttt{u64.size}
86+
\STATE syscall.program\_id = \&frame.get\_return\_data\_program\_id
87+
\STATE \CALL{sol-get-return-data}{}
88+
\STATE acct\_size = frame.token\_account\_data\_size
89+
\COMMENT{Override default return data nonzero return code.}
90+
\STATE result = \texttt{entrypoint.RETURN\_SUCCESS}
91+
\ELSE
92+
\STATE acct\_size = \texttt{token.ACCOUNT\_SIZE}
93+
\ENDIF
94+
\COMMENT{Prepare CreateAccount instruction fields.}
95+
\STATE frame.create\_account\_data.space = acct\_size
96+
\STATE acct\_size = acct\_size + \texttt{account.STORAGE\_OVERHEAD}
97+
\STATE frame.create\_account\_data.lamports =
98+
acct\_size $\times$ frame.lamports\_per\_byte
99+
\STATE frame.create\_account\_data.owner = frame.token\_program\_id
100+
\COMMENT{Assign CPI account fields via immediates.}
101+
\STATE frame.cpi[0].info.is\_signer = \texttt{true}
102+
\STATE frame.cpi[0].info.is\_writable = \texttt{true}
103+
\STATE frame.cpi[0].meta.is\_signer = \texttt{true}
104+
\STATE frame.cpi[0].meta.is\_writable = \texttt{true}
105+
\STATE frame.cpi[1].info.is\_signer = \texttt{true}
106+
\STATE frame.cpi[1].info.is\_writable = \texttt{true}
107+
\STATE frame.cpi[1].meta.is\_signer = \texttt{true}
108+
\STATE frame.cpi[1].meta.is\_writable = \texttt{true}
109+
\STATE frame.cpi[0].info.data\_len = \texttt{data.LEN\_ZERO}
110+
\STATE frame.cpi[1].info.data\_len = \texttt{data.LEN\_ZERO}
111+
\COMMENT{Assign CPI account fields via pointers.}
112+
\STATE frame.cpi[0].meta.pubkey = \&input.user.address
113+
\STATE frame.cpi[0].info.key = \&input.user.address
114+
\STATE frame.cpi[0].info.owner = \&input.user.owner
115+
\STATE frame.cpi[0].info.lamports = \&input.user.lamports
116+
\STATE frame.cpi[0].info.data = \&input.user.data
117+
\STATE frame.cpi[1].meta.pubkey = \&acct.address
118+
\STATE frame.cpi[1].info.key = \&acct.address
119+
\STATE frame.cpi[1].info.owner = \&acct.owner
120+
\STATE frame.cpi[1].info.lamports = \&acct.lamports
121+
\STATE frame.cpi[1].info.data = \&acct.data
122+
\COMMENT{Populate SolInstruction for CreateAccount CPI.}
123+
\STATE frame.sol\_instruction.program\_id = frame.system\_program\_id
124+
\STATE frame.sol\_instruction.accounts = \&frame.cpi.account\_metas
125+
\STATE frame.sol\_instruction.account\_len =
126+
\texttt{register\_misc.CREATE\_ACCOUNT\_N\_ACCOUNTS}
127+
\STATE frame.sol\_instruction.data = \&frame.create\_account\_data
128+
\STATE frame.sol\_instruction.data\_len = \texttt{CreateAccountData.size}
129+
\COMMENT{Invoke CreateAccount CPI.}
130+
\STATE syscall.instruction = \&frame.sol\_instruction
131+
\STATE syscall.account\_infos = \&frame.cpi.account\_infos
132+
\STATE syscall.account\_infos\_len =
133+
\texttt{register\_misc.CREATE\_ACCOUNT\_N\_ACCOUNTS}
134+
\STATE syscall.seeds = \&frame.signers\_seeds
135+
\STATE syscall.seeds\_len = \texttt{register\_misc.N\_PDA\_SIGNERS}
136+
\STATE \CALL{sol-invoke-signed-c}{}
42137
\ENDFUNCTION
43138
\end{algorithmic}
44139
\end{algorithm}

0 commit comments

Comments
 (0)