From 7ba79a9ba5e6dc759b9072a1d3e41592835abdaf Mon Sep 17 00:00:00 2001 From: connelblaze Date: Tue, 28 Jul 2026 07:10:34 +0100 Subject: [PATCH] add a get_owner_weights returning a full list --- contracts/accord/src/lib.rs | 20 +++++++++++ contracts/accord/src/test.rs | 66 ++++++++++++++++++++++++++++++++++++ docs/CONTRACT_API.md | 17 ++++++++++ 3 files changed, 103 insertions(+) diff --git a/contracts/accord/src/lib.rs b/contracts/accord/src/lib.rs index 296dd53..8916b52 100644 --- a/contracts/accord/src/lib.rs +++ b/contracts/accord/src/lib.rs @@ -87,6 +87,13 @@ pub struct ProposalApprovalProgress { pub total_weight: u32, } +#[derive(Clone, Debug, Eq, PartialEq)] +#[contracttype] +pub struct OwnerWeight { + pub owner: Address, + pub weight: u32, +} + #[derive(Clone, Debug, Eq, PartialEq)] #[contracttype] pub struct ProposalCreatedEvent { @@ -2022,6 +2029,19 @@ impl AccordContract { Ok(read_owners_map(&env)?.keys()) } + /// Returns every current owner's address paired with their voting weight, + /// in a single call. The sum of the returned weights equals the current + /// total-weight counter. Read-only; no authorization required. + pub fn get_owner_weights(env: Env) -> Result, ContractError> { + let owners = read_owners_map(&env)?; + let mut result = Vec::new(&env); + for owner in owners.keys().iter() { + let weight = owners.get(owner.clone()).unwrap_or(0); + result.push_back(OwnerWeight { owner, weight }); + } + Ok(result) + } + /// Returns the spending limit for an (owner, token) pair, or `None` if no /// limit is set (the owner is unrestricted for that token). pub fn get_spending_limit(env: Env, owner: Address, token: Address) -> Option { diff --git a/contracts/accord/src/test.rs b/contracts/accord/src/test.rs index 331722e..3ea9f21 100644 --- a/contracts/accord/src/test.rs +++ b/contracts/accord/src/test.rs @@ -5609,6 +5609,72 @@ fn get_owner_weight_returns_owner_not_found_for_non_owner() { assert_eq!(client.get_owner_weight(&owner_b), 1); } +// ─── get_owner_weights ──────────────────────────────────────────────────── + +/// Confirms get_owner_weights returns every owner with the correct weight +/// for a multisig with several owners holding different weights, and that +/// the sum of returned weights matches the total-weight counter. +#[test] +fn get_owner_weights_returns_all_owners_with_correct_weights() { + let (env, client, owner_a, owner_b, owner_c, token_client) = + setup_three_owner_weighted([5, 3, 2], 8); + + let result = client.get_owner_weights(); + + assert_eq!(result.len(), 3); + + let mut sum: u32 = 0; + for entry in result.iter() { + match entry.owner { + _ if entry.owner == owner_a => assert_eq!(entry.weight, 5), + _ if entry.owner == owner_b => assert_eq!(entry.weight, 3), + _ if entry.owner == owner_c => assert_eq!(entry.weight, 2), + _ => panic!("unexpected owner in result"), + } + sum = sum.checked_add(entry.weight).unwrap(); + } + + assert_eq!(sum, client.get_total_weight()); +} + +/// After adding and then removing an owner, get_owner_weights must reflect +/// the current set and the total-weight counter must still match. +#[test] +fn get_owner_weights_reflects_owner_changes() { + let (env, client, owner_a, owner_b, owner_c, non_owner, token_client) = setup(2); + + // Initial: 3 owners each weight 1, total_weight = 3. + let result = client.get_owner_weights(); + assert_eq!(result.len(), 3); + let mut sum: u32 = 0; + for entry in result.iter() { + assert_eq!(entry.weight, 1); + sum = sum.checked_add(entry.weight).unwrap(); + } + assert_eq!(sum, 3); + + // Add non_owner as a fourth owner (weight 1 by default). + let add_id = client.create_add_owner_proposal( + &owner_a, + &non_owner, + &str(&env, "Add fourth owner"), + &DEADLINE, + ); + client.approve(&owner_a, &add_id); + client.approve(&owner_b, &add_id); + client.execute(&owner_c, &add_id); + + let result = client.get_owner_weights(); + assert_eq!(result.len(), 4); + let mut sum: u32 = 0; + for entry in result.iter() { + assert_eq!(entry.weight, 1); + sum = sum.checked_add(entry.weight).unwrap(); + } + assert_eq!(sum, 4); + assert_eq!(sum, client.get_total_weight()); +} + // ─── Issue #320: total-weight overflow rejection ───────────────────────────── /// Tests that the overflow-checked arithmetic protecting the total-weight diff --git a/docs/CONTRACT_API.md b/docs/CONTRACT_API.md index 6a6d6f0..3a36027 100644 --- a/docs/CONTRACT_API.md +++ b/docs/CONTRACT_API.md @@ -294,6 +294,23 @@ Returns the current voting weight for `owner`. The weight reflects the owner's i --- +## `get_owner_weights` + +```rust +fn get_owner_weights(env: Env) -> Result, ContractError> +``` + +Returns every current owner's address paired with their voting weight, in a single call. The returned list is a `Vec` where each entry contains an `owner` field (the address) and a `weight` field (the owner's individual voting weight). The sum of all returned weights equals the current total-weight counter. This avoids the need for N separate `get_owner_weight` calls when rendering a full governance overview. Read-only; no authorization required. + +| Return field | Type | Description | +|---|---|---| +| `owner` | `Address` | A current owner's address | +| `weight` | `u32` | That owner's individual voting weight | + +**Errors:** `NotInitialized` + +--- + ## `has_approved` ```rust