Overview
The Fenines execution layer implements a fully EVM-compatible state machine with modifications to support consensus-driven system transactions and native staking primitives. The architecture maintains Ethereum compatibility while extending functionality through precompiled system contracts at deterministic addresses.
EVM Version : London (EIP-1559 compatible)
Gas Model : Dynamic base fee with burn mechanism
State Model : Modified Patricia Merkle Trie
System Contracts : 4 precompiled addresses
State Machine Architecture
State Transition Function
The state transition function extends Ethereum’s model:
Υ ( σ , T ) = σ ′ ∪ T X system \Upsilon(\sigma, T) = \sigma' \cup \\{\mathcal{TX}_{\text{system}}\\} Υ ( σ , T ) = σ ′ ∪ T X system
where:
σ \sigma σ = World state at block n − 1 n-1 n − 1
T T T = Set of user transactions
T X system \mathcal{TX}_{\text{system}} T X system = System transactions (at epoch boundaries)
σ ′ \sigma' σ ′ = Resulting world state at block n n n
Epoch-Aware Transitions
State evolution branches based on block position:
σ n = { ProcessBlock ( σ n − 1 , T n ) if n m o d 200 ≠ 0 ProcessEpoch ( σ n − 1 , T n , T X sys ) if n m o d 200 = 0 \sigma_{n} = \begin{cases}
\text{ProcessBlock}(\sigma_{n-1}, T_n) & \text{if } n \bmod 200 \neq 0 \\
\text{ProcessEpoch}(\sigma_{n-1}, T_n, \mathcal{TX}_{\text{sys}}) & \text{if } n \bmod 200 = 0
\end{cases} σ n = { ProcessBlock ( σ n − 1 , T n ) ProcessEpoch ( σ n − 1 , T n , T X sys ) if n mod 200 = 0 if n mod 200 = 0
System Contract Framework
Contract Deployment Strategy
System contracts are genesis-embedded with immutable addresses:
Address Contract Type Purpose 0x0000...1000FenineSystem Logic Validator & delegator management 0x0000...1001NFTPassport Logic Whitelist & referral system 0x0000...1002TaxManager Logic Tax calculation & burn mechanism 0x0000...1003RewardManager Storage Dynamic reward configuration 0xFFFF...FFFFFeeRecorder EOA Gas fee accumulation
These addresses are consensus-critical . Changing them requires a hard fork.
FenineSystem Contract (0x1000)
Core Functions
Validator Lifecycle
Delegator Operations
System Functions
Query Functions
// Create validator
function createValidator (
uint256 commissionRate ,
bytes calldata blsPubKey
) external payable returns ( uint256 validatorId )
// Stake as validator
function stakeValidator ( uint256 validatorId )
external payable
// Unstake validator
function unstakeValidator () external
// Exit validator ( withdraw )
function exitValidator () external
State Machine :NOT_EXIST → create CREATED → stake STAKED → epoch VALIDATED \text{NOT\_EXIST} \xrightarrow{\text{create}} \text{CREATED} \xrightarrow{\text{stake}} \text{STAKED} \xrightarrow{\text{epoch}} \text{VALIDATED} NOT_EXIST create CREATED stake STAKED epoch VALIDATED VALIDATED → unstake UNSTAKED → epoch CREATED \text{VALIDATED} \xrightarrow{\text{unstake}} \text{UNSTAKED} \xrightarrow{\text{epoch}} \text{CREATED} VALIDATED unstake UNSTAKED epoch CREATED // Stake to validator
function stake ( address validatorAddress )
external payable
// Unstake from validator
function unstake ( address validatorAddress )
external
// Claim rewards
function claimDelegatorReward ( address validatorAddress )
external
// Emergency exit
function emergencyExit ( address validatorAddress )
external
Reward Calculation :R claim = R pending × ( 1 − P prox ) × ( 1 − τ ) R_{\text{claim}} = R_{\text{pending}} \times (1 - P_{\text{prox}}) \times (1 - \tau) R claim = R pending × ( 1 − P prox ) × ( 1 − τ ) where:
P prox ≈ 0.30 P_{\text{prox}} \approx 0.30 P prox ≈ 0.30 (proximity distribution)
τ = 0.10 \tau = 0.10 τ = 0.10 (tax rate)
// Called at epoch boundary (TX 1)
function updateValidatorCandidates () external
// Called at epoch boundary ( TX 2 )
function distributeBlockReward ( uint256 reward )
external
// Called at epoch boundary ( TX 3 )
function syncRewardState () external
Access Control :require ( msg.sender = block.coinbase ) \text{require}(\text{msg.sender} = \text{block.coinbase}) require ( msg.sender = block.coinbase ) Only the current block validator can call system functions. // Validator info
function getValidatorInfo ( address va )
external view returns ( ValidatorInfo )
// Delegator info
function getDelegatorInfo ( address dc , address va )
external view returns ( DelegatorInfo )
// Active validators
function getActiveValidators ()
external view returns ( address [])
// Proximity config
function getProximityConfig ()
external view returns ( ProximityConfig )
Gas-free read operations for dApps.
Storage Layout
Critical Storage Slots
These slots are consensus-critical and must match exactly across all nodes.
// Slot 11 (0x0B): activeValidatorSet.length
// Used by consensus engine for reward routing
bytes32 constant ACTIVE_VALIDATOR_SET_SLOT = bytes32 ( uint256 ( 11 ));
// Dynamic array elements at:
// keccak256(slot) + index
Invariant Verification :
forge inspect src/FenineSystem.sol:FenineSystem storage-layout
State Variables
Slot Variable Type Purpose 0 initializedboolInitialization guard 1 currentEpochuint256Epoch counter 2 totalValidatorsuint256VA count 3 totalDelegatorsuint256DC count 4 totalStakeduint256Network-wide stake … … … … 11 activeValidatorSet.lengthuint256Consensus-read
NFTPassport Contract (0x1001)
Implements a whitelist + referral system:
contract NFTPassport {
// Mint passport with referral
function mintPassport ( bytes32 referralKey )
external payable returns ( uint256 tokenId )
// Check passport status
function hasPassport ( address user )
external view returns ( bool )
// Get referrer
function getReferrer ( address user )
external view returns ( address )
// Generate referral key ( validator only )
function generateReferralKey ()
external returns ( bytes32 )
}
Referral Tree :
Tree ( u ) = parent ( u ) , children ( u ) \text{Tree}(u) = \\{\text{parent}(u), \text{children}(u)\\} Tree ( u ) = parent ( u ) , children ( u )
where each user has at most 1 parent and unlimited children.
TaxManager Contract (0x1002)
Computes tax and distributes burn/dev shares:
contract TaxManager {
// Compute tax
function computeTax ( uint256 amount )
external view returns (
uint256 taxAmount ,
uint256 burnShare ,
uint256 devShare
)
// Update tax rate ( governance )
function setTaxRate ( uint256 newRate ) external
// Update distribution ( governance )
function setBurnDevSplit ( uint256 burnPct , uint256 devPct )
external
}
Tax Distribution :
Tax = R × τ Burn = Tax × β Dev = Tax × ( 1 − β ) \begin{align*}
\text{Tax} &= R \times \tau \\
\text{Burn} &= \text{Tax} \times \beta \\
\text{Dev} &= \text{Tax} \times (1 - \beta)
\end{align*} Tax Burn Dev = R × τ = Tax × β = Tax × ( 1 − β )
Default: τ = 0.10 \tau = 0.10 τ = 0.10 , β = 0.50 \beta = 0.50 β = 0.50
RewardManager Contract (0x1003)
Stores dynamic reward configuration:
contract RewardManager {
uint256 public blockReward; // Slot 0
uint256 public pendingReward; // Slot 1
uint256 public pendingActivationEpoch; // Slot 2
// Update reward (governance)
function updateBlockReward ( uint256 newReward , uint256 activationEpoch )
external
}
Reward Transition :
R active ( E ) = { R current if E < E activation R pending if E ≥ E activation R_{\text{active}}(E) = \begin{cases}
R_{\text{current}} & \text{if } E < E_{\text{activation}} \\
R_{\text{pending}} & \text{if } E \geq E_{\text{activation}}
\end{cases} R active ( E ) = { R current R pending if E < E activation if E ≥ E activation
Gas Model
EIP-1559 Base Fee
Fenines implements London’s dynamic fee market:
BaseFee n + 1 = BaseFee n × ( 1 + 1 8 × G used − G target G target ) \text{BaseFee}_{n+1} = \text{BaseFee}_n \times \left(1 + \frac{1}{8} \times \frac{G_{\text{used}} - G_{\text{target}}}{G_{\text{target}}}\right) BaseFee n + 1 = BaseFee n × ( 1 + 8 1 × G target G used − G target )
Parameters :
Parameter Value Description Gas Target 15M Target gas per block Gas Limit 30M Maximum gas per block Elasticity 2x Max/target ratio Adjustment 12.5% Max change per block
Base Fee Burn
Portion of gas fees are burned:
Burned n = BaseFee n × G used \text{Burned}_n = \text{BaseFee}_n \times G_{\text{used}} Burned n = BaseFee n × G used
This creates deflationary pressure :
Supply n + 1 = Supply n + R block − Burned n \text{Supply}_{n+1} = \text{Supply}_n + R_{\text{block}} - \text{Burned}_n Supply n + 1 = Supply n + R block − Burned n
Network becomes deflationary when:
Burned > R block ⟺ BaseFee × G used > 1 FEN \text{Burned} > R_{\text{block}} \iff \text{BaseFee} \times G_{\text{used}} > 1 \text{ FEN} Burned > R block ⟺ BaseFee × G used > 1 FEN
Break-even Analysis :
BaseFee break-even = 1 0 18 G used \text{BaseFee}_{\text{break-even}} = \frac{10^{18}}{G_{\text{used}}} BaseFee break-even = G used 1 0 18
For G used = 15 M G_{\text{used}} = 15M G used = 15 M (target):
BaseFee break-even ≈ 66.67 gwei \text{BaseFee}_{\text{break-even}} \approx 66.67 \text{ gwei} BaseFee break-even ≈ 66.67 gwei
Gas Price Priority
Transaction ordering within a block follows:
Priority ( t x ) = min ( maxFeePerGas , maxPriorityFeePerGas + baseFee ) \text{Priority}(tx) = \min(\text{maxFeePerGas}, \text{maxPriorityFeePerGas} + \text{baseFee}) Priority ( t x ) = min ( maxFeePerGas , maxPriorityFeePerGas + baseFee )
Transactions sorted by priority descending.
System Transaction Execution
Exemption Rules
System transactions bypass standard checks:
Check User TX System TX Balance ≥ gasLimit × maxFee ✅ Required ❌ Exempt Nonce sequential ✅ Required ✅ Required Signature valid ✅ Required ✅ Required Gas limit ≤ block limit ✅ Required ✅ Required
Implementation :
func ( st * StateTransition ) preCheck () error {
// Skip balance check for system transactions
if st . isSystemTx () {
return nil
}
// Standard balance check
if st . state . GetBalance ( st . from ) < requiredBalance {
return ErrInsufficientFunds
}
return nil
}
Execution Order
At epoch blocks, transactions execute in strict order:
O exec = [ T X sys,1 , T X sys,2 , T X sys,3 , T X user,1 , … , T X user,n ] \mathcal{O}_{\text{exec}} = [\mathcal{TX}_{\text{sys,1}}, \mathcal{TX}_{\text{sys,2}}, \mathcal{TX}_{\text{sys,3}}, \mathcal{TX}_{\text{user,1}}, \ldots, \mathcal{TX}_{\text{user,n}}] O exec = [ T X sys,1 , T X sys,2 , T X sys,3 , T X user,1 , … , T X user,n ]
Ordering Guarantee :
∀ i < j : nonce ( T X i ) < nonce ( T X j ) \forall i < j: \quad \text{nonce}(\mathcal{TX}_i) < \text{nonce}(\mathcal{TX}_j) ∀ i < j : nonce ( T X i ) < nonce ( T X j )
State Commitment
After all transactions execute, state root computed:
StateRoot = Keccak256 ( MPT ( σ n ) ) \text{StateRoot} = \text{Keccak256}(\text{MPT}(\sigma_n)) StateRoot = Keccak256 ( MPT ( σ n ))
where MPT = Modified Patricia Merkle Trie.
State Storage Model
Account State
Each account stores:
type Account struct {
Nonce uint64 // Transaction counter
Balance * big . Int // FEN balance (wei)
Root common . Hash // Storage trie root
CodeHash common . Hash // Contract code hash
}
Account Hash :
H account = Keccak256 ( RLP ( nonce , balance , storageRoot , codeHash ) ) H_{\text{account}} = \text{Keccak256}(\text{RLP}(\text{nonce}, \text{balance}, \text{storageRoot}, \text{codeHash})) H account = Keccak256 ( RLP ( nonce , balance , storageRoot , codeHash ))
Storage Trie
Contract storage organized as:
Storage ( a d d r , k e y ) = MPT account [ Keccak256 ( k e y ) ] \text{Storage}(addr, key) = \text{MPT}_{\text{account}}\left[\text{Keccak256}(key)\right] Storage ( a dd r , k ey ) = MPT account [ Keccak256 ( k ey ) ]
Optimization : Leaf nodes compressed via RLP encoding.
World State Trie
Global state organized as:
StateRoot = MPT [ Keccak256 ( a d d r ) ↦ H account ] \text{StateRoot} = \text{MPT}\left[\text{Keccak256}(addr) \mapsto H_{\text{account}}\right] StateRoot = MPT [ Keccak256 ( a dd r ) ↦ H account ]
Depth : Maximum depth ≈ 64 \approx 64 ≈ 64 nibbles (32 bytes × 2).
Transaction Lifecycle
Submission
User submits signed transaction: const tx = {
to: "0x..." ,
value: parseEther ( "1.0" ),
data: "0x..." ,
maxFeePerGas: parseUnits ( "100" , "gwei" ),
maxPriorityFeePerGas: parseUnits ( "2" , "gwei" ),
gasLimit: 21000 ,
nonce: await signer . getTransactionCount ()
}
const signedTx = await signer . signTransaction ( tx )
Validation
Node validates: Signature valid ? Nonce = ? σ ( from ) . nonce Balance ≥ ? value + gasLimit × maxFee GasLimit ≤ ? BlockGasLimit \begin{align*}
\text{Signature} &\stackrel{?}{\text{valid}} \\
\text{Nonce} &\stackrel{?}{=} \sigma(\text{from}).\text{nonce} \\
\text{Balance} &\stackrel{?}{\geq} \text{value} + \text{gasLimit} \times \text{maxFee} \\
\text{GasLimit} &\stackrel{?}{\leq} \text{BlockGasLimit}
\end{align*} Signature Nonce Balance GasLimit valid ? = ? σ ( from ) . nonce ≥ ? value + gasLimit × maxFee ≤ ? BlockGasLimit
Mempool
Transaction enters pending pool: P pending ← P pending ∪ t x \mathcal{P}_{\text{pending}} \leftarrow \mathcal{P}_{\text{pending}} \cup \\{tx\\} P pending ← P pending ∪ t x Sorted by:
Nonce (ascending)
Priority fee (descending)
Inclusion
Validator selects transactions for next block: T n = arg max T ⊂ P l e f t ∑ t x ∈ T PriorityFee ( t x ) r i g h t T_n = \arg\max_{T \subset \mathcal{P}} \\left\\{ \sum_{tx \in T} \text{PriorityFee}(tx) \\right\\} T n = arg T ⊂ P max l e f t t x ∈ T ∑ PriorityFee ( t x ) r i g h t subject to: ∑ t x ∈ T GasLimit ( t x ) ≤ 30 M \sum_{tx \in T} \text{GasLimit}(tx) \leq 30M t x ∈ T ∑ GasLimit ( t x ) ≤ 30 M
Execution
EVM executes transaction: 1. Deduct gas: balance -= gasLimit × maxFee
2. Execute bytecode
3. Refund unused: balance += gasRefund × maxFee
4. Pay validator: validator += gasUsed × priorityFee
5. Burn base fee: supply -= gasUsed × baseFee
Receipt
Transaction receipt generated: type Receipt struct {
Status uint64 // 1 = success, 0 = revert
CumulativeGas uint64 // Total gas used in block
Logs [] Log // Event logs
Bloom Bloom // Log bloom filter
TransactionHash Hash // Transaction hash
}
Precompiled Contracts
Fenines inherits Ethereum’s precompiles:
Address Function Gas Cost 0x01ECRecover 3000 0x02SHA256 60 + 12/word 0x03RIPEMD160 600 + 120/word 0x04Identity 15 + 3/word 0x05ModExp Dynamic 0x06BN256Add 150 (Berlin) 0x07BN256Mul 6000 (Berlin) 0x08BN256Pairing 45000 + 34000/pair 0x09Blake2F Dynamic
Gas costs reflect Berlin hardfork pricing (active at genesis).
EVM Opcode Set
London-Active Opcodes
New opcodes enabled:
Opcode Name Gas Description 0x46BASEFEE 2 Get current base fee 0x3FEXTCODEHASH 100 Get code hash of account
Gas Costs (Berlin)
Cold vs warm storage access:
Gas SLOAD = { 2100 if cold access 100 if warm access \text{Gas}_{\text{SLOAD}} = \begin{cases}
2100 & \text{if cold access} \\
100 & \text{if warm access}
\end{cases} Gas SLOAD = { 2100 100 if cold access if warm access
Gas SSTORE = { 20000 if cold, 0→nonzero 2900 if warm, 0→nonzero 5000 if cold, nonzero→nonzero 100 if warm, nonzero→nonzero \text{Gas}_{\text{SSTORE}} = \begin{cases}
20000 & \text{if cold, 0→nonzero} \\
2900 & \text{if warm, 0→nonzero} \\
5000 & \text{if cold, nonzero→nonzero} \\
100 & \text{if warm, nonzero→nonzero}
\end{cases} Gas SSTORE = ⎩ ⎨ ⎧ 20000 2900 5000 100 if cold, 0→nonzero if warm, 0→nonzero if cold, nonzero→nonzero if warm, nonzero→nonzero
State Synchronization
Fast Sync
Nodes can sync via state snapshots:
Sync fast = Headers + StateSnap B recent + Receipts recent \text{Sync}_{\text{fast}} = \text{Headers} + \text{StateSnap}_{B_{\text{recent}}} + \text{Receipts}_{\text{recent}} Sync fast = Headers + StateSnap B recent + Receipts recent
Time Complexity :
T sync = O ( n log n ) T_{\text{sync}} = \mathcal{O}(n \log n) T sync = O ( n log n )
where n n n = number of accounts.
Snap Sync
Optimized sync protocol:
Download block headers
Download state snapshot (parallel)
Heal missing trie nodes
Verify state root
Bandwidth Reduction :
Data snap ≈ 20 % × Data full \text{Data}_{\text{snap}} \approx 20\% \times \text{Data}_{\text{full}} Data snap ≈ 20% × Data full
Security Considerations
Reentrancy Protection
System contracts use OpenZeppelin’s ReentrancyGuard:
modifier nonReentrant () {
require (_status != _ENTERED, "ReentrancyGuard: reentrant call" );
_status = _ENTERED;
_ ;
_status = _NOT_ENTERED;
}
Integer Overflow
Solidity 0.8+ has built-in overflow checks:
∀ a , b ∈ Z + : a + b < 2 256 \forall a, b \in \mathbb{Z}^+: \quad a + b < 2^{256} ∀ a , b ∈ Z + : a + b < 2 256
Otherwise reverts with Panic(0x11).
Access Control
Critical functions protected:
modifier onlyValidator () {
require ( msg.sender == block .coinbase, "Not validator" );
_ ;
}
modifier onlyGovernance () {
require ( msg.sender == governanceAddress, "Not governance" );
_ ;
}
State Caching
Hot paths cache frequently accessed storage:
Cache Capacity Purpose Account Cache 1000 Recent account states Storage Cache 10000 Recent storage slots Code Cache 1000 Contract bytecode
Hit Rate :
HitRate ≈ 85 - 95 % \text{HitRate} \approx 85\text{-}95\% HitRate ≈ 85 - 95%
for typical workloads.
Parallel Execution
Independent transactions execute in parallel:
Speedup = T sequential T parallel ≤ n 1 + α ( n − 1 ) \text{Speedup} = \frac{T_{\text{sequential}}}{T_{\text{parallel}}} \leq \frac{n}{1 + \alpha(n-1)} Speedup = T parallel T sequential ≤ 1 + α ( n − 1 ) n
where α \alpha α = serialization factor (Amdahl’s Law).
FenineSystem ABI Full contract specification
System Transactions Epoch boundary protocol
Gas Optimization Best practices for efficiency
Smart Contract Security Audit checklist and patterns