Contract model
This page specifies how contracts sit inside the transaction rules: the Deploy and Call payloads, what happens when a contract fails, how vaults delay calls, calls between contracts, receipts and the receipt root, how gas meets fees, free reads, and the state keys contracts use. It is written for protocol engineers and auditors. The sandbox, with its gas table and host calls, is on Virtual machine. Writing a contract is on Writing contracts, and the deployed contracts are on Token, AMM and escrow.
What a contract is
A contract is a WebAssembly module held in state, with an address, a native balance and a private key-value store. Each function is an export. init runs once, at deployment.
- Only the governance account can deploy. See why deployment is governance-only.
- Code does not change. No host call or payload replaces it, and the chain has no upgrade mechanism.
- A contract address has no key. It is a hash of the deployer and a nonce, not of an
AuthPolicy, so a transaction signed for it fails withRevealMismatch. - A contract reads and writes only its own storage, and sends only its own native balance. It cannot pull funds from an account.
Payload::Transferto a contract credits its balance and runs no code. There is no receive hook.- A contract cannot deploy a contract. There is no host call for it, so factories cannot exist.
- Host calls handle
AssetId::NATIVEonly. A token is a contract with books in its own storage, and the chain does not know who holds what.
Record and address
/// What is stored for a deployed contract. The code lives once under its hash.
pub struct Contract {
pub code: Hash,
}
pub fn contract_address(deployer: &Address, nonce: u64) -> Address;
pub fn code_hash(code: &[u8]) -> Hash;
Both are BLAKE3 in derive-key mode, each with its own context:
contract_address = BLAKE3(context "qubits.sh 2026-11 contract address",
deployer (32 bytes) || nonce (8 bytes, little-endian))
code_hash = BLAKE3(context "qubits.sh 2026-11 contract code", code)
nonce is the nonce of the deployment transaction, not the account's nonce after it. On a new chain the first deployment is at contract_address(governance_address(), 0).
Payloads
Transaction is unchanged by contracts and has no gas field. Two variants of Payload carry a gas limit, and Payload::gas_limit() returns 0 for all the others.
pub enum Payload {
Transfer { to: Address, asset: AssetId, amount: Amount }, // tag 0
SetAuth { auth: AuthPolicy }, // tag 1
SetVault { vault: Option<VaultPolicy> }, // tag 2
CancelPending { id: u64 }, // tag 3
ExecutePending { account: Address, id: u64 }, // tag 4
SetValidators { validators: Vec<Validator> }, // tag 5
/// Puts a contract on the chain. Only the governance account can, for now.
Deploy { // tag 6
code: Vec<u8>,
args: Vec<u8>,
gas_limit: u64,
},
/// `contract.function(args)`, with `amount` of the native token going along.
Call { // tag 7
contract: Address,
function: String,
args: Vec<u8>,
amount: Amount,
gas_limit: u64,
},
ClaimRewards { payout: Address }, // tag 8
}
| Field | Type | Limit | Checked by |
|---|---|---|---|
Deploy.code | Vec<u8> | MAX_CODE_BYTES, 163,840 bytes (160 KiB) | TxError::TooLarge |
Deploy.args, Call.args | Vec<u8> | MAX_ARGS_BYTES, 16,384 bytes (16 KiB) | TxError::TooLarge |
Call.function | String | MAX_FUNCTION_NAME, 64 bytes | TxError::TooLarge |
gas_limit | u64 | MAX_TX_GAS, 5,000,000 | TxError::GasTooHigh |
Call.amount | u128 | the sender's balance after the fee, less a vault's recovery reserve | TxError::InsufficientBalance, TxError::RecoveryReserve |
| whole transaction | MAX_TX_BYTES, 294,912 bytes (288 KiB) | TxError::TooLarge |
The chain does not read args. It hands them to the function as bytes, and each contract defines its own layout.
Encoding
Both payloads are borsh, as is everything hashed or signed. Vec<u8> and String carry a u32 little-endian length.
Deploy: 06 | u32 len | code | u32 len | args | gas_limit (u64 LE)
Call: 07 | contract (32) | u32 len | function | u32 len | args | amount (u128 LE) | gas_limit (u64 LE)
In JSON, code and args are hex and amount is a decimal string:
{"call":{"contract":"qb1w32tl3w33tcc8pn8g4xwpedcpyvrhvzs040cwqs992y7ph2nfu0smd2dv8","function":"transfer","args":"0102","amount":"5","gas_limit":7}}
{"deploy":{"code":"0061","args":"09","gas_limit":7}}
Deployment
Payload::Deploy is valid only from governance_address(), whose owner policy is always a quorum of the current validators. Any other sender gets TxError::NotGovernance. The governance account can send only SetValidators and Deploy, and anything else from it fails with NotAllowedForGovernance, so it cannot call contracts, hold a vault or transfer. It pays fees like any account, so it needs funds. See Governance.
Why deployment is governance-only
A contract runs only once a quorum of validators has signed its deployment, so in practice only contracts written by the project run. Opening deployment is a separate decision that needs a protocol change.
Three things depend on this. contracts in state stays short enough for wallets to ask each contract what it is. Open deployment would need an index by holder and protection against spam tokens. And there can be no factory, which is why the AMM is one contract holding every pool.
Open deployment: Not implemented.
Algorithm
After the common checks, contracts::deploy runs these steps, all through the undo log:
- Take
stored = code.len() + 32and subtractstored × STATE_BYTEfromgas_limit. On underflow, finishOutOfGaswithgas_used = gas_limitand write nothing. - Write
code/<hash>,contract/<address>and thecontractslist withaddressappended. - Run
initwithargs, with the deployer as caller and value 0. A module with noinitexport deploys doing nothing. - If
initdid not finishDone, roll back everything, including the writes of step 2. - Credit the refund for unused gas to the governance account, and fill in the receipt.
Receipt.contractisSome(address)only when the outcome isDone.
A failed deployment is included, pays and uses up the nonce. It leaves no code, no contract record and no entry in contracts. Two contracts with the same code share one code/<hash> entry, but each deployment pays for it in full. A rollback does not remove an entry that was already in state, because the undo log restores only what this transaction touched.
The gas of a deployment, for code of L bytes and p initial memory pages, is:
gas = (L + 32) × STATE_BYTE state for the code and its hash
+ CALL + L × LOAD_BYTE starting the run
+ p × PAGE instantiating memory
+ whatever init uses
= 80 × L + 4,048 + 500 × p + init
MAX_TX_GASlimits deployments beforeMAX_CODE_BYTESdoes. At 80 gas a byte, the largest module that can deploy has one page, noinit, and 62,443 bytes, which uses 4,999,988 gas. One more byte endedOutOfGaswhen run. A module of the full 163,840 bytes is a valid transaction of 167,274 bytes, is included, pays, and endsOutOfGas.
Module in out/ | Code bytes | Transaction bytes | Deployment gas |
|---|---|---|---|
token.wasm | 6,677 | 10,182 | 681,481 |
escrow.wasm | 5,290 | 8,724 | 427,748 |
amm.wasm | 14,282 | 17,716 | 1,147,108 |
These were measured on a test chain with one validator key, so one signature. The token's init arguments were (owner, 1000, "Test", "TST", 6).
Calls
Validity
Staged::apply checks a transaction in this order. Failing any check gives a TxError: the transaction is invalid, stays out of the block and pays nothing.
- Encoded size at most
MAX_TX_BYTES. genesis_hashmatches, andvalid_until_mshas not passed.- Sizes of
code,argsandfunction. gas_limitat mostMAX_TX_GAS.feeat least the required fee, and at mostMAX_FEE_MULTIPLE(8) times it.- The sender exists, the nonce matches, and the balance covers the fee.
- Policy reveal, then signatures, which go last because they are expensive.
- The auth policy is saved, the nonce goes up, and the fee is debited.
- Governance gating, then the recovery rule: a
Role::Recoverytransaction may only cancel or replace the owner policy, so a recoveryCallfails withNotAllowedForRecovery.
Step 8 writes to a Staged overlay. If anything fails later in apply, the whole overlay is dropped, so the fee and nonce go with it.
For Payload::Call, four more checks follow:
function == "init"fails withTxError::NotCallable.- No
contract/<address>record fails withTxError::NotAContract. - A balance below
amount, after the fee, fails withTxError::InsufficientBalance. For a vault with a recovery policy, one that would leave less than its recovery reserve fails withTxError::RecoveryReserve. - A vault sender goes to the delay queue. Otherwise the gas is reserved and the call runs.
Execution
For an account that is not a vault:
reserve_gas(gas_limit)counts the limit against the block, or fails withBlockGasFull.contracts::callcreates aRunwith an empty undo log, event list and call stack, and callsenterwith the sender asfrom.- The receipt goes into the overlay.
gas_refund(gas_left)is credited to the sender.
Run::enter is the same code for a transaction and for a call from a contract:
- If
stack.len() >= MAX_CALL_DEPTHortois already on the stack, finishReverted { reason: "call not allowed here" }with all gas left. - Load the code through
contract/<to>, thencode/<hash>. If there is none, finishInvalidwith all gas left. - Mark the length of the undo log and of the event list.
- If
value > 0, move it fromfromtoto. If that fails, finishReverted { reason: "insufficient balance" }with all gas left. - Push
to, run the VM, pop. - If the outcome is not
Done, roll back to the marks. The value goes back too.
The failure rule
A transaction can fail in two ways, and they have different results.
| Invalid transaction | Failed contract | |
|---|---|---|
| What it is | Any TxError from apply | A valid transaction whose Outcome is not Done |
| In a block | No. execute_block rejects a block containing one | Yes |
| Fee | Not paid | Paid, less the refund for unused gas |
| Nonce | Unchanged | Advanced |
| Contract's work | Never ran, or discarded | Undone through the undo log |
| Result | The node's error | Receipt with the outcome |
A failing call has to pay, or anyone could burn validators' CPU for free. A node does not run code when it takes a transaction in, so a transaction that will revert is accepted, then included.
Undo log
Everything a contract changes goes through an undo log. The fee and nonce sit outside it and always stand.
enum Undo {
Write {
key: Vec<u8>,
/// None if this transaction hadn't touched the key.
previous: Option<Option<Vec<u8>>>,
},
Balance { address: Address, previous: Amount },
Created(Address),
}
struct Run<'r, 'e, 'a, S: StateReader> {
staged: &'r mut Staged<'e, 'a, S>,
undo: Vec<Undo>,
events: Vec<Event>,
stack: Vec<Address>,
}
Reads go through three layers, in order: this transaction's writes, the writes of earlier transactions in the block, then state. Balances live in Staged.accounts, overlaid the same way.
Each write records what the overlay held for that key. previous has three cases. None means the transaction had not touched the key, Some(None) means it had deleted it, and Some(Some(bytes)) means it had written it. A credit to an address with no account pushes Created, then Balance.
rollback(mark, events) truncates the event list, then pops entries newest first until the log is back at mark. A Write either restores the overlay's value or removes the key from the overlay, a Balance restores the amount, and a Created removes the account. Because every enter takes its own marks, a failed callee undoes only itself and the callers below it keep what they did.
Calls between contracts
The call host call reaches Run::call, which refuses init and otherwise calls enter with the calling contract as from. value comes out of the calling contract's own balance.
No re-entrancy
A running contract cannot be called again, at any depth. enter checks the stack, which holds every running contract, so A calling B calling A fails, and so does A calling itself. Most contract thefts work through re-entry. The failed call is an ordinary failure: the outer contract gets 1 from call and can carry on. tests/contracts.rs covers this in a_contract_cannot_be_reentered.
Depth
MAX_CALL_DEPTH is 8. The stack holds the top-level contract too, so eight contracts can be running. The ninth is refused with call not allowed here, and calls_go_only_so_deep pins this.
Gas across calls
call has no gas parameter. It gives the callee all the gas left, and the caller gets back what the callee did not use.
| What the callee does | call returns | Caller's gas afterwards |
|---|---|---|
| Finishes | 0 | What is left |
| Reverts | 1 | What is left |
Is refused: re-entry, depth, init, value too high | 1 | Unchanged |
| Is not a contract | 1 | Unchanged |
| Has no such function | 1 | What is left after loading it |
| Runs out of gas | 1 | 0 |
| Traps | 1 | 0 |
A failed callee is undone alone, but a trap or running out of gas also takes all the gas that was forwarded. The caller then resumes with none, so its next host call or metered block ends it
OutOfGas, and the whole transaction is undone. Only a caller that does neither finishesDone. Measured with a 2,000,000 gas limit, a reverting callee left the outer callDoneat 67,367 gas. A trapping callee and a looping callee each ended the outer callOutOfGasat 2,000,000 gas. A caller can only count on recovering from a revert.
A callee's revert reason does not reach its caller. Finish::early leaves output empty, and result then holds nothing. Only the outermost failure's reason goes into the receipt.
Vault calls
A vault's Payload::Call does not run at once. It goes into the delay queue like any other owner action. Without this, a stolen owner key could move what the vault holds inside contracts, past the delay. See Vaults.
pub enum DelayedAction {
Transfer { to: Address, asset: AssetId, amount: Amount }, // tag 0
SetAuth(AuthPolicy), // tag 1
SetVault(Option<VaultPolicy>), // tag 2
/// A vault's contract call: it can move anything the vault holds in a contract, so it waits.
Call { // tag 3
contract: Address,
function: String,
args: Vec<u8>,
amount: Amount,
gas_limit: u64,
},
}
Queueing
The Call passes the same validity checks, then queue adds a PendingAction. Up to MAX_PENDING_ACTIONS (16) may wait, and beyond that it fails with TooManyPending. No code runs and no gas is reserved.
The vault pays the full fee now, gas included, and the refund never comes. The receipt is Receipt::default(), so nodes keep none and GET /v1/transactions/{id} shows no receipt. amount is checked against the balance less the recovery reserve, not set aside.
Executing
After unlock_at_ms, anyone may send Payload::ExecutePending { account, id }, and the sender pays only for that transaction's own bytes, because its gas_limit() is 0. Then:
- The action leaves the queue.
reserve_gas(gas_limit)counts the call against this block.BlockGasFullmakes theExecutePendinginvalid for this block, and the dropped overlay leaves the action queued.contracts::callruns with the vault asfrom, so the contract sees the vault ascaller.- The receipt becomes the receipt of the
ExecutePendingtransaction.gas_leftis dropped: nothing comes back to anyone.
A call that fails here has still left the queue and does not retry. If the vault no longer holds amount above its recovery reserve, nothing runs and the receipt says Reverted { reason: "insufficient balance" }, with 0 gas used (a_queued_call_cannot_spend_the_recovery_fee).
Until it runs, the recovery policy can remove the action with CancelPending. a_stolen_vault_key_cannot_move_tokens_through_a_contract and a_vault_call_runs_after_the_delay_as_the_vault in tests/contracts.rs cover both paths, and a_vaults_trade_waits_like_everything_else in tests/amm.rs covers a trade.
Receipts
Every applied transaction has one receipt. It records what state alone cannot say: why a call did nothing.
pub struct Receipt {
pub outcome: Outcome,
pub gas_used: u64,
pub output: Vec<u8>,
pub events: Vec<Event>,
/// The address a deployment created.
pub contract: Option<Address>,
}
/// How a transaction ended. Anything but `Done` undid the contract's work and still paid.
/// No text from the VM goes in here: receipts are hashed into the block.
pub enum Outcome {
Done, // tag 0, the default
Reverted { reason: Vec<u8> }, // tag 1
OutOfGas, // tag 2
Trapped, // tag 3
/// The code is not a usable contract, or has no such function.
Invalid, // tag 4
}
pub struct Event {
pub contract: Address,
pub topic: Vec<u8>,
pub data: Vec<u8>,
}
| Field | Meaning |
|---|---|
outcome | How the outermost run ended. The causes are on the VM page |
gas_used | gas_limit - gas_left |
output | What the outermost function gave back. Empty unless the outcome is Done |
events | Events in the order they were emitted, across all contracts in the call. Those of a failed call are gone, so a failed transaction has none |
contract | The new address, for a deployment that ended Done |
Plain transactions and queued vault calls get Receipt::default(), which is 18 zero bytes in borsh. It has outcome: Done and gas_used: 0, so a Done receipt does not prove that a contract ran. A receipt with Done and 0 gas from a vault's Call means it was queued.
Receipt root
BlockPayload.receipt_root commits to the receipts of a block, in transaction order:
leaf = BLAKE3(context "qubits.sh 2026-11 receipt root leaf", borsh(receipt))
node = BLAKE3(context "qubits.sh 2026-11 receipt root node", left || right)
Pairs are hashed level by level. An unpaired last node moves up unchanged, and an empty block has Hash::ZERO. Every transaction has a leaf, plain ones included. A validator recomputes the root when it executes a block and refuses a proposal that disagrees, so gas used is consensus. A different gas count on one machine is a fork. See Virtual machine.
What is kept
A node stores only receipts that differ from Receipt::default(), in a receipts column keyed by transaction id. History expiry deletes them along with the transaction index. A node that joined from a snapshot has none for blocks below its pivot. Receipts are not in state.
JSON
A successful transfer on the token, from a test run:
{
"outcome": "done",
"gas_used": 190562,
"output": "",
"events": [
{
"contract": "qb1w32tl3w33tcc8pn8g4xwpedcpyvrhvzs040cwqs992y7ph2nfu0smd2dv8",
"topic": "7472616e73666572",
"data": "69b5f32ed0cb56494813a57cc14399d07bf6ea26acecf47bd21428488c474fbaaaad775dbfdb48cc3bf1e4740c60bff70e03a92b1676f6b289ed8d2696a459ea2c010000000000000000000000000000"
}
],
"contract": null
}
The same call for more than the balance, with reason in hex for balance too low:
{
"outcome": { "reverted": { "reason": "62616c616e636520746f6f206c6f77" } },
"gas_used": 130952,
"output": "",
"events": [],
"contract": null
}
The other outcomes are the strings "out_of_gas", "trapped" and "invalid". topic, data, output and reason are hex.
Gas and fees
Gas is paid in the fee, so the transaction format has no gas price. One byte's worth of fee buys GAS_PER_BYTE (16) gas.
pub fn required_fee(min_fee_per_byte: u64, size: usize, gas_limit: u64) -> Amount {
let weight = size as u128 + u128::from(gas_limit.div_ceil(GAS_PER_BYTE));
u128::from(min_fee_per_byte) * weight
}
fn gas_refund(params: &ChainParams, gas_left: u64) -> Amount {
Amount::from(params.min_fee_per_byte) * Amount::from(gas_left / GAS_PER_BYTE)
}
The charge rounds up and the refund rounds down, so a refund is never more than was paid for that gas. size is the encoded SignedTransaction. The fee field has a fixed width in borsh, so the size can be computed from an unsigned draft. A transaction may pay up to MAX_FEE_MULTIPLE (8) times the minimum, and only gas_left is refunded: paying above the minimum buys nothing back.
| Limit | Value |
|---|---|
GAS_PER_BYTE | 16 |
MAX_TX_GAS | 5,000,000 |
MAX_BLOCK_GAS | 10,000,000 |
MAX_FEE_MULTIPLE | 8 |
Worked example
token.transfer from an account whose policy is already revealed, with a limit of 2,000,000 gas and min_fee_per_byte = 1, as a test chain ran it:
| Quantity | Value |
|---|---|
| Transaction size | 3,538 bytes |
gas_limit / 16, rounded up | 125,000 |
| Fee | 128,538 |
gas_used | 190,678 |
gas_left | 1,809,322 |
Refund, gas_left / 16 rounded down | 113,082 |
| Net cost | 15,456 |
At min_fee_per_byte = 10000000000, each number in fee units is multiplied by that: a fee of 1,285,380,000,000,000 base units, a refund of 1,130,820,000,000,000, and a net cost of 154,560,000,000,000, which is 0.00015456 of a token.
A vault's call pays the fee and gets no refund, whatever it uses.
Block gas
reserve_gas counts declared limits, not gas used:
fn reserve_gas(&mut self, gas_limit: u64) -> Result<(), TxError> {
let reserved = self.executor.gas_reserved + self.gas_reserved + gas_limit;
if reserved > MAX_BLOCK_GAS {
return Err(TxError::BlockGasFull);
}
self.gas_reserved += gas_limit;
Ok(())
}
The whole limit counts, used or not, so a proposer can fill a block without running what does not fit. MAX_BLOCK_GAS is twice MAX_TX_GAS, so a block holds two calls at the highest limit. gas_limits_in_a_block_are_capped pins that.
TxError::BlockGasFull means the transaction may fit in the next block. A proposer skips it and leaves it in the mempool, where any other error drops it. A validator checking someone else's block treats it like any error and refuses the block.
Reads and checks
Admission
Executor::without_contracts() makes an executor that checks a transaction and runs nothing. Node::accept must use it, or submitting would run code for free. It goes through every rule above and skips the three places code would run: Deploy, the direct Call, and DelayedAction::Call inside ExecutePending. It reserves no gas. The receipts are Receipt::default(), and the result says nothing about what a call would do. A vault's call still queues, because that runs no code.
View and simulate
pub struct Simulated {
pub receipt: Receipt,
/// The caller's native balance after the call, before any fee.
pub balance_after: Amount,
}
pub fn simulate(
state: &impl StateReader,
env: BlockEnv,
caller: &Address,
contract: &Address,
function: &str,
args: &[u8],
amount: Amount,
) -> Result<Simulated, TxError>;
pub fn view(
state: &impl StateReader,
env: BlockEnv,
contract: &Address,
function: &str,
args: &[u8],
) -> Result<Receipt, TxError>;
simulate runs a call against this state as caller, with amount going along, for free. view is simulate with the all-zero address as caller and no amount.
Both give the run MAX_TX_GAS. init fails with NotCallable, and an unknown address with NotAContract. Writes land in an overlay that is then dropped, so nothing is kept. There is no signature, fee, nonce, gas reservation or vault queue: a simulated call from a vault runs at once. Too large an amount gives a receipt with Reverted { reason: "insufficient balance" }, not an error.
State is public, so acting as anyone reveals nothing. This is how the wallet shows what a call will do without taking a website's word for it. See Wallet.
Node::simulate runs against the latest committed state, with height one above the head and timestamp_ms the larger of the clock and the head's time plus one. A real block can have a different height and time, so a call that reads them can behave differently.
Over HTTP this is POST /v1/contracts/{address}/view, which shares the submission permits because it runs code for free. The request is ViewRequest, and the response is the receipt fields flattened, plus balance_after. See API.
pub struct ViewRequest {
pub function: String,
pub args: Vec<u8>, // hex in JSON
pub caller: Option<Address>, // nobody by default
pub amount: Amount, // decimal string in JSON, 0 by default
}
State keys
All keys are in the state tree, so all of this goes into state_root and comes with a snapshot. Events and receipts do not.
| Key | Value |
|---|---|
account/ + address (32) | borsh Account. A contract's native balance is here, created at its first credit |
contract/ + address (32) | borsh Contract: the 32-byte code hash |
code/ + code hash (32) | The module's bytes |
storage/ + address (32) + name | The value's bytes, as the contract wrote them |
contracts | borsh Vec<Address>: every contract in deployment order |
validators | borsh ValidatorSet |
storage/ keys start with an address of fixed length, so one contract cannot reach another's keys. name is at most MAX_KEY_BYTES (128) and a value at most MAX_VALUE_BYTES (16 KiB).
read_contracts(state) returns the list. It is state and not an index beside it, so a node that joined from a snapshot has it too, and it serves GET /v1/contracts. A failed deployment is not in it. Every deployment rewrites the whole list, which works because only governance deploys. If that ever opens up, wallets will need something better than asking each contract.
Transaction errors
These are the TxError values specific to contracts. All of them make the transaction invalid.
| Error | Message | When |
|---|---|---|
NotGovernance | only the governance account can change validators or deploy contracts | Deploy from any other account |
NotAllowedForGovernance | the governance account can only change the validator set and deploy contracts | A Call or anything else from governance |
NotAContract | no contract at that address | Call to an address with no record |
NotCallable | init runs once, at deployment | Call of init |
GasTooHigh | gas limit is above 5000000 | gas_limit above MAX_TX_GAS |
BlockGasFull | no gas left in this block | The limit does not fit in the block |
NotAllowedForRecovery | recovery may only cancel pending actions or replace the owner policy | A Call signed by recovery |
TooManyPending | vault already has 16 pending actions | A vault's call with a full queue |
RecoveryReserve | a vault with a recovery policy keeps N for its recovery fee | A vault's call that would spend into its recovery reserve |
Storage | storage error | The node itself failed, which stops the block |
Fatal from the VM becomes TxError::Storage. It means a database error, not anything a contract did.
Source
chain/crates/qubits-execution/src/lib.rs:Staged::apply,reserve_gas,gas_refund,simulate,view,read_contracts,keyschain/crates/qubits-execution/src/contracts.rs:Run,Undo,enter,call,deploychain/crates/qubits-types/src/contract.rs:Contract,Event,Outcome,Receipt,contract_address,code_hash,receipt_rootchain/crates/qubits-types/src/transaction.rs:Payloadchain/crates/qubits-types/src/account.rs:DelayedAction,PendingActionchain/crates/qubits-types/src/client.rs:required_feechain/crates/qubits-types/src/lib.rs:limitschain/crates/qubits-types/src/rpc.rs:ViewRequest,ViewResponsechain/crates/qubits-node/src/app.rs: proposing aroundBlockGasFull, the receipt root check, which receipts are keptchain/crates/qubits-node/src/lib.rs:Node::accept,Node::simulatechain/crates/qubits-execution/tests/contracts.rs,chain/crates/qubits-execution/tests/amm.rs