-
-
Notifications
You must be signed in to change notification settings - Fork 34
feat: implement governance token contract for issue #41 #166
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| # Governance Token Contract | ||
|
|
||
| This contract implements the governance token for the StellarCade platform. It provides standard token functionalities such as minting, burning, and transferring, with administrative controls for governance purposes. | ||
|
|
||
| ## Methods | ||
|
|
||
| ### `init(admin: Address, token_config: TokenConfig)` | ||
| Initializes the contract with an admin address and token configuration. | ||
|
|
||
| ### `mint(to: Address, amount: i128)` | ||
| Mints new tokens to the specified address. Requires admin authorization. | ||
|
|
||
| ### `burn(from: Address, amount: i128)` | ||
| Burns tokens from the specified address. Requires admin authorization. | ||
|
|
||
| ### `transfer(from: Address, to: Address, amount: i128)` | ||
| Transfers tokens from one address to another. Requires authorization from the sender. | ||
|
|
||
| ### `total_supply() -> i128` | ||
| Returns the current total supply of tokens. | ||
|
|
||
| ### `balance_of(owner: Address) -> i128` | ||
| Returns the token balance of the specified owner. | ||
|
|
||
| ## 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. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,7 +11,8 @@ pub enum Error { | |
| NotAuthorized = 1, | ||
| AlreadyInitialized = 2, | ||
| InsufficientBalance = 3, | ||
| Overflow = 4, | ||
| InvalidAmount = 4, | ||
| Overflow = 5, | ||
| } | ||
|
|
||
| #[contracttype] | ||
|
|
@@ -30,6 +31,8 @@ pub struct GovernanceToken; | |
|
|
||
| #[contractimpl] | ||
| impl GovernanceToken { | ||
| /// Initializes the contract with the admin address and token setup. | ||
| /// Requires admin authorization to prevent arbitrary initialization. | ||
| pub fn init( | ||
| env: Env, | ||
| admin: Address, | ||
|
|
@@ -40,22 +43,32 @@ impl GovernanceToken { | |
| if env.storage().instance().has(&DataKey::Admin) { | ||
| return Err(Error::AlreadyInitialized); | ||
| } | ||
|
|
||
| // Security Fix: Require admin auth during initialization | ||
| admin.require_auth(); | ||
|
|
||
| env.storage().instance().set(&DataKey::Admin, &admin); | ||
| env.storage().instance().set(&DataKey::Name, &name); | ||
| env.storage().instance().set(&DataKey::Symbol, &symbol); | ||
| env.storage().instance().set(&DataKey::Decimals, &decimals); | ||
| env.storage().instance().set(&DataKey::TotalSupply, &0i128); | ||
|
|
||
| env.events().publish( | ||
| (symbol_short!("init"), admin), | ||
| (name, symbol, decimals) | ||
| ); | ||
| Ok(()) | ||
| } | ||
|
|
||
| /// Mints new tokens to a recipient. Only admin can call. | ||
| pub fn mint(env: Env, to: Address, amount: i128) -> Result<(), Error> { | ||
| let admin: Address = env.storage().instance().get(&DataKey::Admin).ok_or(Error::NotAuthorized)?; | ||
| admin.require_auth(); | ||
|
|
||
| if amount <= 0 { | ||
| return Err(Error::Overflow); | ||
| return Err(Error::InvalidAmount); | ||
| } | ||
|
|
||
| let admin: Address = env.storage().instance().get(&DataKey::Admin).ok_or(Error::NotAuthorized)?; | ||
| admin.require_auth(); | ||
|
|
||
| let balance = Self::balance(env.clone(), to.clone()); | ||
| let new_balance = balance.checked_add(amount).ok_or(Error::Overflow)?; | ||
| env.storage().persistent().set(&DataKey::Balance(to.clone()), &new_balance); | ||
|
|
@@ -68,7 +81,12 @@ impl GovernanceToken { | |
| Ok(()) | ||
| } | ||
|
|
||
| /// Burns tokens from an account. Only admin can call. | ||
| pub fn burn(env: Env, from: Address, amount: i128) -> Result<(), Error> { | ||
| if amount <= 0 { | ||
| return Err(Error::InvalidAmount); | ||
| } | ||
|
|
||
| let admin: Address = env.storage().instance().get(&DataKey::Admin).ok_or(Error::NotAuthorized)?; | ||
| admin.require_auth(); | ||
|
|
||
|
|
@@ -88,7 +106,11 @@ impl GovernanceToken { | |
| Ok(()) | ||
| } | ||
|
|
||
| /// Transfers tokens between accounts. Requires sender authorization. | ||
| pub fn transfer(env: Env, from: Address, to: Address, amount: i128) -> Result<(), Error> { | ||
| if amount <= 0 { | ||
| return Err(Error::InvalidAmount); | ||
| } | ||
| from.require_auth(); | ||
|
|
||
| let balance_from = Self::balance(env.clone(), from.clone()); | ||
|
|
@@ -131,7 +153,8 @@ impl GovernanceToken { | |
| #[cfg(test)] | ||
| mod test { | ||
| use super::*; | ||
| use soroban_sdk::{testutils::Address as _, Env}; | ||
| use soroban_sdk::testutils::{Address as _, Events, MockAuth, MockAuthInvoke}; | ||
| use soroban_sdk::{IntoVal}; | ||
|
|
||
| #[test] | ||
| fn test_token_flow() { | ||
|
|
@@ -145,7 +168,12 @@ mod test { | |
| let contract_id = env.register(GovernanceToken, ()); | ||
| let client = GovernanceTokenClient::new(&env, &contract_id); | ||
|
|
||
| client.init(&admin, &String::from_str(&env, "StellarCade Governance"), &String::from_str(&env, "SCG"), &18); | ||
| client.init( | ||
| &admin, | ||
| &String::from_str(&env, "StellarCade Governance"), | ||
| &String::from_str(&env, "SCG"), | ||
| &18 | ||
| ); | ||
|
|
||
| client.mint(&user1, &1000); | ||
| assert_eq!(client.balance(&user1), 1000); | ||
|
|
@@ -159,4 +187,37 @@ mod test { | |
| assert_eq!(client.balance(&user2), 300); | ||
| assert_eq!(client.total_supply(), 900); | ||
| } | ||
|
|
||
| #[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, | ||
| &String::from_str(&env, "Test"), | ||
| &String::from_str(&env, "T"), | ||
| &0 | ||
| ); | ||
|
|
||
|
Comment on lines
+201
to
+207
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 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:
💡 Result: In How this interacts with
|
||
| // 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 usesSupply/Balance(Address)and persistsConfig.📝 Suggested README patch
📝 Committable suggestion
🤖 Prompt for AI Agents