Skip to content

Feat/token minter reward guards - #20

Merged
cybermax4200 merged 13 commits into
mainfrom
feat/token-minter-reward-guards
Aug 4, 2026
Merged

Feat/token minter reward guards#20
cybermax4200 merged 13 commits into
mainfrom
feat/token-minter-reward-guards

Conversation

@cybermax4200

Copy link
Copy Markdown
Contributor

Summary

This PR hardens the EcoTask Soroban smart contracts with token access control, cross-contract reward validation, admin governance tools, integration testing, and production safety mechanisms.

What Changed

Token Security (eco-token)

  • Minter role — only the designated minter (reward engine) can mint new tokens. Admin assigns minter via set_minter(), which can be transferred at any time.
  • Approve input validation — rejects negative amounts and past expirations to prevent invalid allowance states.

Reward Engine Correctness (reward-engine)

  • Cross-contract task validation — approve_proof and resolve_dispute now verify the task is Active and not expired before minting, preventing rewards for stale or cancelled tasks.
  • Reward cap enforcement — payout cannot exceed the task's declared reward amount; any over-budget attempt panics.
  • Dispute event fix — DisputeResolvedEvent now correctly emits reward_amount: 0 when a dispute is rejected.
  • Total paid tracking — cumulative total_paid view for on-chain transparency and auditing.
  • Post-deployment reconfiguration — set_token() and set_registry() let the admin swap dependencies without redeploying.
  • Emergency pause — pause() / unpause() to instantly halt all proof operations during an exploit or incident. Only admin can toggle.

Task Registry Governance (task-registry)

  • admin_cancel_task() — admin can cancel any active task for governance reasons, rejecting completions and payouts on that task.
  • Empty task_type rejection — create_task now panics if task_type is empty, enforcing data quality.

Testing

  • Root package with real cross-contract integration tests (7 tests covering full lifecycle, disputes, multi-user, minter delegation, reward caps, admin cancel, and emergency pause).
  • Unit tests across all three contracts (108 total, all passing).

CI & Scripts

  • Cargo caching, --locked builds, aligned clippy flags (--all-targets --all-features -- -D warnings).
  • Scripts parameterized with NETWORK env var with validation; verification script checks all required variables before proceeding.

Separate minting authority from the admin role. The admin can delegate
minting to a designated minter address (the reward engine) via
set_minter(). The mint() function now requires minter authorization
instead of admin authorization. This aligns with the documented
architecture where only the reward engine should mint new tokens.

Changes:
- storage: add write_minter/read_minter for the Minter data key
- initialize: sets minter = admin by default
- mint: gated by minter.require_auth() instead of admin
- minter(): view function to query current minter
- set_minter(): admin-gated function to delegate minting rights
- tests: minter authorization, initial minter is admin, set_minter,
  set_minter_unauthorized
Add input validation to approve() to prevent setting nonsensical
allowances:
- Reject negative amounts (zero is valid for revoking an allowance)
- Reject expirations that are not strictly in the future

These guards prevent accidental misuse of the allowance system and
complement the existing input validation on mint, transfer, and burn.
Before approving a proof or resolving a dispute in the reward engine,
cross-contract query the task-registry to validate:
- The task exists and is in Active status (not completed, expired, or cancelled)
- The task has not passed its expiration timestamp
- The payout reward_amount does not exceed the task's declared reward_amount

This prevents the engine from paying out on expired, cancelled, or
already-completed tasks, and prevents overpaying beyond the task's
budget. A new internal helper require_active_task() encapsulates the
registry query and validation logic.
When resolve_dispute is called with approve=false, the event now
correctly emits reward_amount=0 instead of the caller-supplied value.
This prevents misleading on-chain events suggesting a payout occurred
when the dispute was rejected.

Also add guard tests for the new task-status and budget checks:
- Approve over task budget panics with 'reward exceeds task budget'
- Approve on expired task panics with 'task has expired'
- Approve on cancelled task panics with 'task is not active'
- Dispute resolve approve over budget panics with 'reward exceeds task budget'
Add an on-chain counter that accumulates every reward payout made by
the engine (both via approve_proof and dispute resolution). This
enables transparent reporting and auditing without off-chain indexers:
- TotalPaid storage key with overflow-checked accumulation
- add_total_paid() increments after each successful mint
- total_paid() public view function returns cumulative payout amount
- Rejected proofs and rejected disputes do not increment the counter
Add set_token() and set_registry() functions to allow the admin to
update the token and registry contract addresses after deployment.
This mirrors the existing set_oracle() pattern and provides operational
flexibility for contract upgrades or migration scenarios.

Both functions require admin authorization and follow the established
access control pattern used throughout the reward engine.
Add admin_cancel_task() which allows the admin to cancel any active
task regardless of creator. This provides governance-level control for
removing tasks that violate platform policies, are reported as
fraudulent, or need to be taken down for other reasons.

The existing cancel_task() remains creator-only. The new function:
- Requires admin authorization
- Only operates on Active tasks
- Emits the same TaskCancelledEvent for audit trail
- Tests cover admin success, non-admin rejection, and completed task rejection
Add input validation to create_task() that panics if the task_type
string is empty. This prevents creating tasks without a meaningful
type classification, which would break downstream filtering, event
analysis, and off-chain indexing.
Convert the workspace root from a virtual workspace to a root package
with dev-dependencies on all three contracts, enabling the previously
placeholder tests/reward_integration_test.rs to run as a real
integration test suite.

New cross-contract integration tests cover:
- Full payout lifecycle: multi-task, multi-user, balance verification
- Dispute flow: reject, dispute, resolve, payout verification
- Multi-user max completions with automatic task completion
- Reward cap enforcement across contracts (engine rejects over-budget)
- Minter role delegation from admin to reward engine
- Admin cancel prevents subsequent payout approval

Also creates src/lib.rs with #![no_std] for the root package.
- Add Swatinem/rust-cache@v2 for faster CI runs
- Use --locked on cargo build for reproducible builds
- Run cargo test --workspace explicitly (avoids virtual workspace ambiguity)
- Align clippy flags with Makefile: --all-targets --all-features -- -D warnings
- verify-deploy.sh: use NETWORK env with testnet default, require all
  contract ID env vars with clear error messages
- integration-test.sh: use NETWORK env, require env vars, fix
  location_hash to use sha256sum instead of broken xxd invocation
- fund-accounts.sh: require address argument explicitly
Update README to reflect all contract improvements:
- eco-token: minter role for minting authorization, approve validation
- task-registry: admin_cancel_task for governance, empty type rejection
- reward-engine: task status/cap enforcement, total_paid tracking,
  admin setters for token/registry, post-deployment reconfiguration
- Security section updated to reflect minter separation and payout guards
Admin can pause all proof submission, approval, dispute, and resolution
operations instantly to respond to exploits or incidents. Paused state
is checked at entry points via require_not_paused guard. Only admin can
pause/unpause. Added 6 unit tests and 1 integration test verifying the
full pause/unpause lifecycle.
@cybermax4200
cybermax4200 merged commit db590b6 into main Aug 4, 2026
0 of 2 checks passed
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.

1 participant