feat: implement governance token contract for issue #41#166
feat: implement governance token contract for issue #41#166CMI-James merged 2 commits intoTheBlockCade:mainfrom
Conversation
📝 WalkthroughWalkthroughImplements 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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (3)
contracts/governance-token/Cargo.tomlcontracts/governance-token/README.mdcontracts/governance-token/src/lib.rs
| ## 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. |
There was a problem hiding this comment.
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.
| ## 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.
| 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); |
There was a problem hiding this comment.
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.
| 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.
| #[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); | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
contracts/governance-token/src/lib.rs (1)
159-223:⚠️ Potential issue | 🟠 MajorTest 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.
| client.init( | ||
| &admin, | ||
| &String::from_str(&env, "Test"), | ||
| &String::from_str(&env, "T"), | ||
| &0 | ||
| ); | ||
|
|
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -type f -name "lib.rs" | grep governance-tokenRepository: TheBlockCade/StellarCade
Length of output: 107
🏁 Script executed:
cd contracts/governance-token && wc -l src/lib.rsRepository: TheBlockCade/StellarCade
Length of output: 82
🏁 Script executed:
head -210 contracts/governance-token/src/lib.rs | tail -50Repository: 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.rsRepository: 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.rsRepository: 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.rsRepository: 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.rsRepository: 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.rsRepository: 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.rsRepository: 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; anyrequire_auth/require_auth_for_argsthat doesn’t match one of your mocked entries will fail. [2]- A
MockAuthmust match whatrequire_auth()is checking: contract id + function name + args (encoded asVals, typically viaIntoVal). The SDK’s own example for a functionhello(env, from: Address)that callsfrom.require_auth()mocksargsas(&from,).into_val(&env)—becauserequire_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.argsdon’t exactly match the inferred invocation args thatrequire_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 viasub_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).
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
New Features