Background
The gateway has 236 unit/integration tests, but they all mock contract interactions. This means any mismatch between what the gateway expects and what the contracts actually do goes undetected until a real deployment.
To close this gap, I'm building an E2E test suite that deploys the full ChaosChain contract stack on an Anvil local testnet and runs gateway workflows against it. The approach is to dockerize both the gateway and the blockchain (Anvil), so tests exercise the real contract calls end-to-end without mocks. Running against real contracts exposed one design question and two gateway-side bugs.
Related: #25 (ABI alignment fix — first finding from this effort)
Before proposing fixes, I need to clarify the gateway's intended role.
Core question: Who runs the gateway?
The RegisterWorkStep and RegisterValidatorStep in the gateway call registerWork() and registerValidator() on RewardsDistributor. These functions are onlyOwner by design.
This means the gateway's signer must be the RewardsDistributor owner for these steps to succeed. Today the workflow uses input.signer_address (the worker/validator) for these calls, which will always revert unless that address happens to be the owner.
Two possible interpretations:
- The gateway is always operated by ChaosChain (the owner) — the workflow should use the gateway's own signer (which is the owner) for
registerWork/registerValidator calls, not the worker/validator signer.
- The gateway is meant to be run by anyone — the contract access control would need to change to allow this, but that's a contract design decision, not a gateway bug.
Which is the intended model? The answer determines how to fix the workflow.
Additional finding (gateway-side bug)
This is an issue in the gateway's own logic, independent of the question above.
1. RegisterValidatorStep precondition blocks direct mode
Where: src/workflows/score-submission.ts#L955
The step checks if (!progress.reveal_confirmed) before proceeding. In commit-reveal mode this works — AwaitRevealConfirmStep sets reveal_confirmed = true. But in direct mode, AwaitScoreConfirmStep sets score_confirmed = true instead. Since reveal_confirmed is never set, the precondition always fails and the workflow ends in FAILED.
Suggested fix: if (!progress.reveal_confirmed && !progress.score_confirmed)
2. Reconciliation ADVANCE_TO_STEP skips progress state
Where: src/workflows/reconciliation.ts#L227 and #L653
When reconciliation detects work already exists on-chain and advances from SUBMIT_WORK_ONCHAIN to REGISTER_WORK, it updates the step but doesn't set progress.onchain_confirmed = true. Any downstream logic that checks onchain_confirmed won't see it, even though the work is confirmed on-chain.
How this was found
Local E2E testing with:
- Anvil local testnet (port 8546,
--block-time 1)
- Full contract deployment via Foundry script: MockIdentityRegistry → ChaosChainRegistry → RewardsDistributor → StudioProxyFactory → ChaosCore → PredictionMarketLogic
- Gateway with
EthersChainAdapter, InMemoryWorkflowPersistence, MockArweaveAdapter
Result
| Test suite |
Result |
| Unit/integration (236 tests) |
All pass |
| E2E (4 tests) |
1 pass, 3 STALLED |
The E2E infrastructure (Docker Compose with Anvil + gateway + PostgreSQL) will be added in a follow-up PR.
Background
The gateway has 236 unit/integration tests, but they all mock contract interactions. This means any mismatch between what the gateway expects and what the contracts actually do goes undetected until a real deployment.
To close this gap, I'm building an E2E test suite that deploys the full ChaosChain contract stack on an Anvil local testnet and runs gateway workflows against it. The approach is to dockerize both the gateway and the blockchain (Anvil), so tests exercise the real contract calls end-to-end without mocks. Running against real contracts exposed one design question and two gateway-side bugs.
Related: #25 (ABI alignment fix — first finding from this effort)
Before proposing fixes, I need to clarify the gateway's intended role.
Core question: Who runs the gateway?
The
RegisterWorkStepandRegisterValidatorStepin the gateway callregisterWork()andregisterValidator()on RewardsDistributor. These functions areonlyOwnerby design.This means the gateway's signer must be the RewardsDistributor owner for these steps to succeed. Today the workflow uses
input.signer_address(the worker/validator) for these calls, which will always revert unless that address happens to be the owner.Two possible interpretations:
registerWork/registerValidatorcalls, not the worker/validator signer.Which is the intended model? The answer determines how to fix the workflow.
Additional finding (gateway-side bug)
This is an issue in the gateway's own logic, independent of the question above.
1.
RegisterValidatorStepprecondition blocks direct modeWhere:
src/workflows/score-submission.ts#L955The step checks
if (!progress.reveal_confirmed)before proceeding. In commit-reveal mode this works —AwaitRevealConfirmStepsetsreveal_confirmed = true. But in direct mode,AwaitScoreConfirmStepsetsscore_confirmed = trueinstead. Sincereveal_confirmedis never set, the precondition always fails and the workflow ends inFAILED.Suggested fix:
if (!progress.reveal_confirmed && !progress.score_confirmed)2. Reconciliation
ADVANCE_TO_STEPskips progress stateWhere:
src/workflows/reconciliation.ts#L227and#L653When reconciliation detects work already exists on-chain and advances from
SUBMIT_WORK_ONCHAINtoREGISTER_WORK, it updates the step but doesn't setprogress.onchain_confirmed = true. Any downstream logic that checksonchain_confirmedwon't see it, even though the work is confirmed on-chain.How this was found
Local E2E testing with:
--block-time 1)EthersChainAdapter,InMemoryWorkflowPersistence,MockArweaveAdapterResult
The E2E infrastructure (Docker Compose with Anvil + gateway + PostgreSQL) will be added in a follow-up PR.