Ethereum State Transition Function
The Ethereum state transition function, APPLY(S,TX) -> S'
can be defined as follows:
- Check if the transaction is well-formed (ie. has the right number of values), the signature is valid, and the nonce matches the nonce in the sender’s account. If not, return an error.
- Calculate the transaction fee as
STARTGAS * GASPRICE
, and determine the sending address from the signature. Subtract the fee from the sender’s account balance and increment the sender’s nonce. If there is not enough balance to spend, return an error. - Initialize
GAS = STARTGAS
, and take off a certain quantity of gas per byte to pay for the bytes in the transaction. - Transfer the transaction value from the sender’s account to the receiving account. If the receiving account does not yet exist, create it. If the receiving account is a contract, run the contract’s code either to completion or until the execution runs out of gas.
- If the value transfer failed because the sender did not have enough money, or the code execution ran out of gas, revert all state changes except the payment of the fees, and add the fees to the miner’s account.
- Otherwise, refund the fees for all remaining gas to the sender, and send the fees paid for gas consumed to the miner.
For example, suppose that the contract’s code is:
if !self.storage[calldataload(0)]:
self.storage[calldataload(0)] = calldataload(32)
Note that in reality the contract code is written in the low-level EVM code; this example is written in Serpent, one of our high-level languages, for clarity, and can be compiled down to EVM code. Suppose that the contract’s storage starts off empty, and a transaction is sent with 10 ether value, 2000 gas, 0.001 ether gasprice, and 64 bytes of data, with bytes 0-31 representing the number 2
and bytes 32-63 representing the string CHARLIE
.[Note 4] The process for the state transition function in this case is as follows:
- Check that the transaction is valid and well formed.
- Check that the transaction sender has at least 2000 * 0.001 = 2 ether. If they do, then subtract 2 ether from the sender’s account.
- Initialize gas = 2000; assuming the transaction is 170 bytes long and the byte-fee is 5, subtract 850 so that there is 1150 gas left.
- Subtract 10 more ether from the sender’s account, and add it to the contract’s account.
- Run the code. In this case, this is simple: it checks if the contract’s storage at index
2
is used[Note 5], notices that it is not, and so it sets the storage at index2
to the valueCHARLIE
. Suppose this takes 187 gas, so the remaining amount of gas is 1150 - 187 = 963. - Add 963 * 0.001 = 0.963 ether back to the sender’s account, and return the resulting state.
If there was no contract at the receiving end of the transaction, then the total transaction fee would simply be equal to the provided GASPRICE
multiplied by the length of the transaction in bytes, and the data sent alongside the transaction would be irrelevant.
Note that messages work equivalently to transactions in terms of reverts: if a message execution runs out of gas, then that message’s execution, and all other executions triggered by that execution, revert, but parent executions do not need to revert. This means that it is “safe” for a contract to call another contract, as if A calls B with G gas then A’s execution is guaranteed to lose at most G gas. Finally, note that there is an opcode, CREATE
, that creates a contract; its execution mechanics are generally similar to CALL
, with the exception that the output of the execution determines the code of a newly created contract.