Skip to content

feat: implement governance token contract for issue #41#166

Merged
CMI-James merged 2 commits intoTheBlockCade:mainfrom
ogazboiz:feat/issue-41-governance-token
Feb 26, 2026
Merged

feat: implement governance token contract for issue #41#166
CMI-James merged 2 commits intoTheBlockCade:mainfrom
ogazboiz:feat/issue-41-governance-token

Conversation

@ogazboiz
Copy link
Contributor

@ogazboiz ogazboiz commented Feb 26, 2026

This PR implements the Governance Token contract for the StellarCade platform. It includes core token functionality (mint, burn, transfer) and state getters, all protected by appropriate authorization checks. Comprehensive unit tests are included. Closes #41

Summary by CodeRabbit

  • Documentation

    • Added comprehensive governance token contract specification detailing the public API and authorization requirements.
  • New Features

    • Enhanced token operations with improved input validation.
    • Implemented admin authorization controls for privileged operations.
    • Added event emissions for state changes (initialization, transfers, minting, burning).

@coderabbitai
Copy link

coderabbitai bot commented Feb 26, 2026

📝 Walkthrough

Walkthrough

Implements a governance token contract with initialization accepting explicit parameters (name, symbol, decimals), minting and burning operations enforcing admin authorization, transfer functionality with sender validation, total supply tracking, and balance queries. Includes comprehensive error handling for invalid amounts, event emissions on state changes, and full documentation of the public API.

Changes

