Skip to content
Open
99 changes: 38 additions & 61 deletions docs/solidity.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,15 @@

## Compatibility

XDPoSChain compatibility is determined by the target EVM version, not by a
single Solidity compiler version.
XDPoSChain compatibility is determined by the target EVM version, not by a single Solidity compiler version.

Current network activation block numbers:

| Network | Cancun | Prague | Osaka |
| :-----: | -------: | :----: | :---: |
| mainnet | 98802000 | TBD | TBD |
| testnet | 71551800 | TBD | TBD |
| devnet | 25000 | 50000 | TBD |
| Network | Cancun | Prague | Osaka | Amsterdam |
| :-----: | -------: | :----: | :---: | :-------: |
| mainnet | 98802000 | TBD | TBD | TBD |
| testnet | 71551800 | TBD | TBD | TBD |
| devnet | 25000 | 50000 | TBD | TBD |

Recommended compiler settings:

Expand All @@ -23,81 +22,59 @@ Recommended compiler settings:

Notes:

1. `evmVersion` mainly affects opcode availability and code generation strategy,
not Solidity language syntax itself.
2. (0.8.30) pin `evmVersion: "cancun"` only if you need reproducible outputs
with existing Cancun-targeted artifacts.
3. (0.8.31+) set `evmVersion` to `cancun` (or `prague` after Prague activation)
unless your target network has Osaka.
4. The repository contains fixtures generated by `solc 0.8.28`, but that is not
a hard upper bound for supported Solidity versions.
5. If Prague is activated on XDPoSChain in the future, Solidity 0.8.30 and later
can target it by setting `evmVersion: "prague"` explicitly.
6. Solidity 0.8.31 introduced support for the `CLZ` opcode (EIP-7939), which is
part of Osaka, not Prague. The strong pinning warning applies to Osaka-only
features (such as CLZ), not to 0.8.30's default `prague` target by itself.
1. `evmVersion` mainly affects opcode availability and code generation strategy, not Solidity language syntax itself.
2. (0.8.30) pin `evmVersion: "cancun"` only if you need reproducible outputs with existing Cancun-targeted artifacts.
3. (0.8.31+) set `evmVersion` to `cancun` (or `prague` after Prague activation) unless your target network has Osaka. Do not set it to `amsterdam`; see note 7.
4. The repository contains fixtures generated by `solc 0.8.28`, but that is not a hard upper bound for supported Solidity versions.
5. Prague is scheduled on devnet only; mainnet and testnet still show TBD above. Once it activates on a network you target, Solidity 0.8.30 and later can target it by setting `evmVersion: "prague"` explicitly.
6. Solidity 0.8.31 introduced support for the `CLZ` opcode (EIP-7939), which is part of Osaka, not Prague. The strong pinning warning applies to Osaka-only features (such as CLZ), not to 0.8.30's default `prague` target by itself.
7. Solidity 0.8.36 began exposing the `amsterdam` target and 0.8.37 added `block.slotnum` and `slotnum()` to it (EIP-7843), both only with `--evm-version amsterdam`; it is still experimental and is not the compiler's default target. XDPoSChain cannot serve it: the fork switch exists, but its instruction set is Osaka plus EIP-8024 only, because EIP-7843, EIP-8037 and EIP-8038 are not implemented here, and no network in the table above has scheduled Amsterdam. Keep the `osaka` target, or pin `evmVersion` to an older one, until those EIPs land.

## Transaction and block size limits

XDPoSChain enforces neither of the two caps Osaka introduces on Ethereum:
EIP-7825 limits a transaction to 16,777,216 gas and EIP-7934 limits an
RLP-encoded block to 8 MiB. Both are implemented here and both are gated
behind Osaka, which no network has scheduled yet, so neither applies today.
For reference, the public networks run with a block gas limit of 420,000,000,
set through the node's `--targetgaslimit`; the in-repository default
(`XDCGenesisGasLimit`) is 42,000,000.
XDPoSChain enforces neither of the two caps Osaka introduces on Ethereum: EIP-7825 limits a transaction to 16,777,216 gas and EIP-7934 limits an RLP-encoded block to 8 MiB. Both are implemented here and both are gated behind Osaka, which no network has scheduled yet, so neither applies today. For reference, the public networks run with a block gas limit of 420,000,000, set through the node's `--targetgaslimit`; the in-repository default (`XDCGenesisGasLimit`) is 42,000,000.

A transaction or deployment that spends more than 16,777,216 gas therefore
succeeds here but cannot be replayed on an Ethereum network that runs those
caps, which makes such artifacts migrate one way only. Stay below the cap if
the contract has to remain portable.
A transaction or deployment that spends more than 16,777,216 gas therefore succeeds here but cannot be replayed on an Ethereum network that runs those caps, which makes such artifacts migrate one way only. Stay below the cap if the contract has to remain portable.

Both caps take effect by themselves once Osaka is scheduled and active.

Contract code and initcode are limited separately, by EIP-7954: 24,576 bytes of code and 49,152 of initcode today, rising to 65,536 and 131,072 once Amsterdam is active. That raise is gated on Amsterdam, which is where upstream go-ethereum has it, not on Osaka. No network has scheduled Amsterdam, so the lower pair applies everywhere today.

## Prague / EIP-2935

Prague adds a history storage contract at `HistoryStorageAddress`, `0x0000F90827F1C53a10cb7A02335B175320002935`, which holds the hashes of the last 8191 blocks.

`blockhash` itself is unchanged: it answers only for the last 256 blocks and returns zero beyond that. To read an older hash, call the history contract with the block number; it returns the hash it stored for that block, and reverts for a number outside the window it keeps. A slot inside the window that was never written reads back as zero, so right after activation most older numbers return zero rather than a hash.

The contract is only present once Prague is active on the network, so there is nothing to call on mainnet or testnet yet.

## Special variables

### block.prevrandao

The value of `block.prevrandao` is `keccak256(block.number)` in our current implemention. It is predictable and unsafe.
The value of `block.prevrandao` is `keccak256(block.number)` in our current implementation. It is predictable and unsafe.

**NOTICE: do not use it in real business.**

### block.basefee

The value of `block.basefee` is the gas price of the block's tier: `12.5 GWei` up to the Gas2500x fork, and `625 GWei` from it. Between the London and EIP-1559 forks, where block headers carry no base fee, it is `12.5 GWei`.

### blobhash()

XDPoSChain has no blob transactions (transaction type `0x03`) and no blob fields
in the block header, so `blobhash(i)` always returns 0. That is the out-of-bounds
value defined by EIP-4844, so `require(blobhash(i) != 0)` reverts as expected.
The KZG point evaluation precompile (`0x0a`) is not implemented. On the networks
that run the Prague precompile set (devnet from block 50000, localnet from the
genesis block) its address is registered and every call to it fails, consuming
the gas of the frame. No network runs the Osaka set yet, because every network
configuration still leaves `osakaBlock` unset. On mainnet and Apothem, whose
Prague and Osaka blocks are still unset, the address is not registered at all, so
a call to it is handled as a call to an empty account: it succeeds and returns
empty output.
The pool accepts a transaction only when its gas price reaches that same value, the base fee of the block that would include it, and rejects anything priced below with `under min gas price`; transactions to the block-signer and randomize system contracts are exempt. Tools that default to a low `gasPrice` or `maxFeePerGas`, Hardhat and Foundry among them, are rejected until it is raised:

### block.blobbasefee
| Network | Gas tier fork | Base fee and minimum gas price |
| :------ | :------------------ | -------------------------------: |
| mainnet | Gas50x (80370000) | 12.5 GWei |
| testnet | Gas50x (56828700) | 12.5 GWei |
| testnet | Gas2500x (83600000) | 625 GWei |
| devnet | Gas2500x (1385100) | 625 GWei |

The value of `block.blobbasefee` is always 0 in our EIP-7516 implemention.
EIP-7516 sets a floor of 1 wei; XDPoSChain stays at 0 because it runs no blob fee
market. Do not use it to infer on-chain activity.
Read the floor from the node with `eth_gasPrice` instead of hardcoding it.

## Gas price
### blobhash()

A transaction priced below the floor of the tier active at the block that would
include it is rejected with `under min gas price`. That floor is the gas price of
the tier, the same value `block.basefee` reports there, so tools that default to
a low `gasPrice` or `maxFeePerGas` (Hardhat and Foundry among them) must have it
raised explicitly:
XDPoSChain has no blob transactions (transaction type `0x03`) and no blob fields in the block header, so `blobhash(i)` always returns 0. That is the out-of-bounds value defined by EIP-4844, so `require(blobhash(i) != 0)` reverts as expected. The KZG point evaluation precompile (`0x0a`) is not implemented. On the networks that run the Prague precompile set (devnet from block 50000, localnet from the genesis block) its address is registered and every call to it fails, consuming the gas of the frame. No network runs the Osaka set yet, because every network configuration still leaves `osakaBlock` unset. On mainnet and Apothem, whose Prague and Osaka blocks are still unset, the address is not registered at all, so a call to it is handled as a call to an empty account: it succeeds and returns empty output.

| Network | Gas tier fork | Minimum gas price |
| :------ | :----------------- | ----------------: |
| mainnet | Gas50x (80370000) | 12.5 GWei |
| testnet | Gas50x (56828700) | 12.5 GWei |
| devnet | Gas2500x (1385100) | 625 GWei |
### block.blobbasefee

Read the value from the node with `eth_gasPrice` instead of hardcoding it.
The value of `block.blobbasefee` is always 0 in our EIP-7516 implementation. EIP-7516 sets a floor of 1 wei; XDPoSChain stays at 0 because it runs no blob fee market. Do not use it to infer on-chain activity.
Loading
Loading