Costing
Costing in Radix combines both static analysis and runtime enforcement to ensure the fair and efficient use of computational and storage resources. Although we will mostly focus on execution-costing mechanisms in this section, subsystems roughly fall into the following categories:
Execution cost
Native-code execution
Guest-code execution (WASM)
Finalization cost
Commit events
Commit logs
Commit state updates
Execution cost: Native code
Native-code execution here refers to the cost of executing any of the functions exported as the VM runtime. Unlike the guest code, the exact cost cannot be measured during the runtime for the sake of determinism, but the implemented mechanism comes very close to estimating the real cost of each call.
The costing mechanism is implemented as follows:
Marking. A proc macro is used at the specific functions that the guest code interacts with and is of interest to the costing runtime. It can also specify arguments to monomorphize the costing mechanism if any of them affect the cost of the call significantly (e.g., a function that processes a
Vec<T>
argument).Sampling. When executed under QEMU, this macro times the execution of the function and writes a sample. After executing a number of sample transactions, the profiler writes a costing table to be used by production runtime.
Costing. Upon each call to the priorly marked functions, the costing runtime uses the costing table to estimate the cost of the call and charges it to the transaction.
While this is not an exact approach, it provides a reasonable approximation of the cost of each call.
Execution cost: Guest code (WASM)
The guest code's execution cost is measured and enforced by instrumenting the WASM module during the preparation phase. The instrumentation ensures that every operation in the WASM module is metered to track resource usage and enforce runtime limits.
Validation phase
The
ScryptoV1WasmValidator
ensures that the WASM module adheres to predefined limits. It enforces constraints on the following:Maximum memory size in pages
Maximum stack size
Maximum number of instructions
Maximum number of function parameters
Maximum number of function locals
Maximum number of tables
Maximum number of globals
Maximum number of functions
Maximum number of
br_table
targets
Metering
The metering process involves several transformations to the WASM code to ensure proper resource tracking and safety:
The code is instrumented with additional instructions that call a
gas
function to deduct execution-cost units for each block of instructions.Every function is wrapped into an indirect call stub to track the current stack height when used by
call_indirect
callers.If the stack height exceeds the maximum limit, the execution halts to prevent stack overflows.
Given the following input WASM —
First, the control-flow graph is walked and the cost of each instruction within basic blocks are clustered, emitting a gas
function prior.
Afterwards, a thunk is created for each function, wrapping the instrumented function and adding the necessary stack checks.
From this point on, every reference to the Test_f
function is replaced with the thunk, with the exception of direct calls where the stack guard is inlined into the flow.
Costing API
The runtime also exposes an API for querying costing parameters.
Function: COSTING_GET_USD_PRICE
This function retrieves the USD price for the use of stable costing. This is mainly used for fixed USD fees seen in blueprint royalties.
Function: COSTING_GET_EXECUTION_COST_UNIT_LIMIT
This function retrieves the execution-cost unit limit.
Function: COSTING_GET_EXECUTION_COST_UNIT_PRICE
This function retrieves the execution-cost unit price.
Function: COSTING_GET_FINALIZATION_COST_UNIT_LIMIT
This function retrieves the finalization-cost unit limit.
Function: COSTING_GET_FINALIZATION_COST_UNIT_PRICE
This function retrieves the finalization-cost unit price.
Function: COSTING_GET_TIP_PERCENTAGE
This function retrieves the tip percentage.
Function: COSTING_GET_FEE_BALANCE
This function retrieves the fee balance.