fix: classify workflow value fee from definition, not runtime gas#508
Conversation
buildValueFee was inspecting ExecutionLogs for CONTRACT_WRITE/ETH_TRANSFER step types or a LOOP step with non-zero TotalGasCost. Under Tenderly-backed simulation, a loop running an inner contractWrite records no real gas, so the same workflow was classified as Tier 1 in production but "no on-chain execution nodes" in simulation — causing the value fee to silently drop to 0% in simulate responses. Classify off the workflow definition (task.Nodes) instead, mirroring classifyWorkflowValue. Loops are inspected for an inner ETHTransfer or ContractWrite runner. Simulation and production now agree. Tests: - TestBuildValueFee_ClassifiesByDefinitionNotGas: direct unit coverage of buildValueFee across loop(contractWrite), pure off-chain, and direct contractWrite shapes. - TestSimulateTask_ValueFeeMatchesWorkflowDefinition: engine-level test exercising SimulateTask end-to-end with the revenue-splitter shape that triggered the bug, asserting Tier 1 / 0.03%. - TestSimulateTask_ValueFeeOffChainOnly: negative case asserting UNSPECIFIED / 0% for an off-chain-only workflow.
There was a problem hiding this comment.
Pull request overview
Fixes a value-fee classification mismatch between production executions and Tenderly-backed simulations by classifying “on-chain-ness” from the workflow definition (task.Nodes) rather than runtime gas/logs.
Changes:
- Update
buildValueFeeto detect on-chain execution viaTaskNode/LoopNoderunner types (ContractWrite/EthTransfer) instead ofExecutionLogsgas. - Wire the new
buildValueFee(task.Nodes, ...)signature into both execution (RunTask) and simulation (SimulateTask) paths. - Add unit + engine-level regression tests covering loop(contractWrite) and off-chain-only workflows.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| core/taskengine/value_fee_simulation_test.go | Adds end-to-end SimulateTask regression coverage to ensure simulation value fees match workflow definition. |
| core/taskengine/fee_estimator.go | Switches buildValueFee classification from runtime steps/gas to workflow nodes (including loop runner inspection). |
| core/taskengine/fee_estimator_test.go | Adds unit test coverage for buildValueFee classification based on definition. |
| core/taskengine/executor.go | Updates execution path to call buildValueFee(task.Nodes, ...). |
| core/taskengine/engine.go | Updates simulation path to call buildValueFee(task.Nodes, ...). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| hasOnChainSteps := false | ||
| for _, step := range steps { | ||
| stepType := step.Type | ||
| if stepType == avsproto.NodeType_NODE_TYPE_CONTRACT_WRITE.String() || | ||
| stepType == avsproto.NodeType_NODE_TYPE_ETH_TRANSFER.String() { | ||
| for _, node := range nodes { | ||
| switch { | ||
| case node.GetEthTransfer() != nil, node.GetContractWrite() != nil: | ||
| hasOnChainSteps = true | ||
| break | ||
| case node.GetLoop() != nil: | ||
| loop := node.GetLoop() | ||
| if loop.GetEthTransfer() != nil || loop.GetContractWrite() != nil { | ||
| hasOnChainSteps = true | ||
| } |
There was a problem hiding this comment.
buildValueFee now repeats the same on-chain-node detection logic as FeeEstimator.classifyWorkflowValue (fee_estimator.go:460-475). To avoid future drift between EstimateFees and execution/simulation responses, consider extracting a shared helper (e.g., hasOnChainNodes(nodes)) and using it in both places.
| func buildValueFee(nodes []*avsproto.TaskNode, feeRatesConfig *config.FeeRatesConfig) *avsproto.ValueFee { | ||
| rates := convertFeeRatesConfig(feeRatesConfig) | ||
|
|
||
| hasOnChainSteps := false |
There was a problem hiding this comment.
Variable name hasOnChainSteps is a bit misleading now that this function inspects workflow nodes (not execution steps). Renaming to hasOnChainNodes would better reflect what’s being evaluated (and matches naming used in classifyWorkflowValue).
Addresses Copilot review feedback on PR #508: - Both classifyWorkflowValue and buildValueFee duplicated the same on-chain detection loop, risking future drift between EstimateFees and simulation/execution responses. Extracted shared hasOnChainNodes(nodes) helper used by both call sites. - The misleading hasOnChainSteps local in buildValueFee is gone with the loop, removing the steps-vs-nodes naming confusion.
buildValueFee was inspecting ExecutionLogs for CONTRACT_WRITE/ETH_TRANSFER step types or a LOOP step with non-zero TotalGasCost. Under Tenderly-backed simulation, a loop running an inner contractWrite records no real gas, so the same workflow was classified as Tier 1 in production but "no on-chain execution nodes" in simulation — causing the value fee to silently drop to 0% in simulate responses.
Classify off the workflow definition (task.Nodes) instead, mirroring classifyWorkflowValue. Loops are inspected for an inner ETHTransfer or ContractWrite runner. Simulation and production now agree.
Tests: