Skip to content

Byzantium Fork Rules #123

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 20, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ env:
global:
- PYTEST_ADDOPTS="-n 2"
matrix:
# byzantium goes first because it takes the longest.
- TOX_POSARGS="-e py35-state-byzantium"
- TOX_POSARGS="-e py35-state-frontier"
- TOX_POSARGS="-e py35-state-homestead"
- TOX_POSARGS="-e py35-state-eip150"
- TOX_POSARGS="-e py35-state-eip158"
#- TOX_POSARGS="-e py35-state-byzantium"
#- TOX_POSARGS="-e py35-state-constantinople"
#- TOX_POSARGS="-e py35-state-metropolis"
- TOX_POSARGS="-e py35-blockchain"
Expand Down
2 changes: 2 additions & 0 deletions evm/chains/mainnet/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
FrontierVM,
HomesteadVM,
SpuriousDragonVM,
ByzantiumVM,
)


Expand All @@ -17,6 +18,7 @@
(constants.HOMESTEAD_MAINNET_BLOCK, HomesteadVM),
(constants.EIP150_MAINNET_BLOCK, EIP150VM),
(constants.SPURIOUS_DRAGON_MAINNET_BLOCK, SpuriousDragonVM),
(constants.BYZANTIUM_MAINNET_BLOCK, ByzantiumVM),
)


Expand Down
16 changes: 16 additions & 0 deletions evm/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@

GAS_ECRECOVER = 3000

GAS_ECADD = 500
GAS_ECMUL = 40000

GAS_ECPAIRING_BASE = 100000
GAS_ECPAIRING_PER_POINT = 80000


#
# Gas Limit
Expand All @@ -111,6 +117,7 @@

FRONTIER_DIFFICULTY_ADJUSTMENT_CUTOFF = 13
HOMESTEAD_DIFFICULTY_ADJUSTMENT_CUTOFF = 10
BYZANTIUM_DIFFICULTY_ADJUSTMENT_CUTOFF = 9

BOMB_EXPONENTIAL_PERIOD = 100000
BOMB_EXPONENTIAL_FREE_PERIODS = 2
Expand All @@ -119,6 +126,7 @@
# Mining
#
BLOCK_REWARD = 5 * denoms.ether
EIP649_BLOCK_REWARD = 3 * denoms.ether

UNCLE_DEPTH_PENALTY_FACTOR = 8
MAX_UNCLE_DEPTH = 6
Expand Down Expand Up @@ -193,3 +201,11 @@

# https://github.com/ethereum/EIPs/issues/170
EIP170_CODE_SIZE_LIMIT = 24577

#
# ByzantiumVM
#
BYZANTIUM_MAINNET_BLOCK = 4370000
BYZANTIUM_ROPSTEN_BLOCK = 1700000
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BYZANTIUM_ROPSTEN_BLOCK is not used anywhere. Should it be in RopstenChain?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'm thinking it makes sense to migration the various fork-and-chain-specific block numbers into the fork or chain modules themselves.


GAS_MOD_EXP_QUADRATIC_DENOMINATOR = 20
27 changes: 26 additions & 1 deletion evm/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ class VMError(PyEVMError):
"""
Class of errors which can be raised during VM execution.
"""
pass
burns_gas = True
zeros_return_data = True


