I learned this from norm’s thread on error 1010, and from poking at it afterwards.
The part I did not know: the node prices a transaction across five dimensions (readTime, computeTime, blockUsage, bytesWritten, bytesChurned), normalises each against the block limits, and then takes the maximum, not the sum. Your cost is set by your single worst dimension. Squeezing four of them and ignoring the fifth buys you nothing.
Question 3 in that thread asked whether anything can estimate transaction weight before deploying. The answer given was no, eyeball your .zkir and .wasm sizes or deploy to a local devnet and find out.
There is something, though it is partial. Every impure circuit call through @midnight-ntwrk/compact-runtime returns a gasCost object, no chain involved:
const r = contract.impureCircuits.myCircuit(ctx, ...);
console.log(r.gasCost);
On compact-runtime 0.16.0 that gives:
{
readTime: 170000000n,
computeTime: 1389176549n,
bytesWritten: 180n,
bytesDeleted: 180n
}
Two caveats, both real:
- Four dimensions, not five. blockUsage is not there, so you cannot compute the final normalised weight yourself. You can still watch which of the four moves when you change your contract, which is the thing you actually want while developing.
- The names differ. The thread says bytesChurned, the runtime field is bytesDeleted. Same idea as far as I can tell, but worth knowing if you go looking.
Why I care? I have been rewriting a contract to close a privacy leak and I assumed the change was free. Ran both versions through gasCost and one dimension moved about 7x in the wrong direction. My two versions differ in more than one way, so I am not going to claim the redesign caused it, that comparison is not clean. The point is I would never have thought to look without this thread, and I would have found out at deploy time instead.
One question for anyone from the team: is blockUsage derivable from these four, or does it need node state?