Summary
In contracts/governance-dao/src/lib.rs, create_proposal() locks config.proposal_threshold tokens from the proposer. finalize() refunds config.proposal_threshold by reading the live config at finalization time — not the amount that was originally deposited.
Code
create_proposal (line 129): deposits current threshold
gov_token.transfer(&proposer, &env.current_contract_address(), &config.proposal_threshold);
finalize (line 251): refunds current threshold
let config: DaoConfig = env.storage().instance().get(&StorageKey::Config).unwrap();
gov_token.transfer(&env.current_contract_address(), &proposal.proposer, &config.proposal_threshold);
// ↑ uses live config.proposal_threshold, not what was deposited
Attack Scenarios
Scenario A — Admin steals proposer tokens:
- Proposer locks 1,000 SHIELD at creation (threshold = 1,000)
- Admin calls
update_config setting threshold = 1 (before voting ends)
finalize refunds only 1 SHIELD — proposer loses 999 SHIELD
Scenario B — Proposer extracts excess tokens:
- Proposer locks 100 SHIELD (threshold = 100)
- Admin raises threshold to 10,000 before finalize
finalize attempts to transfer 10,000 SHIELD — will panic if contract has < 10,000
Fix
Store the deposited amount on the Proposal struct at creation and refund that exact amount:
// In Proposal struct
pub deposit: i128,
// In create_proposal
let deposit = config.proposal_threshold;
gov_token.transfer(&proposer, &env.current_contract_address(), &deposit);
let proposal = Proposal { ..., deposit, ... };
// In finalize
gov_token.transfer(&env.current_contract_address(), &proposal.proposer, &proposal.deposit);
Severity: High
Summary
In
contracts/governance-dao/src/lib.rs,create_proposal()locksconfig.proposal_thresholdtokens from the proposer.finalize()refundsconfig.proposal_thresholdby reading the live config at finalization time — not the amount that was originally deposited.Code
create_proposal(line 129): deposits current thresholdfinalize(line 251): refunds current thresholdAttack Scenarios
Scenario A — Admin steals proposer tokens:
update_configsetting threshold = 1 (before voting ends)finalizerefunds only 1 SHIELD — proposer loses 999 SHIELDScenario B — Proposer extracts excess tokens:
finalizeattempts to transfer 10,000 SHIELD — will panic if contract has < 10,000Fix
Store the deposited amount on the
Proposalstruct at creation and refund that exact amount:Severity: High