class OutOfGas(VMError):
Expand Down Expand Up @@ -109,3 +110,27 @@ class ContractCreationCollision(VMError):
Error signaling that there was an address collision during contract creation.
"""
pass


class Revert(VMError):
"""
Error used by the REVERT opcode
"""
burns_gas = False
zeros_return_data = False


class WriteProtection(VMError):
"""
Error raised if an attempt to modify the state database is made while
operating inside of a STATICCALL context.
"""
pass


class OutOfBoundsRead(VMError):
"""
Error raised to indicate an attempt was made to read data beyond the
boundaries of the buffer (such as with RETURNDATACOPY)
"""
pass
54 changes: 52 additions & 2 deletions evm/logic/call.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from evm.exceptions import (
OutOfGas,
WriteProtection,
)
from evm.opcode import (
Opcode,
Expand Down Expand Up @@ -42,6 +43,7 @@ def __call__(self, computation):
memory_output_start_position,
memory_output_size,
should_transfer_value,
is_static,
) = self.get_call_params(computation)

computation.extend_memory(memory_input_start_position, memory_input_size)
Expand All @@ -62,6 +64,7 @@ def __call__(self, computation):
stack_too_deep = computation.msg.depth + 1 > constants.STACK_DEPTH_LIMIT

if insufficient_funds or stack_too_deep:
computation.return_data = b''
if insufficient_funds:
err_message = "Insufficient Funds: have: {0} | need: {1}".format(
sender_balance,
Expand Down Expand Up @@ -94,6 +97,7 @@ def __call__(self, computation):
'code': code,
'code_address': code_address,
'should_transfer_value': should_transfer_value,
'is_static': is_static,
}
if sender is not None:
child_msg_kwargs['sender'] = sender
Expand All @@ -105,14 +109,18 @@ def __call__(self, computation):
if child_computation.error:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it be better to explicitly check for None here instead?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so or I'm not understanding your suggestion.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant if child_computation.error is not None:, just in case at some point we have an error class that for some reason implements __bool__() and returns False there, this would no longer work as expected. I can't think why we'd have an error class that would do that, but in general I prefer to explicitly check for None

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 I'm going to file this away in a "Good First Issue" ticket.

computation.stack.push(0)
else:
computation.stack.push(1)

if not child_computation.error or not child_computation.error.zeros_return_data:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it'd make sense to abstract this into a method on Computation, so that we don't need to reach so many levels down into the exception's attributes. Something like Computation.should_return_data()?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 but going to do this as part of a "good first issue" ticket

actual_output_size = min(memory_output_size, len(child_computation.output))
computation.gas_meter.return_gas(child_computation.gas_meter.gas_remaining)
computation.memory.write(
memory_output_start_position,
actual_output_size,
child_computation.output[:actual_output_size],
)
computation.stack.push(1)

if not child_computation.error or not child_computation.error.burns_gas:
computation.gas_meter.return_gas(child_computation.gas_meter.gas_remaining)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two statements could maybe go into a Computation.maybe_return_gas() method?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 but I'm going to add this as a "good first issue" (previously easy-pickings) ticket.



class Call(BaseCall):
Expand Down Expand Up @@ -146,6 +154,7 @@ def get_call_params(self, computation):
memory_output_start_position,
memory_output_size,
True, # should_transfer_value,
computation.msg.is_static,
)


Expand Down Expand Up @@ -179,6 +188,7 @@ def get_call_params(self, computation):
memory_output_start_position,
memory_output_size,
True, # should_transfer_value,
computation.msg.is_static,
)


Expand Down Expand Up @@ -215,6 +225,7 @@ def get_call_params(self, computation):
memory_output_start_position,
memory_output_size,
False, # should_transfer_value,
computation.msg.is_static,
)


Expand Down Expand Up @@ -279,3 +290,42 @@ def compute_msg_extra_gas(self, computation, gas, to, value):
transfer_gas_fee = constants.GAS_CALLVALUE if value else 0
create_gas_fee = constants.GAS_NEWACCOUNT if (account_is_dead and value) else 0
return transfer_gas_fee + create_gas_fee


#
# Byzantium
#
class StaticCall(CallEIP161):
def get_call_params(self, computation):
gas = computation.stack.pop(type_hint=constants.UINT256)
to = force_bytes_to_address(computation.stack.pop(type_hint=constants.BYTES))

(
memory_input_start_position,
memory_input_size,
memory_output_start_position,
memory_output_size,
) = computation.stack.pop(num_items=4, type_hint=constants.UINT256)

return (
gas,
0, # value
to,
None, # sender
None, # code_address
memory_input_start_position,
memory_input_size,
memory_output_start_position,
memory_output_size,
False, # should_transfer_value,
True, # is_static
)


class CallByzantium(CallEIP161):
def get_call_params(self, computation):
call_params = super(CallByzantium, self).get_call_params(computation)
value = call_params[1]
if computation.msg.is_static and value != 0:
raise WriteProtection("Cannot modify state while inside of a STATICCALL context")
return call_params
38 changes: 38 additions & 0 deletions evm/logic/context.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from evm import constants
from evm.exceptions import (
OutOfBoundsRead,
)

from evm.utils.address import (
force_bytes_to_address,
Expand Down Expand Up @@ -138,3 +141,38 @@ def extcodecopy(computation):
padded_code_bytes = pad_right(code_bytes, size, b'\x00')

computation.memory.write(mem_start_position, size, padded_code_bytes)


def returndatasize(computation):
size = len(computation.return_data)
computation.stack.push(size)


def returndatacopy(computation):
(
mem_start_position,
returndata_start_position,
size,
) = computation.stack.pop(num_items=3, type_hint=constants.UINT256)

if returndata_start_position + size > len(computation.return_data):
raise OutOfBoundsRead(
"Return data length is not sufficient to satisfy request. Asked "
"for data from index {0} to {1}. Return data is {2} bytes in "
"length.".format(
returndata_start_position,
returndata_start_position + size,
len(computation.return_data),
)
)

computation.extend_memory(mem_start_position, size)

word_count = ceil32(size) // 32
copy_gas_cost = word_count * constants.GAS_COPY

computation.gas_meter.consume_gas(copy_gas_cost, reason="RETURNDATACOPY fee")

value = computation.return_data[returndata_start_position: returndata_start_position + size]

computation.memory.write(mem_start_position, size, value)
21 changes: 20 additions & 1 deletion evm/logic/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
from evm import mnemonics
from evm.exceptions import (
Halt,
Revert,
WriteProtection,
)

from evm.opcode import (
Expand All @@ -28,6 +30,16 @@ def return_op(computation):
raise Halt('RETURN')


def revert(computation):
start_position, size = computation.stack.pop(num_items=2, type_hint=constants.UINT256)

computation.extend_memory(start_position, size)

output = computation.memory.read(start_position, size)
computation.output = bytes(output)
raise Revert(computation.output)


def selfdestruct(computation):
beneficiary = force_bytes_to_address(computation.stack.pop(type_hint=constants.BYTES))
_selfdestruct(computation, beneficiary)
Expand Down Expand Up @@ -147,10 +159,17 @@ def __call__(self, computation):
if child_computation.error:
computation.stack.push(0)
else:
computation.gas_meter.return_gas(child_computation.gas_meter.gas_remaining)
computation.stack.push(contract_address)
computation.gas_meter.return_gas(child_computation.gas_meter.gas_remaining)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this change necessary?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The previous code with this nested into the else block was just an optimization since the amount of gas to return in the case of an error was always zero. Now with the advent of revert, we should always return whatever gas is leftover.



class CreateEIP150(Create):
def max_child_gas_modifier(self, gas):
return max_child_gas_eip150(gas)


class CreateByzantium(CreateEIP150):
def __call__(self, computation):
if computation.msg.is_static:
raise WriteProtection("Cannot modify state while inside of a STATICCALL context")
return super(CreateEIP150, self).__call__(computation)
4 changes: 4 additions & 0 deletions evm/mnemonics.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@
GASPRICE = 'GASPRICE'
EXTCODESIZE = 'EXTCODESIZE'
EXTCODECOPY = 'EXTCODECOPY'
RETURNDATASIZE = 'RETURNDATASIZE'
RETURNDATACOPY = 'RETURNDATACOPY'
#
# Block Information
#
Expand All @@ -71,6 +73,7 @@
MSIZE = 'MSIZE'
GAS = 'GAS'
JUMPDEST = 'JUMPDEST'
REVERT = 'REVERT'
#
# Push Operations
#
Expand Down Expand Up @@ -158,6 +161,7 @@
CREATE = 'CREATE'
CALL = 'CALL'
CALLCODE = 'CALLCODE'
STATICCALL = 'STATICCALL'
RETURN = 'RETURN'
DELEGATECALL = 'DELEGATECALL'
SELFDESTRUCT = 'SELFDESTRUCT'
4 changes: 4 additions & 0 deletions evm/opcode_values.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@
GASPRICE = 0x3a
EXTCODESIZE = 0x3b
EXTCODECOPY = 0x3c
RETURNDATASIZE = 0x3d
RETURNDATACOPY = 0x3e


#
Expand Down Expand Up @@ -181,4 +183,6 @@
CALLCODE = 0xf2
RETURN = 0xf3
DELEGATECALL = 0xf4
STATICCALL = 0xfa
REVERT = 0xfd
SELFDESTRUCT = 0xff
4 changes: 4 additions & 0 deletions evm/precompiles/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@
from .identity import identity # noqa: F401
from .ecrecover import ecrecover # noqa: F401
from .ripemd160 import ripemd160 # noqa: F401
from .modexp import modexp # noqa: F401
from .ecadd import ecadd # noqa: F401
from .ecmul import ecmul # noqa: F401
from .ecpairing import ecpairing # noqa: F401
Loading