Skip to content

Commit

Permalink
fix(EVM): Store unpadded EVM bytecode length in versioned bytecode ha…
Browse files Browse the repository at this point in the history
…sh (#1066)
  • Loading branch information
0xVolosnikov authored Dec 16, 2024
1 parent daf7022 commit 93b77a0
Show file tree
Hide file tree
Showing 11 changed files with 127 additions and 153 deletions.
22 changes: 14 additions & 8 deletions system-contracts/contracts/ContractDeployer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,12 @@ contract ContractDeployer is IContractDeployer, SystemContractBase {
address _newAddress,
bytes calldata _initCode
) external payable onlySystemCallFromEvmEmulator returns (uint256, address) {
uint256 constructorReturnEvmGas = _performDeployOnAddressEVM(msg.sender, _newAddress, AccountAbstractionVersion.None, _initCode);
uint256 constructorReturnEvmGas = _performDeployOnAddressEVM(
msg.sender,
_newAddress,
AccountAbstractionVersion.None,
_initCode
);
return (constructorReturnEvmGas, _newAddress);
}

Expand Down Expand Up @@ -591,25 +596,26 @@ contract ContractDeployer is IContractDeployer, SystemContractBase {
_isSystem: false
});

// Returned data bytes have structure: bytecode.constructorReturnEvmGas
uint256 evmBytecodeLen;
// Returned data bytes have structure: paddedBytecode.evmBytecodeLen.constructorReturnEvmGas
assembly {
let dataLen := mload(paddedBytecode)
evmBytecodeLen := mload(add(paddedBytecode, sub(dataLen, 0x20)))
constructorReturnEvmGas := mload(add(paddedBytecode, dataLen))
mstore(paddedBytecode, sub(dataLen, 0x20)) // shrink paddedBytecode
mstore(paddedBytecode, sub(dataLen, 0x40)) // shrink paddedBytecode
}

bytes32 versionedCodeHash = KNOWN_CODE_STORAGE_CONTRACT.publishEVMBytecode(paddedBytecode);
ACCOUNT_CODE_STORAGE_SYSTEM_CONTRACT.storeAccountConstructedCodeHash(_newAddress, versionedCodeHash);
bytes32 versionedBytecodeHash = KNOWN_CODE_STORAGE_CONTRACT.publishEVMBytecode(evmBytecodeLen, paddedBytecode);
ACCOUNT_CODE_STORAGE_SYSTEM_CONTRACT.storeAccountConstructedCodeHash(_newAddress, versionedBytecodeHash);

bytes32 evmBytecodeHash;
assembly {
let bytecodeLen := mload(add(paddedBytecode, 0x20))
evmBytecodeHash := keccak256(add(paddedBytecode, 0x40), bytecodeLen)
evmBytecodeHash := keccak256(add(paddedBytecode, 0x20), evmBytecodeLen)
}

_setEvmCodeHash(_newAddress, evmBytecodeHash);

emit ContractDeployed(_sender, versionedCodeHash, _newAddress);
emit ContractDeployed(_sender, versionedBytecodeHash, _newAddress);
}

