Trace-Based Gas Attribution on Avalanche C-Chain
In March 2025 I needed to answer a question the block explorer cannot: when a transaction burns AVAX, which contract did the work? The transaction’s to field points at whatever the user called first. A swap through an aggregator credits the aggregator, and the DEX that executed the trade, the token contract that moved the balance, the wrapped AVAX contract in the middle, all disappear. For anything downstream that wants to reason about which protocols drive fee burn, that is the wrong number.
What we measured
Every EVM transaction leaves an execution trace, a record of each internal call with the gas it was handed. That gives a clean way to split the bill. For each contract in a transaction, self gas is the gas it received minus the gas it delegated to sub-calls. If A receives 100 and passes 60 to B, A consumed 40 and B consumed 60. The parts always sum back to the transaction’s gas used, which is the property that makes the method checkable.
The data lived in ClickHouse, in the raw transaction and raw trace tables I had been building for the evm-metrics migration. The attribution was one query over traces, grouped by contract, multiplied by the gas price of the parent transaction.
How we checked it
Three levels, each against ground truth computed the simple way, gas used times gas price per transaction.
Single transactions first. On sampled multi-contract swaps the trace math balanced to exactly zero: five traces, five self-gas figures, summing to the transaction’s gas used with no residual.
Then one full day, 20 March 2025, 261,037 transactions. Ground truth burned 278.64 AVAX. Trace attribution summed to 278.51. Difference 0.14 AVAX, 0.049%.
Then 22 days, 1 to 22 March, 7.6 million transactions. Ground truth 19,147.70 AVAX, attribution 19,144.01, difference 3.69 AVAX, 0.02%. ClickHouse lagged the chain head by about 20 hours, so the last day was partial, which is the kind of caveat you only find when you run the whole month rather than trusting the day.
Where the residual comes from
The 0.02 to 0.05% gap is not noise. It is DELEGATECALL and STATICCALL frames, which I left out of the attribution model on purpose. A delegatecall runs the callee’s code in the caller’s storage, so charging the callee for that gas would credit a library for work done on behalf of the contract that called it. Excluding those frames is the right accounting for the question being asked, and the residual is the price of that choice, small and explainable.
What I took from it
Two things. First, validate at more than one scale. The single transaction check proved the arithmetic, the day proved the query, the month found the data lag. Any one of them alone would have looked like a pass. Second, write down what you excluded and why. The residual only reads as a result rather than an error because the exclusion was deliberate and documented before the numbers came in.
The two-page validation note that came out of this became the reference for how fee burn gets attributed per protocol on the C-Chain.