01Economics re-derived on-chain
MathMismatchThe contract recomputes net and margin from the quote. If the agent’s claim differs by even one cent it reverts and shows both numbers.
Proved by testLieAboutNetRevertsWithBothNumbersThe rules the vault enforces on the AI agent, and a way to try to break them.
The owner gave the agent a mandate: spend at most $2,500.00 on one purchase and $5,000.00 of new commitments per UTC day, only accept trades that keep at least 18% margin, and only accept quotes the agent dates as less than 180 seconds old. A smart contract on Robinhood Chain Testnet enforces it, so the agent cannot talk its way past it. The daily cap limits new commitments, not cash-out: commitments do not expire, so ones made on earlier days can be funded later, and across a midnight up to twice the cap can be committed within 24 hours. Cash-out is bounded by the vault’s free balance, and each purchase by the per-purchase cap.
Margin is profit as a share of the sell price. A revert means the contract refuses the transaction and undoes it. Below you can read the live mandate, then play the agent and try to cheat the real contract.
Pick a preset or change the numbers. Each change is sent to the real contract as a read-only test call, made as the agent’s public address. It tells you whether the contract would accept the commit and, if not, which rule stopped it and why. Nothing is broadcast and no money moves.
Sending the call to the deployed contract…
Each rule has an error name (what the contract throws), a plain explanation and the Foundry test that proves it. 6 of the 19 were also triggered in the run on the real chain; the rest are proved by the test suite. Only the product-identity check, whether a listing is really the same item, lives off-chain: the contract sees only a hash of it.
The contract recomputes net and margin from the quote. If the agent’s claim differs by even one cent it reverts and shows both numbers.
Proved by testLieAboutNetRevertsWithBothNumbersSpend is derived as landed cost × units, never supplied by the agent, then compared with the owner’s cap.
Proved by testPerExecutionCapRevertsWithValuesA running total of new commitments per UTC day; the agent cannot split a big purchase into many small ones to dodge it. It limits commitments, not cash-out: opportunities do not expire, so commitments banked on earlier days can be funded later. Across a midnight, up to twice the cap can be committed within 24 hours. What actually leaves the vault is bounded by the vault’s free balance; each lot is at most the per-trade cap.
Proved by testDailyCapRevertsAndResetsNextDay Covered by tests, not triggered in the runThe age check uses the observation time the agent supplies, so it limits how stale the agent’s own claim can be, not the real age of the source data. The snapshot hash binds the data that was shown.
Proved by testStaleRevertsWithAgeAndTtlThe opportunity id is keccak256(productHash, quoteHash, snapshotHash), so the same id cannot be committed twice, and each commit is bounded by the per-trade and daily caps. The agent supplies the snapshot hash, so a new snapshot hash is a new id: the caps, not the id, limit how often the same quote can be committed.
Proved by testReplayRevertsEvenWithSameInputsThe agent commits a hash of the quote it used; submitting a different quote is refused.
Proved by testBadQuoteHashRevertsThe agent cannot claim an observation from the future to defeat the freshness check.
Proved by testFutureObservationReverts Covered by tests, not triggered in the runNet margin below the owner’s floor is refused. Rounding always favours caution.
Proved by testWeakMarginReverts Covered by tests, not triggered in the runA loss-making opportunity is never committed.
Proved by testLossMakingReverts Covered by tests, not triggered in the runEvery quote field and unit count has a hard upper bound, so arithmetic cannot overflow or wrap.
Proved by testQuoteFieldOutOfBoundsReverts Covered by tests, not triggered in the runEscrow can only be released to addresses the owner approved. The agent cannot pay itself.
Proved by testPayeeMustBeAllowlistedPayouts are capped by the lot’s remaining escrow.
Proved by testCannotPayMoreThanEscrow Covered by tests, not triggered in the runSettlement pulls the sale proceeds from the owner-approved payer as real tokens on chain, limited by the payer’s balance and allowance. The agent reports the amount, so the contract guarantees the accounting (proceeds paid in are real and counted), not that the reported sale price is true.
Proved by testSettleRequiresAllowlistedPayerAndRealTransfer Covered by tests, not triggered in the runNo lot, and so no funds, without passing every check above first.
Proved by testMintRequiresCommittedOpportunity Covered by tests, not triggered in the runA committed opportunity can be turned into one lot only.
Proved by testOpportunityMintsOnlyOnce Covered by tests, not triggered in the runThe vault cannot fund a lot beyond its free balance, and the owner cannot withdraw escrowed money.
Proved by testFundRevertsWhenVaultUnderfunded Covered by tests, not triggered in the runA lot moves through a fixed state machine; skipping or reversing a step is refused.
Proved by testIllegalTransitionsRevert Covered by tests, not triggered in the runOnly the agent can act as agent; only the owner can change policy, payees or withdraw. The owner can only become the agent by replacing it with setAgent, which is on chain.
Proved by testOnlyOwnerAdminAndOnlyAgentActions Covered by tests, not triggered in the runThe owner can pause every agent action instantly.
Proved by testPausedBlocksAgent Covered by tests, not triggered in the runThe vault’s Foundry tests are in packages/contracts/test; run forge test in packages/contracts to run them. They include fuzz tests, which try random inputs, and invariant tests that check value is never created or lost: tokens held always cover free funds plus escrow, and free plus escrow plus paid out equals deposits plus proceeds.