Skip to content

Commit 3757937

Browse files
committed
Add README comments re Solana hackathon and move 2 SVM demos to public repo
1 parent 837d56c commit 3757937

File tree

25 files changed

+2201
-44
lines changed

25 files changed

+2201
-44
lines changed

README.md

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,31 @@
1-
# Certora AI Composer (Solidity + Solana/SVM Experimental)
1+
# Spec-driven AI Coder (Solidity + Solana/SVM Experimental)
22

3-
AI Composer is a tool for generating verified implementations from documentation and CVL specifications.
3+
Spec-driven AI Coder is an AI agent that codes more securely by generating verified implementations from documentation and specifications, with test-driven development (TDD) and formal verification (FV) support.
4+
5+
# Scope Accomplished for Solana Student Hackathon - Fall 2025
6+
7+
For this hackathon, I extended the original Certora AI Composer (which previously targeted only the EVM) with an experimental Solana/SVM pipeline. The expansions include:
8+
9+
- SVM target selection.
10+
- Solana‑specific prompts and workflows.
11+
- Solana/CVLR (Certora Verification Language for Rust) RAG stack.
12+
- TDD-oriented development flow: the ability to run in a “tests‑first” mode where the Composer generates code guided by passing tests, with an explicit `--no-fv` option to skip formal verification early on and iterate quickly.
13+
- End‑to‑end SVM example: a trivial Rust project under `examples/svm/materialized_trivial_addition` [demonstrating the full loop](examples/svm/materialized_trivial_addition/terminal_logs_for_trivial_demo_with_formal_verification.txt) from natural language/system doc → Rust code → formal checks.
14+
- [Vault program example](examples/svm/materialized_vault) done by TDD.
15+
16+
Compared to the earlier EVM‑only prototype from Certora Labs, this fork now supports a second, Solana‑native verification path (Rust + certoraSolanaProver) with Solana‑specific prompts, RAG, and examples, while staying aligned with the original Composer architecture.
17+
18+
# Next Steps for Solana Development
19+
My next steps would be to:
20+
- Test having the AI Composer implement the vault application in Certora/SolanaExamples with formal verification enabled
21+
- Translate [novel AMM](examples/cccp_fixed/system_doc.txt) spec to CVLR
22+
- Test having the AI Composer implement the novel AMM for Solana
23+
- Test with an Anchor project and make any needed adjustments
24+
- Enable the agent to use the Solana MCP
25+
- Explore integration of Kani Rust Verifier before running the more specific Certora Solana Prover
26+
- Make more developer-friendly
27+
- Enable using a local LLM
28+
- Do AI coding workshops/bootcamps advocating for TDD and FV adoption
429

530
# Installation
631

@@ -209,5 +234,7 @@ to the updated/refined spec file to use for the next iteration.
209234

210235
# Disclaimer
211236

212-
AI Composer is a research prototype released by Certora Labs. The code generated by AI Composer should **not** be
237+
AI Composer is a research prototype initially released by Certora Labs. This Solana extension is not affiliated to Certora Labs.
238+
239+
The code generated by AI Composer should **not** be
213240
placed into production without thorough vetting/testing/auditing.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
crypto_session_2f8508c2-dc70-11f0-a4d3-1c697aaa77f7

examples/svm/trivial/Cargo.toml renamed to examples/svm/materialized_trivial_addition/Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[package]
2-
name = "svm_trivial"
2+
name = "simple-addition"
33
version = "0.1.0"
44
edition = "2021"
55

@@ -8,9 +8,10 @@ crate-type = ["cdylib"]
88

99
[features]
1010
certora = []
11+
rt = []
1112

1213
[dependencies]
13-
cvlr = { path = "../../../cvlr/cvlr" }
14+
cvlr = "0.4"
1415

