Run: slither . --solc solc-0.8.28 --exclude-dependencies.
Baseline: 25 INFO findings, 0 High/Medium. Triage:
divide-before-multiply(_updateDebts): intentional — dust to payer, conservation invariant holds (tested).reentrancy-no-eth/benign(settleDebt): mitigated —nonReentrant+ immutable OZ ERC20 without hooks.calls-loop: all calls to trusted sibling contracts, bounded ≤ 50 members.reentrancy-events: events after calls to trusted contracts only.assembly(array resize),low-level-calls(checked ETHcall),pragma(OZ^0.8.20compiles under one solc),naming-convention(UPPER immutables): accepted style.missing-inheritance(ExpenseManagervsIExpenseManager): deferred, rename-only value.- Not in CI (needs Python+solc runners) — run locally before mainnet.
- Contracts are custodial for ETH (TrustToken holds mint payments) and for
accounting (debts/balances). No oracles, no external calls except ETH
transfers (
redeem,withdraw) andTRUST.transferFrom(settleDebt). - Owner powers:
TrustToken.withdraw(all ETH), set-once wiring (setExpenseManager,setDebtSimplifierContract). No upgrade proxy — owner cannot change accounting logic, only drain ETH and set wiring once. - Frontend holds no secrets:
frontend/.envcontains public deployment addresses only; private keys never touch the app (MetaMask signs locally).
mint/redeem/withdraw(TrustToken) andsettleDebt(ExpenseManager) carry OZnonReentrant(storage-based guard, no EIP-1153 dependency).redeemadditionally burns before the ETHcall(checks-effects-interactions).- Ownership is two-step (
Ownable2Step):transferOwnership+acceptOwnership. Tested intest/TrustToken.test.ts("Ownership Transfer"). settleDebtupdates state aftertransferFrombut under the guard; with the trusted immutableTRUST_TOKEN(plain OZ ERC20, no receiver hooks) there is no callback vector — guard is defense in depth.
- EQUAL/PERCENTAGE truncate; remainder is absorbed by the payer (credited only
the distributed sum). Invariant sum == 0 holds; payer silently overpays dust
(< n wei per expense). Documented, tested in
test/DustConservation.test.ts. redeemreverts ontokenAmount % MINT_RATE != 0(Amount not multiple) instead of silently burning dust value.ethAmountFull == 0also reverts (Token amount too small).
- 2% of the ETH value stays in the contract (
REDEMPTION_FEE_PERCENT = 2); only owner can withdraw it. The token side never truncates (non-multiples revert); only(ethAmountBeforeFee * 2) / 100can truncate sub-wei dust.
initializeGroupBalancescallable only by GroupManager; groups created before wiring keep implicit zero balances (mapping default) — safe.joinGrouprelies on implicit zero balance for newcomers — safe but implicit.leaveGroup/deleteGroupdebt scans are O(n²) externalgetDebtcalls; DoS-safe at 50 members, revisit if the cap rises.