function _setEvmCodeHash(address _address, bytes32 _hash) internal {
Expand Down
127 changes: 46 additions & 81 deletions system-contracts/contracts/EvmEmulator.yul
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,14 @@ object "EvmEmulator" {
copyActivePtrData(BYTECODE_OFFSET(), 0, size)
}

function padBytecode(offset, len) -> blobOffset, blobLen {
blobOffset := sub(offset, 32)
function padBytecode(offset, len) -> blobLen {
let trueLastByte := add(offset, len)

mstore(blobOffset, len)
// clearing out additional bytes
mstore(trueLastByte, 0)
mstore(add(trueLastByte, 32), 0)

blobLen := add(len, 32)
blobLen := len

if iszero(eq(mod(blobLen, 32), 0)) {
blobLen := add(blobLen, sub(32, mod(blobLen, 32)))
Expand Down Expand Up @@ -411,61 +409,36 @@ object "EvmEmulator" {

// Basically performs an extcodecopy, while returning the length of the copied bytecode.
function fetchDeployedCode(addr, dstOffset, srcOffset, len) -> copiedLen {
let codeHash := getRawCodeHash(addr)
mstore(0, codeHash)
let rawCodeHash := getRawCodeHash(addr)
mstore(0, rawCodeHash)

let success := staticcall(gas(), CODE_ORACLE_SYSTEM_CONTRACT(), 0, 32, 0, 0)
// it fails if we don't have any code deployed at this address
if success {
returndatacopy(0, 0, 32)
// The first word of returndata is the true length of the bytecode
let codeLen := mload(0)
// The true length of the bytecode is encoded in bytecode hash
let codeLen := and(shr(224, rawCodeHash), 0xffff)

if gt(len, codeLen) {
len := codeLen
}

let shiftedSrcOffset := add(32, srcOffset) // first 32 bytes is length

let _returndatasize := returndatasize()
if gt(shiftedSrcOffset, _returndatasize) {
shiftedSrcOffset := _returndatasize
if gt(srcOffset, _returndatasize) {
srcOffset := _returndatasize
}

if gt(add(len, shiftedSrcOffset), _returndatasize) {
len := sub(_returndatasize, shiftedSrcOffset)
if gt(add(len, srcOffset), _returndatasize) {
len := sub(_returndatasize, srcOffset)
}

if len {
returndatacopy(dstOffset, shiftedSrcOffset, len)
returndatacopy(dstOffset, srcOffset, len)
}

copiedLen := len
}
}

// Returns the length of the EVM bytecode.
function fetchDeployedEvmCodeLen(addr) -> codeLen {
let codeHash := getRawCodeHash(addr)
mstore(0, codeHash)

let success := staticcall(gas(), CODE_ORACLE_SYSTEM_CONTRACT(), 0, 32, 0, 0)

switch iszero(success)
case 1 {
// The code oracle call can only fail in the case where the contract
// we are querying is the current one executing and it has not yet been
// deployed, i.e., if someone calls codesize (or extcodesize(address()))
// inside the constructor. In that case, code length is zero.
codeLen := 0
}
default {
// The first word is the true length of the bytecode
returndatacopy(0, 0, 32)
codeLen := mload(0)
}
}

function getMax(a, b) -> max {
max := b
if gt(a, b) {
Expand Down Expand Up @@ -1810,9 +1783,17 @@ object "EvmEmulator" {
evmGasLeft := chargeGas(evmGasLeft, 2500)
}

switch isEvmContract(addr)
case 0 { stackHead := extcodesize(addr) }
default { stackHead := fetchDeployedEvmCodeLen(addr) }
let rawCodeHash := getRawCodeHash(addr)
switch shr(248, rawCodeHash)
case 1 {
stackHead := extcodesize(addr)
}
case 2 {
stackHead := and(shr(224, rawCodeHash), 0xffff)
}
default {
stackHead := 0
}

ip := add(ip, 1)
}
Expand Down Expand Up @@ -3176,11 +3157,12 @@ object "EvmEmulator" {

gasToReturn := validateBytecodeAndChargeGas(offset, len, gasToReturn)

offset, len := padBytecode(offset, len)
let blobLen := padBytecode(offset, len)

mstore(add(offset, len), gasToReturn)
mstore(add(offset, blobLen), len)
mstore(add(offset, add(32, blobLen)), gasToReturn)

verbatim_2i_0o("return_deployed", offset, add(len, 32))
verbatim_2i_0o("return_deployed", offset, add(blobLen, 64))
}
object "EvmEmulator_deployed" {
code {
Expand Down Expand Up @@ -3551,61 +3533,36 @@ object "EvmEmulator" {

// Basically performs an extcodecopy, while returning the length of the copied bytecode.
function fetchDeployedCode(addr, dstOffset, srcOffset, len) -> copiedLen {
let codeHash := getRawCodeHash(addr)
mstore(0, codeHash)
let rawCodeHash := getRawCodeHash(addr)
mstore(0, rawCodeHash)

let success := staticcall(gas(), CODE_ORACLE_SYSTEM_CONTRACT(), 0, 32, 0, 0)
// it fails if we don't have any code deployed at this address
if success {
returndatacopy(0, 0, 32)
// The first word of returndata is the true length of the bytecode
let codeLen := mload(0)
// The true length of the bytecode is encoded in bytecode hash
let codeLen := and(shr(224, rawCodeHash), 0xffff)

if gt(len, codeLen) {
len := codeLen
}

let shiftedSrcOffset := add(32, srcOffset) // first 32 bytes is length

let _returndatasize := returndatasize()
if gt(shiftedSrcOffset, _returndatasize) {
shiftedSrcOffset := _returndatasize
if gt(srcOffset, _returndatasize) {
srcOffset := _returndatasize
}

if gt(add(len, shiftedSrcOffset), _returndatasize) {
len := sub(_returndatasize, shiftedSrcOffset)
if gt(add(len, srcOffset), _returndatasize) {
len := sub(_returndatasize, srcOffset)
}

if len {
returndatacopy(dstOffset, shiftedSrcOffset, len)
returndatacopy(dstOffset, srcOffset, len)
}

copiedLen := len
}
}

// Returns the length of the EVM bytecode.
function fetchDeployedEvmCodeLen(addr) -> codeLen {
let codeHash := getRawCodeHash(addr)
mstore(0, codeHash)

let success := staticcall(gas(), CODE_ORACLE_SYSTEM_CONTRACT(), 0, 32, 0, 0)

switch iszero(success)
case 1 {
// The code oracle call can only fail in the case where the contract
// we are querying is the current one executing and it has not yet been
// deployed, i.e., if someone calls codesize (or extcodesize(address()))
// inside the constructor. In that case, code length is zero.
codeLen := 0
}
default {
// The first word is the true length of the bytecode
returndatacopy(0, 0, 32)
codeLen := mload(0)
}
}

function getMax(a, b) -> max {
max := b
if gt(a, b) {
Expand Down Expand Up @@ -4950,9 +4907,17 @@ object "EvmEmulator" {
evmGasLeft := chargeGas(evmGasLeft, 2500)
}

switch isEvmContract(addr)
case 0 { stackHead := extcodesize(addr) }
default { stackHead := fetchDeployedEvmCodeLen(addr) }
let rawCodeHash := getRawCodeHash(addr)
switch shr(248, rawCodeHash)
case 1 {
stackHead := extcodesize(addr)
}
case 2 {
stackHead := and(shr(224, rawCodeHash), 0xffff)
}
default {
stackHead := 0
}

ip := add(ip, 1)
}
Expand Down
4 changes: 3 additions & 1 deletion system-contracts/contracts/KnownCodesStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,13 @@ contract KnownCodesStorage is IKnownCodesStorage, SystemContractBase {

/// @notice The method used by ContractDeployer to publish EVM bytecode
/// @dev Bytecode should be padded by EraVM rules
/// @param paddedBytecode The length of EVM bytecode in bytes
/// @param paddedBytecode The bytecode to be published
function publishEVMBytecode(
uint256 evmBytecodeLen,
bytes calldata paddedBytecode
) external payable onlyCallFrom(address(DEPLOYER_SYSTEM_CONTRACT)) returns (bytes32) {
bytes32 vesionedBytecodeHash = Utils.hashEVMBytecode(paddedBytecode);
bytes32 vesionedBytecodeHash = Utils.hashEVMBytecode(evmBytecodeLen, paddedBytecode);

if (getMarker(vesionedBytecodeHash) == 0) {
L1_MESSENGER_CONTRACT.sendToL1(paddedBytecode);
Expand Down
3 changes: 2 additions & 1 deletion system-contracts/contracts/SystemContractErrors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -145,5 +145,6 @@ enum BytecodeError {
Length,
WordsMustBeOdd,
DictionaryLength,
EvmBytecodeLength
EvmBytecodeLength,
EvmBytecodeLengthTooBig
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ interface IKnownCodesStorage {

function getMarker(bytes32 _hash) external view returns (uint256);

function publishEVMBytecode(bytes calldata bytecode) external payable returns (bytes32);
function publishEVMBytecode(uint256 evmBytecodeLen, bytes calldata bytecode) external payable returns (bytes32);
}
22 changes: 15 additions & 7 deletions system-contracts/contracts/libraries/Utils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -138,35 +138,43 @@ library Utils {
uint256 internal constant MAX_EVM_BYTECODE_LENGTH = (2 ** 16) - 1;

/// @notice Validate the bytecode format and calculate its hash.
/// @param _bytecode The EVM bytecode to hash.
/// @param _evmBytecodeLen The length of original EVM bytecode in bytes
/// @param _paddedBytecode The padded EVM bytecode to hash.
/// @return hashedEVMBytecode The 32-byte hash of the EVM bytecode.
/// Note: The function reverts the execution if the bytecode has non expected format:
/// - Bytecode bytes length is not a multiple of 32
/// - Bytecode bytes length is greater than 2^16 - 1 bytes
/// - Bytecode words length is not odd
function hashEVMBytecode(bytes calldata _bytecode) internal view returns (bytes32 hashedEVMBytecode) {
function hashEVMBytecode(
uint256 _evmBytecodeLen,
bytes calldata _paddedBytecode
) internal view returns (bytes32 hashedEVMBytecode) {
// Note that the length of the bytecode must be provided in 32-byte words.
if (_bytecode.length % 32 != 0) {
if (_paddedBytecode.length % 32 != 0) {
revert MalformedBytecode(BytecodeError.Length);
}

if (_bytecode.length > MAX_EVM_BYTECODE_LENGTH) {
if (_evmBytecodeLen > _paddedBytecode.length) {
revert MalformedBytecode(BytecodeError.EvmBytecodeLength);
}

uint256 lengthInWords = _bytecode.length / 32;
if (_evmBytecodeLen > MAX_EVM_BYTECODE_LENGTH) {
revert MalformedBytecode(BytecodeError.EvmBytecodeLengthTooBig);
}

uint256 lengthInWords = _paddedBytecode.length / 32;
// bytecode length in words must be odd
if (lengthInWords % 2 == 0) {
revert MalformedBytecode(BytecodeError.WordsMustBeOdd);
}

hashedEVMBytecode =
EfficientCall.sha(_bytecode) &
EfficientCall.sha(_paddedBytecode) &
0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;

// Setting the version of the hash
hashedEVMBytecode = (hashedEVMBytecode | bytes32(uint256(EVM_BYTECODE_FLAG) << 248));
hashedEVMBytecode = hashedEVMBytecode | bytes32(_bytecode.length << 224);
hashedEVMBytecode = hashedEVMBytecode | bytes32(_evmBytecodeLen << 224);
}

/// @notice Calculates the address of a deployed contract via create2 on the EVM
Expand Down
19 changes: 15 additions & 4 deletions system-contracts/contracts/precompiles/CodeOracle.yul
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,19 @@ object "CodeOracle" {
return(0, lenInBytes)
}

function paddedBytecodeLen(len) -> blobLen {
blobLen := len

if iszero(eq(mod(blobLen, 32), 0)) {
blobLen := add(blobLen, sub(32, mod(blobLen, 32)))
}

// Now it is divisible by 32, but we must make sure that the number of 32 byte words is odd
if iszero(eq(mod(blobLen, 64), 32)) {
blobLen := add(blobLen, 32)
}
}

////////////////////////////////////////////////////////////////
// FALLBACK
////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -147,12 +160,10 @@ object "CodeOracle" {
decommit(versionedCodeHash, lengthInWords)
}
case 2 {
// We do not double check whether the length is 32 mod 64, since it assumed that only valid bytecodes
// can pass the `isCodeHashKnown` check.
let lengthInBytes := and(shr(224, versionedCodeHash), 0xffff)
let paddedLengthInBytes := paddedBytecodeLen(lengthInBytes)

// It is assumed that the `lengthInBytes` is divisible by 32.
decommit(versionedCodeHash, div(lengthInBytes, 32))
decommit(versionedCodeHash, div(paddedLengthInBytes, 32))
}
default {
// Unsupported
Expand Down
Loading

0 comments on commit 93b77a0

Please sign in to comment.