Audits
Sitowise has not been audited. No third-party firm has reviewed the contract that holds node balances, there is no report to link, and no audit is currently scheduled.
There is no audit. If you are deciding whether to spend money here, treat the contracts as unreviewed code written by one team and verify them yourself, or do not use them.
Nobody involved will claim otherwise, and if you see a page, a post or a badge claiming Sitowise is audited, it is not from us and it is not true.
What exists instead
Not an audit, and not a substitute for one. It is what can honestly be said about how the code was written and checked.
| What | Detail |
|---|---|
| Test suite | 112 tests across 4 Foundry suites, all passing, against the one contract this repository contains. |
| Coverage of the money paths | Minting and the payment-reference guard; the wallet cap; pausing; credit batches, including value mismatches, oversized amounts, unknown ids and length mismatches; single and sweeping withdrawals, repeated and redirected, and to recipients that reject ETH; rescue against the free-balance bound; reentrancy; role access control; an empty contract. |
| Invariants | Six invariant properties run under fuzzing, including that the contract balance always covers outstanding, that outstanding equals the sum of every node balance, and that freeBalance() is exactly the rescuable remainder. Three further tests check the fuzzing handler is not passing vacuously. |
| Dependencies | None in the deployed code. SitowiseFactory inherits nothing, imports nothing, and contains no cryptography: access control is three address comparisons and withdrawal authorisation is msg.sender. Foundry and forge-std are test-only. |
| Upgradeability | None. There is no proxy, so the code you review is the code that stays deployed. |
| Verification | Sources are verified on the explorer, so the deployed bytecode can be matched against the repository. |
Tests demonstrate that the paths the author thought of behave as intended. An audit is someone whose job is to think of the other paths. Do not confuse the two.
Reviewing it yourself
The contract is small on purpose. SitowiseFactory is a single file of a few hundred lines with no inheritance and no imports, and there is nothing else deployed. A competent Solidity reader can go through the whole thing in an afternoon.
cd contracts
forge install foundry-rs/forge-std --no-git
forge build
forge test -vv
# the properties, under fuzzing
forge test --match-path 'test/*invariant*'Read these six things first, in this order:
mintFor. Confirm it is relayer-only, that it rejects a zeropaymentRef, and that it setspaymentRefUsed[paymentRef]before creating the node, so one payment can never back two nodes. Without that guard thepaymentRefinNodeMintedwould prove nothing.creditBatch. Confirm it is payable and reverts withValueMismatchunlessmsg.valueequals the sum ofamounts, so a balance can never exist without the ETH behind it. Confirm the sum is validated before any storage is written, and that the loop rejects an unknown node id.- The
AmountTooLargeguard inside that loop. Balances areuint128, and an explicit narrowing cast in Solidity truncates silently rather than reverting, which would credit a node less than the ETH backing it and breakoutstandingpermanently. Confirm the check is on the rawuint256, before the cast. withdrawandwithdrawAll. Confirm the owner check is againstmsg.senderand nothing else, and that the node balance is zeroed andoutstandingreduced before the external call. Confirm neither function readspaused.rescuetogether withfreeBalance(). Confirm the bound is the contract balance minusoutstanding, so the owner cannot reach a node balance under any sequence of calls. This is the holders’ guarantee, and it is the one line worth checking twice.transferOwnershipandacceptOwnership. Confirm ownership only moves when the named address accepts, so a mistyped address cannot brick the admin surface.
Then read the invariant suite, and satisfy yourself that it is not passing vacuously. invariant_FreeBalanceIsTheRescuableRemainder is the mutation canary: change rescue’s bound from freeBalance() to the full contract balance and the suite must fail. If it still passes, the invariant is not testing what it claims to. The contracts README describes that check.
Checking the deployed code
Reading the repository tells you what was written. Reading the chain tells you what is deployed. Do both.
# compare deployed bytecode against a local build
cast code $FACTORY --rpc-url $RPC_URL | shasum
# who holds which power
cast call $FACTORY "owner()(address)"
cast call $FACTORY "relayer()(address)"
cast call $FACTORY "distributor()(address)"
# the settings those roles operate under
cast call $FACTORY "maxPerWallet()(uint256)"
cast call $FACTORY "MAX_PER_WALLET_CEILING()(uint256)"
cast call $FACTORY "paused()(bool)"
# the guarantee, checked against the contract's own balance
cast call $FACTORY "outstanding()(uint256)"
cast call $FACTORY "freeBalance()(uint256)"
cast call $FACTORY "isSolvent()(bool)"
cast call $FACTORY "totalNodes()(uint256)"freeBalance() is the ceiling on rescue, and isSolvent() is the one-word version of the same question. Neither needs an account, a session or this website.
The verified source is browsable at https://robinhoodchain.blockscout.com. Addresses are on Addresses.
Reporting something
If you find a bug, report it before publishing it, through the account linked in the site footer. There is no bug bounty programme, and pretending one exists would be another thing to be honest about. A report will be read, acted on, and credited if you want credit.
What this means for you
Unaudited code can contain a fault that nobody involved has noticed, and a fault in a contract holding ETH can mean the loss of that ETH. That is a real possibility, not a formality, and it sits alongside the other risks on Risks. Size your exposure accordingly.