1516
[package.metadata.certora]
1617
sources = [
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
; By default we do not inline core, std, alloc, and solana_program
2+
; with some exceptions below with #[inline]
3+
4+
#[inline(never)] ^core::.*$
5+
#[inline(never)] ^std::.*$
6+
#[inline(never)] ^<?alloc::.*$
7+
#[inline(never)] ^solana_program::.*$
8+
9+
; CVT functions
10+
#[inline(never)] ^([^:]+::)*CVT_.*$
11+
12+
; Rust memory allocation functions
13+
#[inline(never)] ^__rust_alloc$
14+
#[inline(never)] ^__rust_dealloc$
15+
#[inline(never)] ^__rust_alloc_zeroed$
16+
#[inline(never)] ^__rg_alloc$
17+
#[inline(never)] ^__rg_dealloc$
18+
#[inline(never)] ^__rg_oom$
19+
20+
;; We want to inline wrappers that call the global allocator
21+
#[inline] ^alloc::alloc::exchange_malloc$
22+
;;;#[inline] ^alloc::fmt::format::format_inner$
23+
24+
; memcpy/memmove/memset/memcmp
25+
; These functions are wrappers to sol_memcpy_, sol_memmove_,
26+
; sol_memset_, and sol_memcmp_. These wrappers ensure that sol_*
27+
; preconditions are satisfied when these functions are called
28+
; (alignment conditions, non-nullity, etc). Since, we are not interested in
29+
; verifying the code of the wrappers, we don't inline calls to
30+
; memcpy, memmove, memset, and memcmp so that we can replace them
31+
; directly with sol_memcpy_, sol_memmove_, sol_memset_, and
32+
; sol_memcmp_, respectively.
33+
#[inline(never)] ^memcpy$
34+
#[inline(never)] ^memmove$
35+
#[inline(never)] ^memset$
36+
#[inline(never)] ^memcmp$
37+
38+
39+
; Compiler-RT: integer arithmetic routines used on platforms that don't provide HW support
40+
; All the functions are described here
41+
; https://github.com/llvm/llvm-project/blob/main/compiler-rt/lib/builtins/README.txt
42+
;
43+
; Starting with sbfv2, the code of compiler-rt library is not included in the final ELF file
44+
; but in ebpf and sbf did so we make sure that we don't inline those functions.
45+
;
46+
; Integral bit manipulation
47+
#[inline(never)] ^__ashldi3$
48+
#[inline(never)] ^__ashlti3$
49+
#[inline(never)] ^__ashrdi3$
50+
#[inline(never)] ^__ashrti3$
51+
#[inline(never)] ^__lshrdi3$
52+
#[inline(never)] ^__lshrti3$
53+
#[inline(never)] ^__clzsi2$
54+
#[inline(never)] ^__clzdi2$
55+
#[inline(never)] ^__clzti2$
56+
#[inline(never)] ^__ctzsi2$
57+
#[inline(never)] ^__ctzdi2$
58+
#[inline(never)] ^__ctzti2$
59+
#[inline(never)] ^__ffssi2$
60+
#[inline(never)] ^__ffsdi2$
61+
#[inline(never)] ^__ffsti2$
62+
#[inline(never)] ^__paritysi2$
63+
#[inline(never)] ^__paritydi2$
64+
#[inline(never)] ^__parityti2$
65+
#[inline(never)] ^__popcountsi2$
66+
#[inline(never)] ^__popcountdi2$
67+
#[inline(never)] ^__popcountti2$
68+
#[inline(never)] ^__bswapsi2$
69+
#[inline(never)] ^__bswapdi2$
70+
; integral arithmetic
71+
#[inline(never)] ^__negdi2$
72+
#[inline(never)] ^__negti2$
73+
#[inline(never)] ^__muldi3$
74+
#[inline(never)] ^__multi3$
75+
#[inline(never)] ^__divsi3$
76+
#[inline(never)] ^__divdi3$
77+
#[inline(never)] ^__divti3$
78+
#[inline(never)] ^__udivsi3$
79+
#[inline(never)] ^__udivdi3$
80+
#[inline(never)] ^__udivti3$
81+
#[inline(never)] ^__modsi3$
82+
#[inline(never)] ^__moddi3$
83+
#[inline(never)] ^__modti3$
84+
#[inline(never)] ^__umodsi3$
85+
#[inline(never)] ^__umoddi3$
86+
#[inline(never)] ^__umodti3$
87+
#[inline(never)] ^__udivmoddi4$
88+
#[inline(never)] ^__udivmodti4$
89+
#[inline(never)] ^__udivmodsi4$
90+
#[inline(never)] ^__divmodsi4$
91+
#[inline(never)] ^__divmoddi4$
92+
#[inline(never)] ^__divmodti4$
93+
; floating point arithmetic
94+
#[inline(never)] ^(compiler_builtins::float::add::)?__adddf3$
95+
#[inline(never)] ^__muldf3$
96+
#[inline(never)] ^(compiler_builtins::float::div::)?__divdf3$
97+
#[inline(never)] ^(compiler_builtins::math::libm::exp::)?exp$
98+
#[inline(never)] ^__floatundidf$
99+
#[inline(never)] ^__powidf2$
100+
#[inline(never)] ^__unorddf2$
101+
#[inline(never)] ^__truncdfsf2$
102+
#[inline(never)] ^__ltdf2$
103+
#[inline(never)] ^__gtdf2$
104+
#[inline(never)] ^__fixdfdi$
105+
#[inline(never)] ^__gedf2$
106+
#[inline(never)] ^__floatsidf$
107+
#[inline(never)] ^__subdf3$
108+
#[inline(never)] ^__floattidf$
109+
110+
#[inline(never)] ^.*::fmt$
111+
112+
;; This is a wrapper so we inline it
113+
#[inline] ^([^:]+::)*CVT_uninterpreted_usize$
114+
115+
#[inline] ^solana_program::account_info::AccountInfo::new$
116+
#[inline] ^solana_program::account_info::AccountInfo::lamports$
117+
#[inline] ^solana_program::account_info::AccountInfo::try_borrow_mut_lamports$
118+
#[inline] ^solana_program::account_info::AccountInfo::data_len$
119+
#[inline] ^solana_program::account_info::AccountInfo::try_data_len$
120+
#[inline] ^solana_program::account_info::AccountInfo::try_borrow_data$
121+
#[inline] ^solana_program::account_info::AccountInfo::try_borrow_mut_data$
122+
#[inline] ^solana_program::account_info::AccountInfo::data_is_empty$
123+
#[inline] ^solana_program::program::invoke_signed$
124+
#[inline] ^solana_program::program::invoke$
125+
#[inline] ^solana_program::program_pack::Pack::unpack$
126+
#[inline] ^solana_program::hash::Hash::new_from_array$
127+
#[inline] ^solana_program::sysvar::clock::<impl solana_program::sysvar::Sysvar for solana_program::clock::Clock>::get$
128+
#[inline] ^solana_program::poseidon::PoseidonHash::new$
129+
#[inline] ^solana_program::account_info::AccountInfo::assign$
130+
#[inline] ^solana_program::incinerator::check_id$
131+
#[inline] ^solana_program::system_program::check_id$
132+
#[inline] ^solana_program::system_program::id$
133+
#[inline] ^solana_program::rent::Rent::minimum_balance$
134+
#[inline] ^solana_program::sysvar::rent::<impl solana_program::sysvar::Sysvar for solana_program::rent::Rent>::get$
135+
#[inline] ^solana_program::instruction::get_stack_height$
136+
#[inline] ^solana_program::program::set_return_data$
137+
138+
#[inline(never)] ^<solana_program::program_error::ProgramError as core::convert::From<u64>>::from$
139+
140+
#[inline] ^core::result::unwrap_failed$
141+
#[inline] ^core::cell::RefCell<T>::borrow(_\d+)?$
142+
#[inline] ^core::cell::RefCell<T>::borrow_mut(_\d+)?$
143+
144+
145+
;; Borsh and common functions used by Borsh
146+
#[inline(never)] ^std::io::error::Error::new(_\d+)?$
147+
#[inline(never)] ^borsh::de::unexpected_eof_to_unexpected_length_of_input$
148+
149+
150+
;; We need to inline this function to avoid unsoundness results in
151+
;; NcnOperatorTicket::seeds and others.
152+
#[inline] ^<alloc::vec::Vec<T> as alloc::vec::spec_from_iter::SpecFromIter<T,I>>::from_iter(_\d+)?$
153+
154+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
155+
;; Anchor-specific inlining
156+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
157+
158+
;; By default we don't inline anything from anchor.
159+
#[inline(never)] ^.*anchor_lang.*$
160+
161+
;; except these functions
162+
163+
#[inline] ^anchor_lang::accounts::account_loader::AccountLoader<T>::load(_[0-9][0-9]*)*$
164+
#[inline] ^anchor_lang::accounts::account_loader::AccountLoader<T>::load_mut(_[0-9][0-9]*)*$
165+
166+
#[inline] ^<anchor_lang::accounts::account::Account<T> as core::clone::Clone>::clone(_[0-9][0-9]*)*$
167+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
168+
;; try_from and try_from_unchecked might call to deserialize so we need to check case by case
169+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
170+
#[inline] ^anchor_lang::accounts::account_loader::AccountLoader<T>::try_from(_[0-9][0-9]*)*$
171+
#[inline] ^anchor_lang::accounts::account_loader::AccountLoader<T>::try_from_unchecked(_[0-9][0-9]*)*$
172+
#[inline] ^anchor_lang::accounts::account::Account<T>::try_from_unchecked(_[0-9][0-9]*)*$
173+
#[inline] ^anchor_lang::accounts::account::Account<T>::try_from(_[0-9][0-9]*)*$
174+
#[inline] ^anchor_lang::accounts::signer::Signer::try_from$
175+
#[inline] ^<anchor_lang::accounts::program::Program<T> as core::convert::TryFrom<&solana_program::account_info::AccountInfo>>::try_from$
176+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
177+
178+
#[inline] ^<anchor_lang::accounts::unchecked_account::UncheckedAccount as core::convert::AsRef<solana_program::account_info::AccountInfo>>::as_ref$
179+
#[inline] ^<anchor_lang::accounts::unchecked_account::UncheckedAccount as anchor_lang::ToAccountInfos>::to_account_infos$
180+
181+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
182+
;;; These are needed to include the code for key()
183+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
184+
#[inline] ^<anchor_lang::accounts::unchecked_account::UncheckedAccount as anchor_lang::Key>::key$
185+
#[inline] ^<solana_program::pubkey::Pubkey as anchor_lang::Key>::key$
186+
#[inline] ^.*::ZeroCopyAccessor<solana_program::pubkey::Pubkey>>::get$
187+
#[inline] ^anchor_lang::accounts::account_info::<impl anchor_lang::Key for solana_program::account_info::AccountInfo>::key$
188+
189+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
190+
;;; These do conversion between error codes
191+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
192+
#[inline] ^<anchor_lang::error::Error as core::convert::From<anchor_lang::error::AnchorError>>::from$
193+
#[inline] ^<anchor_lang::error::Error as core::convert::From<anchor_lang::error::ErrorCode>>::from$
194+
#[inline] ^<anchor_lang::error::Error as core::convert::From<solana_program::program_error::ProgramError>>::from$
195+
#[inline] ^anchor_lang::error::<impl core::convert::From<anchor_lang::error::ErrorCode> for u32>::from$
196+
#[inline] ^squads_multisig_program::errors::<impl core::convert::From<squads_multisig_program::errors::MultisigError> for anchor_lang::error::Error>::from$
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2+
;;
3+
;; POINTS-TO SUMMARIES
4+
;;
5+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
6+
7+
;;; if the call returns then (*i64)(r1+0) is always a valid pointer.
8+
;;; 1st call:
9+
;;; - precondition: (*i64)(r1+0) is a Rust dangling pointer
10+
;;; - post-condition: (*i64)(r1+0) points to new allocated memory (malloc)
11+
;;; 2nd call:
12+
;;; - precondition: (*i64)(r1+0) is a valid pointer
13+
;;; - post-condition: (*i64)(r1+0) points to a new allocated memory after resizing the memory object
14+
;;; to which r1 pointed to before the call (realloc).
15+
#[type((*i64)(r1+0):ptr_heap)]
16+
^alloc::raw_vec::RawVec<T,A>::reserve_for_push(_[0-9][0-9]*)*$
17+
#[type((*i64)(r1+0):ptr_heap)]
18+
^alloc::raw_vec::RawVec<T,A>::reserve::do_reserve_and_handle(_[0-9][0-9]*)*$
19+
20+
#[type((*i64)(r1+0):num)]
21+
#[type((*i64)(r1+8):num)]
22+
^__multi3$
23+
24+
#[type((*i64)(r1+0):num)]
25+
#[type((*i64)(r1+8):num)]
26+
^__udivti3$
27+
28+
#[type((*i64)(r1+0):num)]
29+
#[type((*i64)(r1+8):num)]
30+
^__divti3$
31+
32+
#[type(r0:num)]
33+
^__muldf3$
34+
35+
#[type(r0:num)]
36+
^__divdf3$
37+
38+
#[type((*i64)(r1+0):num)]
39+
#[type((*i64)(r1+8):num)]
40+
#[type((*i64)(r1+16):num)]
41+
#[type((*i64)(r1+24):num)]
42+
#[type((*i64)(r1+32):num)]
43+
^sol_get_clock_sysvar$
44+
45+
;; %"AccountInfo" = type { %"Pubkey"*, i64*, i64*, %"Pubkey"*, i64, i8, i8, i8, [5 x i8] }
46+
#[type((*i64)(r1+0):ptr_external)]
47+
#[type((*i64)(r1+8):ptr_external)]
48+
#[type((*i64)(r1+16):ptr_external)]
49+
#[type((*i64)(r1+24):ptr_external)]
50+
#[type((*i64)(r1+32):num)]
51+
#[type((*i8)(r1+40):num)]
52+
#[type((*i8)(r1+41):num)]
53+
#[type((*i8)(r1+42):num)]
54+
^([^:]+::)*CVT_nondet_account_info$
55+
56+
#[type((*i64)(r1+0):num)]
57+
#[type((*i64)(r1+8):num)]
58+
#[type((*i64)(r1+16):num)]
59+
#[type((*i64)(r1+24):num)]
60+
^([^:]+::)*CVT_nondet_pubkey$
61+
62+
#[type((*i64)(r1+0):num)]
63+
#[type((*i64)(r1+8):num)]
64+
^([^:]+::)*CVT_nondet_layout_unchecked$
65+
66+
#[type(r0:ptr_external)]
67+
^([^:]+::)*CVT_nondet_pointer_usize$
68+
69+
#[type((*i32)(r1+0):num)]
70+
^solana_program::account_info::AccountInfo::realloc$
71+
72+
;; Result<Pubkey, PubkeyError>
73+
#[type((*i8)(r1+0):num)]
74+
#[type((*i64)(r1+1):num)]
75+
#[type((*i64)(r1+9):num)]
76+
#[type((*i64)(r1+17):num)]
77+
#[type((*i64)(r1+25):num)]
78+
^solana_program::pubkey::Pubkey::create_program_address$
79+
80+
;; (Pubkey, u8)
81+
#[type((*i64)(r1+0):num)]
82+
#[type((*i64)(r1+8):num)]
83+
#[type((*i64)(r1+16):num)]
84+
#[type((*i64)(r1+24):num)]
85+
#[type((*i8)(r1+32):num)]
86+
^solana_program::pubkey::Pubkey::find_program_address$
87+
88+
89+
#[type((*i32)(r1+0):num)]
90+
^solana_program::program::invoke_signed_unchecked$
91+
92+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
93+
;; Anchor-specific summaries
94+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
95+
96+
;; Empty for now
File renamed without changes.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
use crate::add;
2+
3+
use cvlr::prelude::*;
4+
5+
/// Verifies that `add` correctly computes the sum of two numbers.
6+
#[rule]
7+
pub fn rule_add_is_correct() {
8+
let x: u64 = nondet();
9+
let y: u64 = nondet();
10+
let result = add(x, y);
11+
cvlr_assert_eq!(result, x + y);
12+
}

0 commit comments

Comments
 (0)