Problem
set_admin() in contracts/reputation-contract/src/lib.rs (lines 189–209) contains an "initialization" branch that fires whenever no admin is stored: it writes new_admin with no require_auth() call whatsoever. Anyone can claim admin of the reputation contract before the deployer does. Admin of the reputation contract controls set_updater(), and updaters control increase_score, decrease_score, and set_score — i.e., the entire creditworthiness layer. A hijacked reputation admin can mint maximum scores (set_score(user, MAX_SCORE)) for sybil borrowers, who then pass validate_reputation() in creditline-contract (lib.rs:284–301), unlock the highest credit tier (credit_limit(score) at line 432: 10,000 at score ≥ 90, lowest interest rate), and drain the liquidity pool through loans that were never underwritten.
Note the asymmetry: the admin-exists branch correctly does old_admin.require_auth() + access::require_admin(), while the no-admin branch does nothing. The same flaw class was fixed in the liquidity pool by requiring auth unconditionally.
Ground Rules
- Read context/architecture-context.md in full
- Read context/code-standards.md in full
- Read context/progress-tracker.md before starting
- Read
contracts/reputation-contract/src/lib.rs in full plus src/access.rs and src/storage.rs
- Read
contracts/liquidity-pool-contract/src/lib.rs:28–44 for the canonical guarded-init pattern
What To Build
- Split initialization out of
set_admin() into an explicit initialize(env, admin) that requires admin.require_auth() and rejects re-initialization, matching the other contracts.
- Make
set_admin() panic with NotInitialized (or equivalent error) when no admin exists — it must become impossible to take admin without a signature.
- Keep the existing admin-present behavior identical (
old_admin.require_auth() then access::require_admin()).
- Add tests: (a) unauthenticated first-time
set_admin call fails; (b) initialize works once with auth; (c) second initialize fails; (d) updater flows still function end-to-end.
- Coordinate redeployment: this changes the contract's public interface; update deployed-testnet.json and downstream integrations (creditline stores the reputation address via
set_reputation_contract) accordingly.
Files To Touch
contracts/reputation-contract/src/lib.rs
contracts/reputation-contract/src/tests.rs
context/progress-tracker.md
Acceptance Criteria
Mandatory Checks Before Opening PR
PRs failing any check will be closed without review.
Problem
set_admin()incontracts/reputation-contract/src/lib.rs(lines 189–209) contains an "initialization" branch that fires whenever no admin is stored: it writesnew_adminwith norequire_auth()call whatsoever. Anyone can claim admin of the reputation contract before the deployer does. Admin of the reputation contract controlsset_updater(), and updaters controlincrease_score,decrease_score, andset_score— i.e., the entire creditworthiness layer. A hijacked reputation admin can mint maximum scores (set_score(user, MAX_SCORE)) for sybil borrowers, who then passvalidate_reputation()in creditline-contract (lib.rs:284–301), unlock the highest credit tier (credit_limit(score)at line 432: 10,000 at score ≥ 90, lowest interest rate), and drain the liquidity pool through loans that were never underwritten.Note the asymmetry: the admin-exists branch correctly does
old_admin.require_auth()+access::require_admin(), while the no-admin branch does nothing. The same flaw class was fixed in the liquidity pool by requiring auth unconditionally.Ground Rules
contracts/reputation-contract/src/lib.rsin full plussrc/access.rsandsrc/storage.rscontracts/liquidity-pool-contract/src/lib.rs:28–44for the canonical guarded-init patternWhat To Build
set_admin()into an explicitinitialize(env, admin)that requiresadmin.require_auth()and rejects re-initialization, matching the other contracts.set_admin()panic withNotInitialized(or equivalent error) when no admin exists — it must become impossible to take admin without a signature.old_admin.require_auth()thenaccess::require_admin()).set_admincall fails; (b)initializeworks once with auth; (c) secondinitializefails; (d) updater flows still function end-to-end.set_reputation_contract) accordingly.Files To Touch
contracts/reputation-contract/src/lib.rscontracts/reputation-contract/src/tests.rscontext/progress-tracker.mdAcceptance Criteria
require_auth()from the previous admin or an explicit one-time authorized initializationMandatory Checks Before Opening PR
PRs failing any check will be closed without review.