Cohort / File(s) Summary
Documentation
contracts/governance-token/README.md
Added comprehensive README documenting the governance token contract's public API (init, mint, burn, transfer, total_supply, balance_of), storage components (Admin, TotalSupply, Balances), events, and authorization requirements.
Core Implementation
contracts/governance-token/src/lib.rs
Implemented governance token contract with explicit init parameters replacing TokenConfig, added InvalidAmount error variant, enforced admin authorization for privileged operations (init, mint, burn), added non-positive amount validation on transfers and burns, event emission on all state changes, and updated tests to verify authorization and new initialization semantics.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 A token born of careful thought,
Admin guards and checks well-wrought,
Mint and burn with validation's might,
Events glow when transfers ignite!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: implementing a governance token contract for issue #41. It is concise, specific, and clearly communicates the primary objective.
Linked Issues check ✅ Passed The PR implements all required public interface methods (init, mint, burn, transfer, total_supply, balance_of), includes authorization checks, event emissions, error handling with InvalidAmount validation, and provides README documentation with storage and event details. Tests have been extended to cover new initialization style and authorization scenarios.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #41 objectives: governance token contract implementation, required public methods, authorization enforcement, event emissions, error handling, and documentation. No unrelated or out-of-scope modifications are present.
Docstring Coverage ✅ Passed Docstring coverage is 90.91% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@contracts/governance-token/README.md`:
- Around line 25-36: The README storage and events section is out-of-sync: it
references TotalSupply/Balances but the implementation uses Supply and
Balance(Address) and also persists a Config entry, and it lacks required
invariants and integration assumptions; update the README to (1) rename storage
entries to match the contract symbols (Supply, Balance(Address), Config, Admin),
(2) add a clear "Invariants" section (e.g., Supply equals sum of all
Balance(Address) and Admin is immutable or governed as implemented), and (3) add
an "Integration assumptions" section describing expected external behaviors
(e.g., token decimal assumptions, message formats, how init must be called, and
any expected external contracts or permissions) so docs match the contract
implementation and acceptance criteria.

In `@contracts/governance-token/src/lib.rs`:
- Around line 104-121: The transfer function mints tokens on self-transfer
because it writes Balance(from) then Balance(to) even when from == to; fix by
detecting self-transfers in transfer (after from.require_auth()) and
short-circuiting (e.g., return Ok(()) ) or otherwise avoid mutating storage when
from == to; update the logic around GovernanceToken::balance_of, the from/to
balance adjustments, and the env.storage().persistent().set calls for
DataKey::Balance to ensure no double-write occurs for the same Address.
- Around line 145-264: Add tests to assert failure paths and emitted events: add
a test that init called twice on GovernanceTokenClient::init panics (re-init
rejection); add tests that mint/burn/transfer reject invalid amounts (e.g., zero
or negative) by calling GovernanceTokenClient::mint, ::burn, ::transfer with
invalid values and asserting panics; add unauthorized burn and unauthorized
transfer tests mirroring test_unauthorized_mint using client.mock_auths to
simulate a malicious caller invoking "burn" and "transfer" and asserting
Error(Auth, InvalidAction); and enhance existing happy-path tests (test_init,
test_mint, test_burn, test_transfer) to assert expected event payloads emitted
by each call to init, mint, burn, transfer (inspect the contract test events via
the test environment API the project uses) and ensure balances/total_supply
reflect each event's amounts.
- Around line 92-94: In the burn function, protect the total supply update by
replacing the direct subtraction of the local variable supply with a checked
subtraction (use supply.checked_sub(amount)) and handle the None case the same
way as balance underflow is handled (return an error or panic with a clear
message) before calling env.storage().instance().set(&DataKey::Supply, &supply);
ensure you reference DataKey::Supply and preserve the existing
error-handling/log pattern used elsewhere (e.g., transfer) so the supply cannot
become negative.
- Around line 37-43: The init entrypoint currently allows any caller to set the
admin and contract state; fix this by requiring the supplied admin address to
authorize the call before writing state: inside the init function call
admin.require_auth() after verifying the contract is not already initialized
(check DataKey::Admin) and before calling env.storage().instance().set for
DataKey::Admin, DataKey::Config, and DataKey::Supply so only the provided admin
can sign the initialization.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2718bea and ad9f4f3.

📒 Files selected for processing (3)
  • contracts/governance-token/Cargo.toml
  • contracts/governance-token/README.md
  • contracts/governance-token/src/lib.rs

Comment on lines +25 to +36
## Storage

- `Admin`: The address with administrative privileges.
- `TotalSupply`: Current total number of tokens in circulation.
- `Balances`: Mapping of addresses to their respective token balances.

## Events

- `mint`: Emitted when new tokens are minted.
- `burn`: Emitted when tokens are burned.
- `transfer`: Emitted when tokens are transferred.
- `init`: Emitted when the contract is initialized.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

README is missing required invariants/integration assumptions and has storage-name drift.

From Line 25 onward, docs cover methods/storage/events, but acceptance criteria also requires invariants and integration assumptions. Also, docs mention TotalSupply/Balances, while implementation uses Supply/Balance(Address) and persists Config.

📝 Suggested README patch
 ## Storage

 - `Admin`: The address with administrative privileges.
-- `TotalSupply`: Current total number of tokens in circulation.
-- `Balances`: Mapping of addresses to their respective token balances.
+- `Supply`: Current total number of tokens in circulation.
+- `Config`: Token configuration (`name`, `symbol`, `decimals`).
+- `Balance(Address)`: Mapping of addresses to their respective token balances.

 ## Events

 - `mint`: Emitted when new tokens are minted.
 - `burn`: Emitted when tokens are burned.
 - `transfer`: Emitted when tokens are transferred.
 - `init`: Emitted when the contract is initialized.
+
+## Invariants
+
+- Total supply is never negative.
+- Balances are never negative.
+- `total_supply` must remain consistent with mint/burn transitions.
+- Contract initialization is one-time.
+
+## Integration assumptions
+
+- `init` must be called exactly once before privileged operations.
+- `mint`/`burn` require admin authorization.
+- `transfer` requires sender authorization.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
## Storage
- `Admin`: The address with administrative privileges.
- `TotalSupply`: Current total number of tokens in circulation.
- `Balances`: Mapping of addresses to their respective token balances.
## Events
- `mint`: Emitted when new tokens are minted.
- `burn`: Emitted when tokens are burned.
- `transfer`: Emitted when tokens are transferred.
- `init`: Emitted when the contract is initialized.
## Storage
- `Admin`: The address with administrative privileges.
- `Supply`: Current total number of tokens in circulation.
- `Config`: Token configuration (`name`, `symbol`, `decimals`).
- `Balance(Address)`: Mapping of addresses to their respective token balances.
## Events
- `mint`: Emitted when new tokens are minted.
- `burn`: Emitted when tokens are burned.
- `transfer`: Emitted when tokens are transferred.
- `init`: Emitted when the contract is initialized.
## Invariants
- Total supply is never negative.
- Balances are never negative.
- `total_supply` must remain consistent with mint/burn transitions.
- Contract initialization is one-time.
## Integration assumptions
- `init` must be called exactly once before privileged operations.
- `mint`/`burn` require admin authorization.
- `transfer` requires sender authorization.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/governance-token/README.md` around lines 25 - 36, The README
storage and events section is out-of-sync: it references TotalSupply/Balances
but the implementation uses Supply and Balance(Address) and also persists a
Config entry, and it lacks required invariants and integration assumptions;
update the README to (1) rename storage entries to match the contract symbols
(Supply, Balance(Address), Config, Admin), (2) add a clear "Invariants" section
(e.g., Supply equals sum of all Balance(Address) and Admin is immutable or
governed as implemented), and (3) add an "Integration assumptions" section
describing expected external behaviors (e.g., token decimal assumptions, message
formats, how init must be called, and any expected external contracts or
permissions) so docs match the contract implementation and acceptance criteria.

Comment on lines 104 to 121
pub fn transfer(env: Env, from: Address, to: Address, amount: i128) -> Result<(), Error> {
if amount <= 0 {
return Err(Error::InvalidAmount);
}
from.require_auth();

let mut from_balance = self::GovernanceToken::balance_of(env.clone(), from.clone());
if from_balance < amount {
return Err(Error::InsufficientBalance);
}

let mut to_balance = self::GovernanceToken::balance_of(env.clone(), to.clone());

from_balance -= amount;
to_balance = to_balance.checked_add(amount).ok_or(Error::Overflow)?;

env.storage().persistent().set(&DataKey::Balance(from.clone()), &from_balance);
env.storage().persistent().set(&DataKey::Balance(to.clone()), &to_balance);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Self-transfer currently inflates balance when from == to.

At Lines 120-121 the same key is written twice for self-transfer: first with balance - amount, then overwritten with balance + amount, which mints tokens in-place.

🐛 Suggested fix
 pub fn transfer(env: Env, from: Address, to: Address, amount: i128) -> Result<(), Error> {
     if amount <= 0 {
         return Err(Error::InvalidAmount);
     }
     from.require_auth();
+    if from == to {
+        env.events().publish((symbol_short!("transfer"),), (from, to, amount));
+        return Ok(());
+    }

     let mut from_balance = self::GovernanceToken::balance_of(env.clone(), from.clone());
     if from_balance < amount {
         return Err(Error::InsufficientBalance);
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub fn transfer(env: Env, from: Address, to: Address, amount: i128) -> Result<(), Error> {
if amount <= 0 {
return Err(Error::InvalidAmount);
}
from.require_auth();
let mut from_balance = self::GovernanceToken::balance_of(env.clone(), from.clone());
if from_balance < amount {
return Err(Error::InsufficientBalance);
}
let mut to_balance = self::GovernanceToken::balance_of(env.clone(), to.clone());
from_balance -= amount;
to_balance = to_balance.checked_add(amount).ok_or(Error::Overflow)?;
env.storage().persistent().set(&DataKey::Balance(from.clone()), &from_balance);
env.storage().persistent().set(&DataKey::Balance(to.clone()), &to_balance);
pub fn transfer(env: Env, from: Address, to: Address, amount: i128) -> Result<(), Error> {
if amount <= 0 {
return Err(Error::InvalidAmount);
}
from.require_auth();
if from == to {
env.events().publish((symbol_short!("transfer"),), (from, to, amount));
return Ok(());
}
let mut from_balance = self::GovernanceToken::balance_of(env.clone(), from.clone());
if from_balance < amount {
return Err(Error::InsufficientBalance);
}
let mut to_balance = self::GovernanceToken::balance_of(env.clone(), to.clone());
from_balance -= amount;
to_balance = to_balance.checked_add(amount).ok_or(Error::Overflow)?;
env.storage().persistent().set(&DataKey::Balance(from.clone()), &from_balance);
env.storage().persistent().set(&DataKey::Balance(to.clone()), &to_balance);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/governance-token/src/lib.rs` around lines 104 - 121, The transfer
function mints tokens on self-transfer because it writes Balance(from) then
Balance(to) even when from == to; fix by detecting self-transfers in transfer
(after from.require_auth()) and short-circuiting (e.g., return Ok(()) ) or
otherwise avoid mutating storage when from == to; update the logic around
GovernanceToken::balance_of, the from/to balance adjustments, and the
env.storage().persistent().set calls for DataKey::Balance to ensure no
double-write occurs for the same Address.

Comment on lines 145 to 264
#[test]
fn test_init() {
let env = Env::default();
let admin = Address::generate(&env);
let contract_id = env.register(GovernanceToken, ());
let client = GovernanceTokenClient::new(&env, &contract_id);

let config = TokenConfig {
name: Symbol::new(&env, "Governance"),
symbol: Symbol::new(&env, "GOV"),
decimals: 18,
};

client.init(&admin, &config);

assert_eq!(client.total_supply(), 0);
}

#[test]
fn test_mint() {
let env = Env::default();
env.mock_all_auths();
let admin = Address::generate(&env);
let user = Address::generate(&env);
let contract_id = env.register(GovernanceToken, ());
let client = GovernanceTokenClient::new(&env, &contract_id);

let config = TokenConfig {
name: Symbol::new(&env, "Governance"),
symbol: Symbol::new(&env, "GOV"),
decimals: 18,
};
client.init(&admin, &config);

client.mint(&user, &1000);

assert_eq!(client.balance_of(&user), 1000);
assert_eq!(client.total_supply(), 1000);
}

#[test]
fn test_burn() {
let env = Env::default();
env.mock_all_auths();
let admin = Address::generate(&env);
let user = Address::generate(&env);
let contract_id = env.register(GovernanceToken, ());
let client = GovernanceTokenClient::new(&env, &contract_id);

let config = TokenConfig {
name: Symbol::new(&env, "Governance"),
symbol: Symbol::new(&env, "GOV"),
decimals: 18,
};
client.init(&admin, &config);

client.mint(&user, &1000);
client.burn(&user, &400);

assert_eq!(client.balance_of(&user), 600);
assert_eq!(client.total_supply(), 600);
}

#[test]
fn test_transfer() {
let env = Env::default();
env.mock_all_auths();
let admin = Address::generate(&env);
let user1 = Address::generate(&env);
let user2 = Address::generate(&env);
let contract_id = env.register(GovernanceToken, ());
let client = GovernanceTokenClient::new(&env, &contract_id);

let config = TokenConfig {
name: Symbol::new(&env, "Governance"),
symbol: Symbol::new(&env, "GOV"),
decimals: 18,
};
client.init(&admin, &config);

client.mint(&user1, &1000);
client.transfer(&user1, &user2, &300);

assert_eq!(client.balance_of(&user1), 700);
assert_eq!(client.balance_of(&user2), 300);
assert_eq!(client.total_supply(), 1000);
}

#[test]
#[should_panic(expected = "Error(Auth, InvalidAction)")]
fn test_unauthorized_mint() {
let env = Env::default();
let admin = Address::generate(&env);
let user = Address::generate(&env);
let malicious = Address::generate(&env);
let contract_id = env.register(GovernanceToken, ());
let client = GovernanceTokenClient::new(&env, &contract_id);

client.init(&admin, &TokenConfig {
name: Symbol::new(&env, "G"),
symbol: Symbol::new(&env, "G"),
decimals: 0,
});

// Use mock_auths to simulate authorization from malicious address
client.mock_auths(&[
MockAuth {
address: &malicious,
invoke: &MockAuthInvoke {
contract: &contract_id,
fn_name: "mint",
args: (user.clone(), 1000i128).into_val(&env),
sub_invokes: &[],
},
},
]);

client.mint(&user, &1000);
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add failure-path and event assertions to meet the stated acceptance criteria.

Current tests cover happy paths and one unauthorized mint, but still miss key cases: re-init rejection, invalid amount for mint/burn/transfer, unauthorized burn, unauthorized transfer, and emitted event payload assertions for init/mint/burn/transfer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/governance-token/src/lib.rs` around lines 145 - 264, Add tests to
assert failure paths and emitted events: add a test that init called twice on
GovernanceTokenClient::init panics (re-init rejection); add tests that
mint/burn/transfer reject invalid amounts (e.g., zero or negative) by calling
GovernanceTokenClient::mint, ::burn, ::transfer with invalid values and
asserting panics; add unauthorized burn and unauthorized transfer tests
mirroring test_unauthorized_mint using client.mock_auths to simulate a malicious
caller invoking "burn" and "transfer" and asserting Error(Auth, InvalidAction);
and enhance existing happy-path tests (test_init, test_mint, test_burn,
test_transfer) to assert expected event payloads emitted by each call to init,
mint, burn, transfer (inspect the contract test events via the test environment
API the project uses) and ensure balances/total_supply reflect each event's
amounts.

@CMI-James CMI-James merged commit 869183a into TheBlockCade:main Feb 26, 2026
4 of 6 checks passed
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
contracts/governance-token/src/lib.rs (1)

159-223: ⚠️ Potential issue | 🟠 Major

Test coverage still misses required failure paths and event assertions.

The suite still lacks re-init rejection, invalid-amount failures for all mutating entry points, unauthorized burn/transfer, and explicit emitted-event assertions for init/mint/burn/transfer, which are part of the acceptance criteria.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/governance-token/src/lib.rs` around lines 159 - 223, Add tests
covering the missing failure paths and event assertions: create new unit tests
(e.g., test_reinit_rejected, test_invalid_amounts_for_mutators,
test_unauthorized_burn_transfer) and extend test_token_flow to assert emitted
events for init, mint, burn, transfer; exercise GovernanceTokenClient::init,
::mint, ::burn, ::transfer with zero/negative amounts to assert failure, call
init twice to assert re-init is rejected, attempt mint/burn/transfer from
unauthorized addresses to assert Auth/InvalidAction errors, and in each
happy-path test capture and assert the contract events emitted after calling
init/mint/burn/transfer (use the test env or client event inspection helpers
available in the test harness). Ensure you reference the existing functions
GovernanceTokenClient::init, mint, burn, transfer and test names when adding
these assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@contracts/governance-token/src/lib.rs`:
- Around line 201-207: The test test_unauthorized_mint currently calls
client.init which internally calls admin.require_auth() but no auth mocks are
set, causing a panic; before calling client.init (and similarly in the later
block around lines 209-221), mock authentication—e.g., call env.mock_all_auths()
or set up a specific mock for the admin principal—so the init's require_auth()
succeeds and the test can proceed to assert the unauthorized mint behavior;
locate the test_unauthorized_mint function and add the mock before invoking
client.init (mirroring test_token_flow where env.mock_all_auths() is used).

---

Duplicate comments:
In `@contracts/governance-token/src/lib.rs`:
- Around line 159-223: Add tests covering the missing failure paths and event
assertions: create new unit tests (e.g., test_reinit_rejected,
test_invalid_amounts_for_mutators, test_unauthorized_burn_transfer) and extend
test_token_flow to assert emitted events for init, mint, burn, transfer;
exercise GovernanceTokenClient::init, ::mint, ::burn, ::transfer with
zero/negative amounts to assert failure, call init twice to assert re-init is
rejected, attempt mint/burn/transfer from unauthorized addresses to assert
Auth/InvalidAction errors, and in each happy-path test capture and assert the
contract events emitted after calling init/mint/burn/transfer (use the test env
or client event inspection helpers available in the test harness). Ensure you
reference the existing functions GovernanceTokenClient::init, mint, burn,
transfer and test names when adding these assertions.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ad9f4f3 and 22a85c5.

📒 Files selected for processing (1)
  • contracts/governance-token/src/lib.rs

Comment on lines +201 to +207
client.init(
&admin,
&String::from_str(&env, "Test"),
&String::from_str(&env, "T"),
&0
);

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "lib.rs" | grep governance-token

Repository: TheBlockCade/StellarCade

Length of output: 107


🏁 Script executed:

cd contracts/governance-token && wc -l src/lib.rs

Repository: TheBlockCade/StellarCade

Length of output: 82


🏁 Script executed:

head -210 contracts/governance-token/src/lib.rs | tail -50

Repository: TheBlockCade/StellarCade

Length of output: 1694


🏁 Script executed:

# Search for the init function and require_auth calls
rg -A 10 "fn init" contracts/governance-token/src/lib.rs

Repository: TheBlockCade/StellarCade

Length of output: 356


🏁 Script executed:

# Search for the test_unauthorized_mint function
rg -A 25 "fn test_unauthorized_mint" contracts/governance-token/src/lib.rs

Repository: TheBlockCade/StellarCade

Length of output: 988


🏁 Script executed:

# Check for mock_auths usage elsewhere in tests
rg "mock_auths\|mock_all_auths\|set_auths" contracts/governance-token/src/lib.rs

Repository: TheBlockCade/StellarCade

Length of output: 50


🏁 Script executed:

# Look at the full init function to see if it calls require_auth
rg -B 5 -A 15 "pub fn init" contracts/governance-token/src/lib.rs

Repository: TheBlockCade/StellarCade

Length of output: 764


🏁 Script executed:

# Check for require_auth calls on admin in init
rg -B 2 -A 2 "admin.*require_auth\|require_auth.*admin" contracts/governance-token/src/lib.rs

Repository: TheBlockCade/StellarCade

Length of output: 50


🏁 Script executed:

# Get the full test_unauthorized_mint test
rg -A 35 "fn test_unauthorized_mint" contracts/governance-token/src/lib.rs

Repository: TheBlockCade/StellarCade

Length of output: 1044


🌐 Web query:

soroban-sdk 25.0.2 Address::require_auth() behavior mock_auths testutils

💡 Result:

In soroban-sdk v25.0.2, Address::require_auth() is just shorthand for “require auth for the current contract invocation with all the invocation arguments automatically inferred.” If the auth isn’t present/matching, it panics/fails. (Use require_auth_for_args when you want a custom arg list instead.) [1]

How this interacts with testutils mock_auths

  • mock_auths(...) installs only the specific authorizations you describe; any require_auth / require_auth_for_args that doesn’t match one of your mocked entries will fail. [2]
  • A MockAuth must match what require_auth() is checking: contract id + function name + args (encoded as Vals, typically via IntoVal). The SDK’s own example for a function hello(env, from: Address) that calls from.require_auth() mocks args as (&from,).into_val(&env)—because require_auth() infers the invocation args. [2][1]

Minimal pattern (client-side):

client.mock_auths(&[MockAuth {
    address: &addr,
    invoke: &MockAuthInvoke {
        contract: &contract_id,
        fn_name: "hello",
        args: (&addr,).into_val(&env),
        sub_invokes: &[],
    },
}]);
client.hello(&addr);

(From SDK docs/source example.) [2]

If your test is failing unexpectedly

Common causes are:

  • Your MockAuthInvoke.args don’t exactly match the inferred invocation args that require_auth() uses (wrong order / missing params / wrong encoding). [1][2]
  • You have additional require_auth* calls (including in sub-calls) but didn’t model them via sub_invokes / additional mocked entries, so they don’t match and fail. [2]

If you paste the contract function signature + the mock_auths snippet you’re using, I can point out exactly what doesn’t match.

Sources:
[1] Address::require_auth / require_auth_for_args docs (docs.rs) https://docs.rs/soroban-sdk/latest/soroban_sdk/struct.Address.html
[2] Env::mock_auths docs + example (docs.rs source) https://docs.rs/soroban-sdk/latest/src/soroban_sdk/env.rs.html


test_unauthorized_mint will fail at init because admin auth is not mocked.

The init function calls admin.require_auth(), but the test invokes init before setting up any auth mocks. In soroban-sdk 25.0.2, require_auth() fails (panics) if no matching auth is configured. This prevents the test from reaching the unauthorized mint assertion, making it ineffective.

Mocking auth for init is necessary before the call:

Proposed fix
+        env.mock_auths(&[MockAuth {
+            address: &admin,
+            invoke: &MockAuthInvoke {
+                contract: &contract_id,
+                fn_name: "init",
+                args: (
+                    admin.clone(),
+                    String::from_str(&env, "Test"),
+                    String::from_str(&env, "T"),
+                    0u32,
+                )
+                    .into_val(&env),
+                sub_invokes: &[],
+            },
+        }]);
+
         client.init(
             &admin, 
             &String::from_str(&env, "Test"), 
             &String::from_str(&env, "T"), 
             &0
         );

Alternatively, add env.mock_all_auths(); before init (simpler, as used in test_token_flow).

Also applies to: 209-221

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/governance-token/src/lib.rs` around lines 201 - 207, The test
test_unauthorized_mint currently calls client.init which internally calls
admin.require_auth() but no auth mocks are set, causing a panic; before calling
client.init (and similarly in the later block around lines 209-221), mock
authentication—e.g., call env.mock_all_auths() or set up a specific mock for the
admin principal—so the init's require_auth() succeeds and the test can proceed
to assert the unauthorized mint behavior; locate the test_unauthorized_mint
function and add the mock before invoking client.init (mirroring test_token_flow
where env.mock_all_auths() is used).

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.

Implement Governance Token Contract

2 participants