testnet hardening: Ownable AgentEscrow + Foundry deploys + pip install#23
Open
testnet hardening: Ownable AgentEscrow + Foundry deploys + pip install#23
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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