diff --git a/system-contracts/contracts/ContractDeployer.sol b/system-contracts/contracts/ContractDeployer.sol index 3b36ed732..9796b40d3 100644 --- a/system-contracts/contracts/ContractDeployer.sol +++ b/system-contracts/contracts/ContractDeployer.sol @@ -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); } @@ -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 { diff --git a/system-contracts/contracts/EvmEmulator.yul b/system-contracts/contracts/EvmEmulator.yul index ea8b65d34..52b579962 100644 --- a/system-contracts/contracts/EvmEmulator.yul +++ b/system-contracts/contracts/EvmEmulator.yul @@ -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))) @@ -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) { @@ -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) } @@ -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 { @@ -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) { @@ -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) } diff --git a/system-contracts/contracts/KnownCodesStorage.sol b/system-contracts/contracts/KnownCodesStorage.sol index 1f3755e4e..463105a64 100644 --- a/system-contracts/contracts/KnownCodesStorage.sol +++ b/system-contracts/contracts/KnownCodesStorage.sol @@ -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); diff --git a/system-contracts/contracts/SystemContractErrors.sol b/system-contracts/contracts/SystemContractErrors.sol index 3f0f8c257..002069396 100644 --- a/system-contracts/contracts/SystemContractErrors.sol +++ b/system-contracts/contracts/SystemContractErrors.sol @@ -145,5 +145,6 @@ enum BytecodeError { Length, WordsMustBeOdd, DictionaryLength, - EvmBytecodeLength + EvmBytecodeLength, + EvmBytecodeLengthTooBig } diff --git a/system-contracts/contracts/interfaces/IKnownCodesStorage.sol b/system-contracts/contracts/interfaces/IKnownCodesStorage.sol index 984f203aa..fc019be93 100644 --- a/system-contracts/contracts/interfaces/IKnownCodesStorage.sol +++ b/system-contracts/contracts/interfaces/IKnownCodesStorage.sol @@ -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); } diff --git a/system-contracts/contracts/libraries/Utils.sol b/system-contracts/contracts/libraries/Utils.sol index bc423c885..94a615da0 100644 --- a/system-contracts/contracts/libraries/Utils.sol +++ b/system-contracts/contracts/libraries/Utils.sol @@ -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 diff --git a/system-contracts/contracts/precompiles/CodeOracle.yul b/system-contracts/contracts/precompiles/CodeOracle.yul index cb12af19f..87145d3be 100644 --- a/system-contracts/contracts/precompiles/CodeOracle.yul +++ b/system-contracts/contracts/precompiles/CodeOracle.yul @@ -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 //////////////////////////////////////////////////////////////// @@ -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 diff --git a/system-contracts/evm-emulator/EvmEmulator.template.yul b/system-contracts/evm-emulator/EvmEmulator.template.yul index 247cfd786..8e1a4b813 100644 --- a/system-contracts/evm-emulator/EvmEmulator.template.yul +++ b/system-contracts/evm-emulator/EvmEmulator.template.yul @@ -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))) @@ -98,11 +96,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 { diff --git a/system-contracts/evm-emulator/EvmEmulatorFunctions.template.yul b/system-contracts/evm-emulator/EvmEmulatorFunctions.template.yul index 50089c79d..be1e0f1d3 100644 --- a/system-contracts/evm-emulator/EvmEmulatorFunctions.template.yul +++ b/system-contracts/evm-emulator/EvmEmulatorFunctions.template.yul @@ -349,61 +349,36 @@ function isHashOfConstructedEvmContract(rawCodeHash) -> isConstructedEVM { // 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) { diff --git a/system-contracts/evm-emulator/EvmEmulatorLoop.template.yul b/system-contracts/evm-emulator/EvmEmulatorLoop.template.yul index b5d956b44..1c98a9acb 100644 --- a/system-contracts/evm-emulator/EvmEmulatorLoop.template.yul +++ b/system-contracts/evm-emulator/EvmEmulatorLoop.template.yul @@ -468,9 +468,17 @@ for { } true { } { 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) } diff --git a/system-contracts/test/KnownCodesStorage.spec.ts b/system-contracts/test/KnownCodesStorage.spec.ts index 395249eec..5444c519d 100644 --- a/system-contracts/test/KnownCodesStorage.spec.ts +++ b/system-contracts/test/KnownCodesStorage.spec.ts @@ -96,7 +96,7 @@ describe("KnownCodesStorage tests", function () { }); it("non-deployer failed to call", async () => { - await expect(knownCodesStorage.publishEVMBytecode("0x00")).to.be.revertedWithCustomError( + await expect(knownCodesStorage.publishEVMBytecode(1, "0x00")).to.be.revertedWithCustomError( knownCodesStorage, "Unauthorized" ); @@ -104,15 +104,14 @@ describe("KnownCodesStorage tests", function () { it("bytecode with even length failed to publish", async () => { await expect( - knownCodesStorage.connect(deployerAccount).publishEVMBytecode(InvalidBytecode) + knownCodesStorage.connect(deployerAccount).publishEVMBytecode(64, InvalidBytecode) ).to.be.revertedWithCustomError(knownCodesStorage, "MalformedBytecode"); }); it("invalid length bytecode failed to call", async () => { - await expect(knownCodesStorage.connect(deployerAccount).publishEVMBytecode("0x00")).to.be.revertedWithCustomError( - knownCodesStorage, - "MalformedBytecode" - ); + await expect( + knownCodesStorage.connect(deployerAccount).publishEVMBytecode(1, "0x00") + ).to.be.revertedWithCustomError(knownCodesStorage, "MalformedBytecode"); }); });