Skip to content
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

fix(EVM): Store unpadded EVM bytecode length in versioned bytecode hash #1066

Merged
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
22 changes: 14 additions & 8 deletions system-contracts/contracts/ContractDeployer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@
function createEVM(bytes calldata _initCode) external payable override onlySystemCall returns (uint256, address) {
uint256 senderNonce;
// If the account is an EOA, use the min nonce. If it's a contract, use deployment nonce
if (msg.sender == tx.origin) {

Check warning on line 184 in system-contracts/contracts/ContractDeployer.sol

View workflow job for this annotation

GitHub Actions / lint

Avoid to use tx.origin

Check warning on line 184 in system-contracts/contracts/ContractDeployer.sol

View workflow job for this annotation

GitHub Actions / lint

Avoid to use tx.origin

Check warning on line 184 in system-contracts/contracts/ContractDeployer.sol

View workflow job for this annotation

GitHub Actions / lint

Avoid to use tx.origin
// Subtract 1 for EOA since the nonce has already been incremented for this transaction
senderNonce = NONCE_HOLDER_SYSTEM_CONTRACT.getMinNonce(msg.sender) - 1;
} else {
Expand Down Expand Up @@ -250,7 +250,12 @@
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 @@
_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
Loading