Skip to content

docs(contracts): document the proposals storage layout and treasury interface - #607

Closed
jarik2014 wants to merge 1 commit into
codebestia:devfrom
jarik2014:docs/470-proposals-storage
Closed

jarik2014 wants to merge 1 commit into
codebestia:devfrom
jarik2014:docs/470-proposals-storage

Conversation

@jarik2014

Copy link
Copy Markdown

Closes #470

Adds contracts/docs/contracts-proposals-storage.md: the storage layout of the proposals contract (DataKey, Proposal, ProposalStatus and the transitions the code actually permits, vote tracking, the event structs in storage.rs) and the typed client it uses to call group_treasury. Style follows the existing contracts/docs/*.md files (overview → keys → types → interface → gaps, File:line citations inline).

What the doc covers

  • Every DataKey variant with its storage type and value type, plus which function writes it.
  • All six ProposalStatus values, with a transition table saying which transitions the code allows, which it refuses, and which test pins each one. Includes the two boundary asymmetries: finalize_proposal accepts now == expires_at while finalize_expired_proposal refuses it, and Approved is declared but no code path ever assigns it.
  • Vote tracking: the per-voter DataKey::Vote(u64, Address) -> bool entry is the double-vote guard, alongside the aggregate yes_votes/no_votes counters that finalize_proposal actually reads.
  • The cross-contract call: the #[contractclient] trait, how the contract ID comes from Proposal.treasury and is handed to TreasuryClient::new, and the three ordered calls (is_member, balance, withdraw) with the guard each one feeds.
  • TTL/bump: no TTL code exists anywhere in the workspace, so the section documents the absence with the greps below, plus what the committed test snapshots show the host doing by default.

One thing to flag

treasury_interface_client.rs is a single comment line — // Intentionally left empty - this file was generated automatically. — and is not declared in the module tree, so it is never compiled. The typed client the issue asks about is generated by the #[contractclient(name = "TreasuryClient")] attribute on the trait in treasury_interface.rs. The doc documents that file as the empty placeholder it is and points at the macro instead, rather than describing a mechanism that does not exist in it.

Acceptance criteria → evidence

Criterion Where it is documented Source evidence
Every storage key/structure with its type Storage Keys, The Proposal Data Structure storage.rs:3-9 (DataKey), storage.rs:22-39 (Proposal); writes at lib.rs:46, lib.rs:93-95, lib.rs:96-98, lib.rs:133, lib.rs:140-142, lib.rs:176-178, lib.rs:204-206, lib.rs:225-227, lib.rs:280-282; reads at lib.rs:42, lib.rs:73-77, lib.rs:130, lib.rs:298-303
Full set of ProposalStatus values ProposalStatus and Permitted Transitions storage.rs:11-20; assignments at lib.rs:86, lib.rs:171, lib.rs:173, lib.rs:203, lib.rs:224, lib.rs:279; Approved referenced only at storage.rs:15, lib.rs:241, lib.rs:254
Transitions the code allows transition table + refusal list guards at lib.rs:66, lib.rs:69, lib.rs:121-123, lib.rs:125-127, lib.rs:162, lib.rs:166, lib.rs:170, lib.rs:173, lib.rs:195, lib.rs:199, lib.rs:221, lib.rs:250, lib.rs:253; tests: create_then_vote_then_pass_then_execute_happy_path, finalize_with_more_no_votes_rejects, finalize_with_a_tie_rejects, finalize_with_zero_votes_rejects, finalize_before_expiry_panics, finalize_twice_panics, finalize_expired_success, finalize_expired_when_passed_panics, finalize_expired_before_expiry_panics, execute_when_rejected_panics, execute_when_still_active_panics, vote_after_expiry_panics, double_vote_panics, execute_withdraw_pending_panics, execute_withdraw_already_executed_panics, execute_withdraw_non_member_panics, execute_withdraw_reduces_balance (test.rs:150-473)
Vote-tracking storage Vote Tracking storage.rs:8; lib.rs:129-133 (key + duplicate guard), lib.rs:135-139 (counters), lib.rs:170-174 (tally reads counters only), events at lib.rs:144-151
treasury_interface_client.rs cross-contract mechanism Treasury Cross-Contract Client placeholder file content (1 line, uncompiled — lib.rs:19-22 module list); generated client from treasury_interface.rs:4-9; contract ID from Proposal.treasury (lib.rs:87) into TreasuryClient::new (lib.rs:258-259); calls at lib.rs:261, lib.rs:266, lib.rs:272; treasury side at group_treasury/src/lib.rs:126, :175, :205, require_admin at :15/:180; nested-call auth noted in test.rs:78-81; constructor shape confirmed in soroban-sdk-macros-22.0.11/src/derive_client.rs (derive_client_type)
Storage type Storage Types Used all accesses are env.storage().instance() (grep output below); persistent storage appears only in the test mock token (test.rs:28-53)
TTL/bump behaviour TTL and Bump Behaviour greps below return nothing; snapshot test_snapshots/test/create_then_vote_then_pass_then_execute_happy_path.1.json shows the instance entry at live_until_ledger_seq = 4095, ledger defaults min_persistent_entry_ttl 4096, min_temp_entry_ttl 16, max_entry_ttl 6312000

Commands run and output

$ grep -n "ProposalStatus" contracts/contracts/proposals/src/storage.rs
13:pub enum ProposalStatus {
32:    pub status: ProposalStatus,
68:    pub status: ProposalStatus,

$ grep -rn "extend_ttl\|bump" contracts/contracts/proposals/src/
(no matches, exit status 1)

$ grep -rn "extend_ttl" contracts/contracts/
(no matches, exit status 1)

$ grep -rn "storage()" contracts/contracts/proposals/src/lib.rs
42:        if env.storage().instance().has(&DataKey::Admin) {
46:        env.storage().instance().set(&DataKey::Admin, &admin);
47:        env.storage()
74:            .storage()
93:        env.storage()
96:        env.storage()
130:        if env.storage().instance().has(&vote_key) {
133:        env.storage().instance().set(&vote_key, &support);
140:        env.storage()
176:        env.storage()
204:        env.storage()
225:        env.storage()
280:        env.storage()
299:        env.storage()

$ cat contracts/contracts/proposals/src/treasury_interface_client.rs
// Intentionally left empty - this file was generated automatically.

$ grep -n "^mod\|^pub mod" contracts/contracts/proposals/src/lib.rs
19:mod storage;
20:mod test;
22:mod treasury_interface;

Every storage access in the contract resolves to .instance() at the following line (the multi-line env.storage() calls above continue on the next line): lib.rs:47-49, 74-77, 93-95, 96-98, 140-142, 176-178, 204-206, 225-227, 280-282, 299-302.

Build and test verification

Toolchain: cargo 1.98.1, rustc 1.98.1 (contracts/rust-toolchain.toml: stable, wasm32-unknown-unknown). Dependencies are not vendored, so --offline cannot resolve them; the run below used the network.

$ cargo check -p proposals
    Checking proposals v0.1.0 (/tmp/ck-470/contracts/contracts/proposals)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 35.39s
(exit status 0)

$ cargo test -p proposals
running 18 tests
test test::create_with_past_expiry_panics - should panic ... ok
test test::execute_when_still_active_panics - should panic ... ok
test test::execute_withdraw_pending_panics - should panic ... ok
test test::execute_withdraw_non_member_panics - should panic ... ok
test test::execute_withdraw_reduces_balance ... ok
test test::double_vote_panics - should panic ... ok
test test::execute_when_rejected_panics - should panic ... ok
test test::execute_withdraw_already_executed_panics - should panic ... ok
test test::create_then_vote_then_pass_then_execute_happy_path ... ok
test test::finalize_expired_when_passed_panics - should panic ... ok
test test::finalize_expired_before_expiry_panics - should panic ... ok
test test::finalize_before_expiry_panics - should panic ... ok
test test::finalize_expired_success ... ok
test test::finalize_twice_panics - should panic ... ok
test test::vote_after_expiry_panics - should panic ... ok
test test::finalize_with_a_tie_rejects ... ok
test test::finalize_with_more_no_votes_rejects ... ok
test test::finalize_with_zero_votes_rejects ... ok

test result: ok. 18 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.06s

Docs only — no Rust, config, Cargo.toml or Cargo.lock file is touched. Diff is the single new markdown file.


Retargeted to dev — the previous PR (#605) was auto-closed because contributor PRs must merge into dev, not main.

@codebestia codebestia closed this Sep 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants