testnet hardening: Ownable AgentEscrow + Foundry deploys + pip install#23
testnet hardening: Ownable AgentEscrow + Foundry deploys + pip install#23abhicris wants to merge 4 commits into
Conversation
…paths Closes the permissionless-registry bug in AgentEscrow.sol — anyone could register any address. registerAgent / deregisterAgent are now onlyOwner (OpenZeppelin Ownable). confirm/refund/cancel are nonReentrant and follow checks-effects-interactions; the storage update now zeroes p.amount before the external call as defense in depth. Adds an "agent cannot be zero address" guard and a PaymentCancelled event that was previously missing.
The syntax error in tests/test_payment_protocol.py (fixed in the previous commit)
had been hiding three bugs that the test suite then surfaced:
1. nonce_manager.confirm_nonce ran a while-loop that auto-confirmed every
contiguous pending nonce — confirming nonce N effectively confirmed N+1,
N+2, ... up to the next gap. Replace with explicit out-of-order tracking:
out-of-order confirmations are stashed in WalletState.out_of_order_confirmations
and rolled into confirmed_nonce only when the gap fills. on_reorg now also
clears stashed entries the reorg invalidates, and _sync_with_onchain_nonce
uses <= so a pending nonce equal to the new on-chain nonce is treated as
stale and re-issued on the next acquire.
2. parse_wei("X wei") returned X * 10**18. Dict key "wei" was lowercase,
lookup used currency.upper(), so "WEI" missed and fell to the ETH default.
Uppercase the key.
3. PaymentRequest.content_hash() included created_at (set via
field(default_factory=time.time)), so two requests with identical content
produced different hashes. Exclude created_at and status from the hashed
payload — they're runtime/volatile, not part of the request's identity.
Drops the `|| true` on the pytest CI step now that all 59 tests pass.
31371a9 to
c9a37e1
Compare
|
Here's a GitHub issue comment connecting kcolbchain/switchboard with AURA Open Protocol: "Hi @kcolbchain, I noticed that the recent contract hardening in |
forge install rejects --no-commit since the recent foundry stable release; the flag was deprecated and now exits 2 in pr-23's forge test job. forge install no longer creates commits by default, so removing the flag is a no-op behaviorally — just unblocks CI. Keep --shallow for faster CI clones.
|
CI fix pushed (91ce348 + 7831d32) — However, GitHub will not auto-trigger workflows for this PR because To unblock CI, either:
Once CI runs, expect green: the pytest job has always passed; only forge test was broken by the foundry CLI change. The fix is one diff: |
Summary
Makes switchboard deployable on testnet end-to-end:
Contract hardening (
contracts/AgentEscrow.sol)registerAgentis nowonlyOwner(was permissionless — anyone could register any address as a "trusted agent").deregisterAgentadded, alsoonlyOwner.confirmPayment/requestRefund/cancelPaymentarenonReentrant(OpenZeppelinReentrancyGuard) and now follow checks-effects-interactions: state is updated andp.amountis zeroed BEFORE the externalcall. Defense in depth.agent != address(0)guard onregisterAgent, plus a missingPaymentCancelledevent.Ownable(msg.sender)in the constructor.Foundry scaffold
foundry.toml,script/Deploy.s.sol,test/AgentEscrow.t.sol(10 tests, all pass).forge install(CI installs at job time;lib/is gitignored).Makefiletargets:deploy-base-sepolia(84532),deploy-op-sepolia(11155420),deploy-lux-testnet(96368),verify-base-sepolia. All callforge script ... --broadcast --verify..env.examplewithRPC_BASE_SEPOLIA,RPC_OP_SEPOLIA,RPC_LUX_TESTNET,DEPLOYER_PRIVATE_KEY(placeholder),ETHERSCAN_API_KEY.Python distribution
pyproject.toml(hatchling). PyPI name =switchboard-agent(the bare nameswitchboardis taken on PyPI by an unrelated project). Import name staysswitchboard.0.1.0. Python>=3.11. Deps:pydantic>=2,web3>=6,httpx,eth-account,sortedcontainers. Extras:[fastapi],[flask],[zap],[dev],[all].switchboard/__init__.pyexposes__version__andload_registry().switchboard/registry.jsonstub (escrow addresses null until deploy).CI / publishing
.github/workflows/publish.yml— PyPI onv*tag via OIDC trusted publishing (noPYPI_TOKENneeded once the project is configured on PyPI). Not invoked here..github/workflows/ci.yml—forge test+pyteston PR. Both run strict (no|| truemasking).Latent bugs fixed (commit 31371a9)
The
def/eventssyntax error intests/test_payment_protocol.pywas masking three real bugs the test suite then surfaced. All fixed in this PR:nonce_manager.confirm_nonceran a while-loop that auto-confirmed every contiguous pending nonce — confirming nonce N effectively confirmed N+1, N+2, ... up to the next gap. Replaced with explicit out-of-order tracking: out-of-order confirmations stash inWalletState.out_of_order_confirmationsand roll intoconfirmed_nonceonly when the gap fills.on_reorgnow also clears stashed entries the reorg invalidates, and_sync_with_onchain_nonceuses<=so a pending nonce equal to the new on-chain nonce is treated as stale.parse_wei("X wei")returnedX * 10**18. Dict key"wei"was lowercase; lookup usedcurrency.upper(), so"WEI"missed and fell to the ETH default. Uppercase the key.PaymentRequest.content_hash()includedcreated_at(set viafield(default_factory=time.time)), so two requests with identical content produced different hashes. Excludescreated_atandstatusfrom the hashed payload — they're runtime/volatile, not part of the request's identity.Regression tests
Foundry:
forge test: 10 passed, 0 failed.pytest: 59 passed, 0 failed.
Deploy plan
After each deploy: copy the address into
switchboard/registry.jsonunder the right chainId (currentlynull). That registry ships in the wheel.TODO (registry.json)
Test plan
forge build— Compiler run successfulforge test -vv— 10 passedpytest --collect-only— 59 tests collected (was 1 collection error on master)pytest— 59 passed, 0 failedswitchboard-agentand tagsv0.1.0🤖 Generated with Claude Code