mirror of https://github.com/hyperledger/besu
"Small" Ethereum Object Format (EIP-3540 + EIP-3670) (#4644)
Implement "Small" EOF - EIP-3540 (container) and EIP-3670 (validation). Make code an interface so EOF specific features are compartmentalized, including an 'invalid' code type representing a code block that didn't pass validation, CodeV1 for EOF1, and CodeV0 which represents pre-EOF code. EVMs track a maximum supported EOF version (where 0 is pre-eof)and code is generated from a CodeFactory taking in context (is it a CREATE operation and max code size) for the validation. Includes spec versions for "Shanghai" and transient testnet "Shandong". "Small" EOF is only activated in Shandong. Signed-off-by: Danno Ferrin <danno.ferrin@swirldslabs.com>pull/4687/head
parent
f940878f98
commit
f20b4b3bd1
@ -0,0 +1,63 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
* |
||||
*/ |
||||
package org.hyperledger.besu.evm; |
||||
|
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
public enum EvmSpecVersion { |
||||
FRONTIER(0, true), |
||||
HOMESTEAD(0, true), |
||||
BYZANTIUM(0, true), |
||||
CONSTANTINOPLE(0, true), |
||||
ISTANBUL(0, true), |
||||
LONDON(0, true), |
||||
PARIS(0, true), |
||||
SHANGHAI(1, false), |
||||
|
||||
/** Transient fork, will be removed */ |
||||
SHANDONG(1, false); |
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(EvmSpecVersion.class); |
||||
|
||||
final boolean specFinalized; |
||||
final int maxEofVersion; |
||||
|
||||
boolean versionWarned = false; |
||||
|
||||
EvmSpecVersion(final int maxEofVersion, final boolean specFinalized) { |
||||
this.maxEofVersion = maxEofVersion; |
||||
this.specFinalized = specFinalized; |
||||
} |
||||
|
||||
public int getMaxEofVersion() { |
||||
return maxEofVersion; |
||||
} |
||||
|
||||
@SuppressWarnings("AlreadyChecked") // false positive
|
||||
public void maybeWarnVersion() { |
||||
if (versionWarned) { |
||||
return; |
||||
} |
||||
|
||||
if (!specFinalized) { |
||||
LOGGER.error( |
||||
"****** Not for Production Network Use ******\nExecuting code from EVM Spec Version {}, which has not been finalized.\n****** Not for Production Network Use ******", |
||||
this.name()); |
||||
} |
||||
versionWarned = true; |
||||
} |
||||
} |
@ -0,0 +1,81 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
* |
||||
*/ |
||||
|
||||
package org.hyperledger.besu.evm.code; |
||||
|
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.evm.Code; |
||||
|
||||
import org.apache.tuweni.bytes.Bytes; |
||||
|
||||
public final class CodeFactory { |
||||
|
||||
public static final byte EOF_LEAD_BYTE = -17; // 0xEF in signed byte form
|
||||
|
||||
public static final int MAX_KNOWN_CODE_VERSION = 1; |
||||
|
||||
private CodeFactory() { |
||||
// factory class, no instantiations.
|
||||
} |
||||
|
||||
public static Code createCode( |
||||
final Bytes bytes, |
||||
final Hash codeHash, |
||||
final int maxEofVersion, |
||||
final boolean inCreateOperation) { |
||||
if (maxEofVersion == 0) { |
||||
return new CodeV0(bytes, codeHash); |
||||
} else if (maxEofVersion == 1) { |
||||
int codeSize = bytes.size(); |
||||
if (codeSize > 0 && bytes.get(0) == EOF_LEAD_BYTE) { |
||||
if (codeSize == 1 && !inCreateOperation) { |
||||
return new CodeV0(bytes, codeHash); |
||||
} |
||||
if (codeSize < 3) { |
||||
return new CodeInvalid(codeHash, bytes, "EOF Container too short"); |
||||
} |
||||
if (bytes.get(1) != 0) { |
||||
if (inCreateOperation) { |
||||
// because some 0xef code made it to mainnet, this is only an error at contract create
|
||||
return new CodeInvalid(codeHash, bytes, "Incorrect second byte"); |
||||
} else { |
||||
return new CodeV0(bytes, codeHash); |
||||
} |
||||
} |
||||
int version = bytes.get(2); |
||||
if (version != 1) { |
||||
return new CodeInvalid(codeHash, bytes, "Unsupported EOF Version: " + version); |
||||
} |
||||
final EOFLayout layout = EOFLayout.parseEOF(bytes); |
||||
if (!layout.isValid()) { |
||||
return new CodeInvalid( |
||||
codeHash, bytes, "Invalid EOF Layout: " + layout.getInvalidReason()); |
||||
} |
||||
final long[] jumpMap = |
||||
OpcodesV1.validateAndCalculateJumpDests(layout.getSections()[EOFLayout.SECTION_CODE]); |
||||
if (jumpMap != null) { |
||||
return new CodeV1(codeHash, layout, jumpMap); |
||||
} else { |
||||
return new CodeInvalid(codeHash, bytes, "Opcode Validation Failed"); |
||||
} |
||||
} else { |
||||
return new CodeV0(bytes, codeHash); |
||||
} |
||||
} else { |
||||
return new CodeInvalid(codeHash, bytes, "Unsupported max code version " + maxEofVersion); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,74 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
* |
||||
*/ |
||||
|
||||
package org.hyperledger.besu.evm.code; |
||||
|
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.evm.Code; |
||||
|
||||
import org.apache.tuweni.bytes.Bytes; |
||||
|
||||
/** |
||||
* For code versions where code can be deemed "invalid" this represents a cachable instance of |
||||
* invalid code. Note that EXTCODE operations can still access invalid code. |
||||
*/ |
||||
public class CodeInvalid implements Code { |
||||
|
||||
private final Hash codeHash; |
||||
private final Bytes codeBytes; |
||||
|
||||
private final String invalidReason; |
||||
|
||||
public CodeInvalid(final Hash codeHash, final Bytes codeBytes, final String invalidReason) { |
||||
this.codeHash = codeHash; |
||||
this.codeBytes = codeBytes; |
||||
this.invalidReason = invalidReason; |
||||
} |
||||
|
||||
public String getInvalidReason() { |
||||
return invalidReason; |
||||
} |
||||
|
||||
@Override |
||||
public int getSize() { |
||||
return codeBytes.size(); |
||||
} |
||||
|
||||
@Override |
||||
public Bytes getCodeBytes() { |
||||
return getContainerBytes(); |
||||
} |
||||
|
||||
@Override |
||||
public Bytes getContainerBytes() { |
||||
return codeBytes; |
||||
} |
||||
|
||||
@Override |
||||
public Hash getCodeHash() { |
||||
return codeHash; |
||||
} |
||||
|
||||
@Override |
||||
public boolean isJumpDestInvalid(final int jumpDestination) { |
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
public boolean isValid() { |
||||
return false; |
||||
} |
||||
} |
@ -0,0 +1,148 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
* |
||||
*/ |
||||
|
||||
package org.hyperledger.besu.evm.code; |
||||
|
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.evm.Code; |
||||
import org.hyperledger.besu.evm.operation.JumpDestOperation; |
||||
import org.hyperledger.besu.evm.operation.PushOperation; |
||||
|
||||
import com.google.common.base.MoreObjects; |
||||
import org.apache.tuweni.bytes.Bytes; |
||||
|
||||
public class CodeV0 implements Code { |
||||
|
||||
public static final CodeV0 EMPTY_CODE = new CodeV0(Bytes.EMPTY, Hash.EMPTY); |
||||
|
||||
/** The bytes representing the code. */ |
||||
private final Bytes bytes; |
||||
|
||||
/** The hash of the code, needed for accessing metadata about the bytecode */ |
||||
private final Hash codeHash; |
||||
|
||||
/** Used to cache valid jump destinations. */ |
||||
private long[] validJumpDestinations; |
||||
|
||||
/** |
||||
* Public constructor. |
||||
* |
||||
* @param bytes The byte representation of the code. |
||||
* @param codeHash the Hash of the bytes in the code. |
||||
*/ |
||||
CodeV0(final Bytes bytes, final Hash codeHash) { |
||||
this.bytes = bytes; |
||||
this.codeHash = codeHash; |
||||
} |
||||
|
||||
/** |
||||
* Returns true if the object is equal to this; otherwise false. |
||||
* |
||||
* @param other The object to compare this with. |
||||
* @return True if the object is equal to this; otherwise false. |
||||
*/ |
||||
@Override |
||||
public boolean equals(final Object other) { |
||||
if (other == null) return false; |
||||
if (other == this) return true; |
||||
if (!(other instanceof CodeV0)) return false; |
||||
|
||||
final CodeV0 that = (CodeV0) other; |
||||
return this.bytes.equals(that.bytes); |
||||
} |
||||
|
||||
@Override |
||||
public int hashCode() { |
||||
return bytes.hashCode(); |
||||
} |
||||
|
||||
/** |
||||
* Size of the Code, in bytes |
||||
* |
||||
* @return The number of bytes in the code. |
||||
*/ |
||||
@Override |
||||
public int getSize() { |
||||
return bytes.size(); |
||||
} |
||||
|
||||
@Override |
||||
public Bytes getCodeBytes() { |
||||
return getContainerBytes(); |
||||
} |
||||
|
||||
@Override |
||||
public Bytes getContainerBytes() { |
||||
return bytes; |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return MoreObjects.toStringHelper(this).add("bytes", bytes).toString(); |
||||
} |
||||
|
||||
@Override |
||||
public Hash getCodeHash() { |
||||
return codeHash; |
||||
} |
||||
|
||||
@Override |
||||
public boolean isJumpDestInvalid(final int jumpDestination) { |
||||
if (jumpDestination < 0 || jumpDestination >= getSize()) { |
||||
return true; |
||||
} |
||||
if (validJumpDestinations == null || validJumpDestinations.length == 0) { |
||||
validJumpDestinations = calculateJumpDests(); |
||||
} |
||||
|
||||
final long targetLong = validJumpDestinations[jumpDestination >>> 6]; |
||||
final long targetBit = 1L << (jumpDestination & 0x3F); |
||||
return (targetLong & targetBit) == 0L; |
||||
} |
||||
|
||||
@Override |
||||
public boolean isValid() { |
||||
return true; |
||||
} |
||||
|
||||
long[] calculateJumpDests() { |
||||
final int size = getSize(); |
||||
final long[] bitmap = new long[(size >> 6) + 1]; |
||||
final byte[] rawCode = bytes.toArrayUnsafe(); |
||||
final int length = rawCode.length; |
||||
for (int i = 0; i < length; ) { |
||||
long thisEntry = 0L; |
||||
final int entryPos = i >> 6; |
||||
final int max = Math.min(64, length - (entryPos << 6)); |
||||
int j = i & 0x3F; |
||||
for (; j < max; i++, j++) { |
||||
final byte operationNum = rawCode[i]; |
||||
if (operationNum == JumpDestOperation.OPCODE) { |
||||
thisEntry |= 1L << j; |
||||
} else if (operationNum > PushOperation.PUSH_BASE) { |
||||
// not needed - && operationNum <= PushOperation.PUSH_MAX
|
||||
// Java quirk, all bytes are signed, and PUSH32 is 127, which is Byte.MAX_VALUE
|
||||
// so we don't need to check the upper bound as it will never be violated
|
||||
final int multiByteDataLen = operationNum - PushOperation.PUSH_BASE; |
||||
j += multiByteDataLen; |
||||
i += multiByteDataLen; |
||||
} |
||||
} |
||||
bitmap[entryPos] = thisEntry; |
||||
} |
||||
return bitmap; |
||||
} |
||||
} |
@ -0,0 +1,93 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
* |
||||
*/ |
||||
|
||||
package org.hyperledger.besu.evm.code; |
||||
|
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.evm.Code; |
||||
|
||||
import java.util.Objects; |
||||
|
||||
import org.apache.tuweni.bytes.Bytes; |
||||
|
||||
public class CodeV1 implements Code { |
||||
private final Hash codeHash; |
||||
private final Bytes container; |
||||
private final Bytes code; |
||||
|
||||
private final long[] validJumpDestinations; |
||||
|
||||
CodeV1(final Hash codeHash, final EOFLayout layout, final long[] validJumpDestinations) { |
||||
this.codeHash = codeHash; |
||||
this.container = layout.getContainer(); |
||||
this.code = layout.getSections()[EOFLayout.SECTION_CODE]; |
||||
this.validJumpDestinations = validJumpDestinations; |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(final Object o) { |
||||
if (this == o) return true; |
||||
if (o == null || getClass() != o.getClass()) return false; |
||||
final CodeV1 codeV1 = (CodeV1) o; |
||||
return codeHash.equals(codeV1.codeHash) |
||||
&& container.equals(codeV1.container) |
||||
&& code.equals(codeV1.code); |
||||
} |
||||
|
||||
@Override |
||||
public int hashCode() { |
||||
return Objects.hash(codeHash, container, code); |
||||
} |
||||
|
||||
@Override |
||||
public int getSize() { |
||||
return container.size(); |
||||
} |
||||
|
||||
@Override |
||||
public Bytes getCodeBytes() { |
||||
return code; |
||||
} |
||||
|
||||
@Override |
||||
public Bytes getContainerBytes() { |
||||
return container; |
||||
} |
||||
|
||||
@Override |
||||
public Hash getCodeHash() { |
||||
return codeHash; |
||||
} |
||||
|
||||
@Override |
||||
public boolean isJumpDestInvalid(final int jumpDestination) { |
||||
if (jumpDestination < 0 || jumpDestination >= getSize()) { |
||||
return true; |
||||
} |
||||
if (validJumpDestinations == null || validJumpDestinations.length == 0) { |
||||
return true; |
||||
} |
||||
|
||||
final long targetLong = validJumpDestinations[jumpDestination >>> 6]; |
||||
final long targetBit = 1L << (jumpDestination & 0x3F); |
||||
return (targetLong & targetBit) == 0L; |
||||
} |
||||
|
||||
@Override |
||||
public boolean isValid() { |
||||
return true; |
||||
} |
||||
} |
@ -0,0 +1,173 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
* |
||||
*/ |
||||
|
||||
package org.hyperledger.besu.evm.code; |
||||
|
||||
import java.io.ByteArrayInputStream; |
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import org.apache.tuweni.bytes.Bytes; |
||||
|
||||
public class EOFLayout { |
||||
|
||||
static final int SECTION_CODE = 0x01; |
||||
static final int SECTION_DATA = 0x02; |
||||
|
||||
static final int MAX_SUPPORTED_SECTION = SECTION_DATA; |
||||
static final int MAX_SUPPORTED_VERSION = 1; |
||||
|
||||
private final Bytes container; |
||||
private final int version; |
||||
private final Bytes[] sections; |
||||
private final String invalidReason; |
||||
|
||||
private EOFLayout(final Bytes container, final int version, final Bytes[] sections) { |
||||
this.container = container; |
||||
this.version = version; |
||||
this.sections = sections; |
||||
this.invalidReason = null; |
||||
} |
||||
|
||||
private EOFLayout(final Bytes container, final String invalidReason) { |
||||
this.container = container; |
||||
this.version = -1; |
||||
this.sections = null; |
||||
this.invalidReason = invalidReason; |
||||
} |
||||
|
||||
public static EOFLayout parseEOF(final Bytes container) { |
||||
final ByteArrayInputStream inputStream = new ByteArrayInputStream(container.toArrayUnsafe()); |
||||
if (inputStream.available() < 3) { |
||||
return new EOFLayout(container, "EOF Container too small"); |
||||
} |
||||
if (inputStream.read() != 0xEF) { |
||||
return new EOFLayout(container, "EOF header byte 0 incorrect"); |
||||
} |
||||
if (inputStream.read() != 0x0) { |
||||
return new EOFLayout(container, "EOF header byte 1 incorrect"); |
||||
} |
||||
|
||||
final int version = inputStream.read(); |
||||
if (version > MAX_SUPPORTED_VERSION || version < 1) { |
||||
return new EOFLayout(container, "Unsupported EOF Version " + version); |
||||
} |
||||
final List<EOFSectionInfo> sectionInfos = new ArrayList<>(3); |
||||
EOFSectionInfo sectionInfo = EOFSectionInfo.getSectionInfo(inputStream); |
||||
while (sectionInfo != null && sectionInfo.kind() != 0) { |
||||
sectionInfos.add(sectionInfo); |
||||
sectionInfo = EOFSectionInfo.getSectionInfo(inputStream); |
||||
} |
||||
if (sectionInfo == null) { |
||||
return new EOFLayout(container, "Improper section headers"); |
||||
} |
||||
|
||||
int remaining = inputStream.available(); |
||||
int pos = container.size() - remaining; |
||||
|
||||
final Bytes[] sections = new Bytes[MAX_SUPPORTED_SECTION + 1]; |
||||
for (final var info : sectionInfos) { |
||||
final int kind = info.kind(); |
||||
final int size = info.size(); |
||||
if (kind > MAX_SUPPORTED_SECTION) { |
||||
return new EOFLayout(container, "EOF Section kind " + kind + " not supported"); |
||||
} |
||||
if (sections[kind] != null) { |
||||
return new EOFLayout(container, "Duplicate section number " + kind); |
||||
} |
||||
if (size == 0) { |
||||
return new EOFLayout(container, "Empty section contents"); |
||||
} |
||||
if (size > remaining) { |
||||
return new EOFLayout(container, "Missing or incomplete section data"); |
||||
} |
||||
if (kind == 1 && sections[2] != null) { |
||||
return new EOFLayout(container, "Code section cannot follow data section"); |
||||
} |
||||
|
||||
sections[kind] = container.slice(pos, size); |
||||
pos += size; |
||||
remaining -= size; |
||||
} |
||||
if (pos < container.size()) { |
||||
return new EOFLayout(container, "Dangling data at end of container"); |
||||
} |
||||
if (sections[1] == null) { |
||||
return new EOFLayout(container, "Missing code (kind=1) section"); |
||||
} else if (sections[1].size() < 1) { |
||||
return new EOFLayout(container, "Code section empty"); |
||||
} |
||||
return new EOFLayout(container, version, sections); |
||||
} |
||||
|
||||
public Bytes getContainer() { |
||||
return container; |
||||
} |
||||
|
||||
public int getVersion() { |
||||
return version; |
||||
} |
||||
|
||||
public Bytes[] getSections() { |
||||
return sections; |
||||
} |
||||
|
||||
public String getInvalidReason() { |
||||
return invalidReason; |
||||
} |
||||
|
||||
public boolean isValid() { |
||||
return invalidReason == null; |
||||
} |
||||
} |
||||
|
||||
// TODO should be a record
|
||||
final class EOFSectionInfo { |
||||
private final int kind; |
||||
private final int size; |
||||
|
||||
private EOFSectionInfo(final int kind, final int size) { |
||||
this.kind = kind; |
||||
this.size = size; |
||||
} |
||||
|
||||
static EOFSectionInfo getSectionInfo(final InputStream in) { |
||||
try { |
||||
final int kind = in.read(); |
||||
if (kind < 0) { |
||||
return null; |
||||
} else if (kind == 0) { |
||||
return new EOFSectionInfo(kind, 0); |
||||
} else { |
||||
final int msb = in.read() << 8; |
||||
final int lsb = in.read(); |
||||
return new EOFSectionInfo(kind, msb + lsb); |
||||
} |
||||
} catch (final IOException ioe) { |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
public int kind() { |
||||
return kind; |
||||
} |
||||
|
||||
public int size() { |
||||
return size; |
||||
} |
||||
} |
@ -0,0 +1,327 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
* |
||||
*/ |
||||
|
||||
package org.hyperledger.besu.evm.code; |
||||
|
||||
import org.hyperledger.besu.evm.operation.PushOperation; |
||||
|
||||
import org.apache.tuweni.bytes.Bytes; |
||||
|
||||
class OpcodesV1 { |
||||
static final byte INVALID = 0x01; |
||||
static final byte VALID = 0x02; |
||||
static final byte TERMINAL = 0x04; |
||||
static final byte JUMPDEST = 0x08; |
||||
|
||||
static final byte VALID_AND_TERMINAL = VALID | TERMINAL; |
||||
static final byte VALID_AND_JUMPDEST = VALID | JUMPDEST; |
||||
|
||||
private static final byte[] opcodeAttributes = { |
||||
VALID_AND_TERMINAL, // 0x00 STOP
|
||||
VALID, // 0x01 - ADD
|
||||
VALID, // 0x02 - MUL
|
||||
VALID, // 0x03 - SUB
|
||||
VALID, // 0x04 - DIV
|
||||
VALID, // 0x05 - SDIV
|
||||
VALID, // 0x06 - MOD
|
||||
VALID, // 0x07 - SMOD
|
||||
VALID, // 0x08 - ADDMOD
|
||||
VALID, // 0x09 - MULMOD
|
||||
VALID, // 0x0a - EXP
|
||||
VALID, // 0x0b - SIGNEXTEND
|
||||
INVALID, // 0x0c
|
||||
INVALID, // 0x0d
|
||||
INVALID, // 0x0e
|
||||
INVALID, // 0x0f
|
||||
VALID, // 0x10 - LT
|
||||
VALID, // 0x11 - GT
|
||||
VALID, // 0x12 - SLT
|
||||
VALID, // 0x13 - SGT
|
||||
VALID, // 0x14 - EQ
|
||||
VALID, // 0x15 - ISZERO
|
||||
VALID, // 0x16 - AND
|
||||
VALID, // 0x17 - OR
|
||||
VALID, // 0x18 - XOR
|
||||
VALID, // 0x19 - NOT
|
||||
VALID, // 0x1a - BYTE
|
||||
VALID, // 0x1b - SHL
|
||||
VALID, // 0x1c - SHR
|
||||
VALID, // 0x1d - SAR
|
||||
INVALID, // 0x1e
|
||||
INVALID, // 0x1f
|
||||
VALID, // 0x20 - SHA3
|
||||
INVALID, // 0x21
|
||||
INVALID, // 0x22
|
||||
INVALID, // 0x23
|
||||
INVALID, // 0x24
|
||||
INVALID, // 0x25
|
||||
INVALID, // 0x26
|
||||
INVALID, // 0x27
|
||||
INVALID, // 0x28
|
||||
INVALID, // 0x29
|
||||
INVALID, // 0x2a
|
||||
INVALID, // 0x2b
|
||||
INVALID, // 0x2c
|
||||
INVALID, // 0x2d
|
||||
INVALID, // 0x2e
|
||||
INVALID, // 0x2f
|
||||
VALID, // 0x30 - ADDRESS
|
||||
VALID, // 0x31 - BALANCE
|
||||
VALID, // 0x32 - ORIGIN
|
||||
VALID, // 0x33 - CALLER
|
||||
VALID, // 0x34 - CALLVALUE
|
||||
VALID, // 0x35 - CALLDATALOAD
|
||||
VALID, // 0x36 - CALLDATASIZE
|
||||
VALID, // 0x37 - CALLDATACOPY
|
||||
VALID, // 0x38 - CODESIZE
|
||||
VALID, // 0x39 - CODECOPY
|
||||
VALID, // 0x3a - GASPRICE
|
||||
VALID, // 0x3b - EXTCODESIZE
|
||||
VALID, // 0x3c - EXTCODECOPY
|
||||
VALID, // 0x3d - RETURNDATASIZE
|
||||
VALID, // 0x3e - RETURNDATACOPY
|
||||
VALID, // 0x3f - EXTCODEHASH
|
||||
VALID, // 0x40 - BLOCKHASH
|
||||
VALID, // 0x41 - COINBASE
|
||||
VALID, // 0x42 - TIMESTAMP
|
||||
VALID, // 0x43 - NUMBER
|
||||
VALID, // 0x44 - DIFFICULTY
|
||||
VALID, // 0x45 - GASLIMIT
|
||||
VALID, // 0x46 - CHAINID
|
||||
VALID, // 0x47 - SELFBALANCE
|
||||
VALID, // 0x48 - BASEFEE
|
||||
INVALID, // 0x49
|
||||
INVALID, // 0x4a
|
||||
INVALID, // 0x4b
|
||||
INVALID, // 0x4c
|
||||
INVALID, // 0x4d
|
||||
INVALID, // 0x4e
|
||||
INVALID, // 0x4f
|
||||
VALID, // 0x50 - POP
|
||||
VALID, // 0x51 - MLOAD
|
||||
VALID, // 0x52 - MSTORE
|
||||
VALID, // 0x53 - MSTORE8
|
||||
VALID, // 0x54 - SLOAD
|
||||
VALID, // 0x55 - SSTORE
|
||||
VALID, // 0x56 - JUMP
|
||||
VALID, // 0x57 - JUMPI
|
||||
VALID, // 0x58 - PC
|
||||
VALID, // 0x59 - MSIZE
|
||||
VALID, // 0x5a - GAS
|
||||
VALID_AND_JUMPDEST, // 0x5b - JUMPDEST
|
||||
INVALID, // 0X5c
|
||||
INVALID, // 0X5d
|
||||
INVALID, // 0X5e
|
||||
INVALID, // 0X5f - ?PUSH0?
|
||||
VALID, // 0x60 - PUSH1
|
||||
VALID, // 0x61 - PUSH2
|
||||
VALID, // 0x62 - PUSH3
|
||||
VALID, // 0x63 - PUSH4
|
||||
VALID, // 0x64 - PUSH5
|
||||
VALID, // 0x65 - PUSH6
|
||||
VALID, // 0x66 - PUSH7
|
||||
VALID, // 0x67 - PUSH8
|
||||
VALID, // 0x68 - PUSH9
|
||||
VALID, // 0x69 - PUSH10
|
||||
VALID, // 0x6a - PUSH11
|
||||
VALID, // 0x6b - PUSH12
|
||||
VALID, // 0x6c - PUSH13
|
||||
VALID, // 0x6d - PUSH14
|
||||
VALID, // 0x6e - PUSH15
|
||||
VALID, // 0x6f - PUSH16
|
||||
VALID, // 0x70 - PUSH17
|
||||
VALID, // 0x71 - PUSH18
|
||||
VALID, // 0x72 - PUSH19
|
||||
VALID, // 0x73 - PUSH20
|
||||
VALID, // 0x74 - PUSH21
|
||||
VALID, // 0x75 - PUSH22
|
||||
VALID, // 0x76 - PUSH23
|
||||
VALID, // 0x77 - PUSH24
|
||||
VALID, // 0x78 - PUSH25
|
||||
VALID, // 0x79 - PUSH26
|
||||
VALID, // 0x7a - PUSH27
|
||||
VALID, // 0x7b - PUSH28
|
||||
VALID, // 0x7c - PUSH29
|
||||
VALID, // 0x7d - PUSH30
|
||||
VALID, // 0x7e - PUSH31
|
||||
VALID, // 0x7f - PUSH32
|
||||
VALID, // 0x80 - DUP1
|
||||
VALID, // 0x81 - DUP2
|
||||
VALID, // 0x82 - DUP3
|
||||
VALID, // 0x83 - DUP4
|
||||
VALID, // 0x84 - DUP5
|
||||
VALID, // 0x85 - DUP6
|
||||
VALID, // 0x86 - DUP7
|
||||
VALID, // 0x87 - DUP8
|
||||
VALID, // 0x88 - DUP9
|
||||
VALID, // 0x89 - DUP10
|
||||
VALID, // 0x8a - DUP11
|
||||
VALID, // 0x8b - DUP12
|
||||
VALID, // 0x8c - DUP13
|
||||
VALID, // 0x8d - DUP14
|
||||
VALID, // 0x8e - DUP15
|
||||
VALID, // 0x8f - DUP16
|
||||
VALID, // 0x90 - SWAP1
|
||||
VALID, // 0x91 - SWAP2
|
||||
VALID, // 0x92 - SWAP3
|
||||
VALID, // 0x93 - SWAP4
|
||||
VALID, // 0x94 - SWAP5
|
||||
VALID, // 0x95 - SWAP6
|
||||
VALID, // 0x96 - SWAP7
|
||||
VALID, // 0x97 - SWAP8
|
||||
VALID, // 0x98 - SWAP9
|
||||
VALID, // 0x99 - SWAP10
|
||||
VALID, // 0x9a - SWAP11
|
||||
VALID, // 0x9b - SWAP12
|
||||
VALID, // 0x9c - SWAP13
|
||||
VALID, // 0x9d - SWAP14
|
||||
VALID, // 0x9e - SWAP15
|
||||
VALID, // 0x9f - SWAP16
|
||||
VALID, // 0xa0 - LOG0
|
||||
VALID, // 0xa1 - LOG1
|
||||
VALID, // 0xa2 - LOG2
|
||||
VALID, // 0xa3 - LOG3
|
||||
VALID, // 0xa4 - LOG4
|
||||
INVALID, // 0xa5
|
||||
INVALID, // 0xa6
|
||||
INVALID, // 0xa7
|
||||
INVALID, // 0xa8
|
||||
INVALID, // 0xa9
|
||||
INVALID, // 0xaa
|
||||
INVALID, // 0xab
|
||||
INVALID, // 0xac
|
||||
INVALID, // 0xad
|
||||
INVALID, // 0xae
|
||||
INVALID, // 0xaf
|
||||
INVALID, // 0xb0
|
||||
INVALID, // 0xb1
|
||||
INVALID, // 0xb2
|
||||
INVALID, // 0xb3
|
||||
INVALID, // 0xb4
|
||||
INVALID, // 0xb5
|
||||
INVALID, // 0xb6
|
||||
INVALID, // 0xb7
|
||||
INVALID, // 0xb8
|
||||
INVALID, // 0xb9
|
||||
INVALID, // 0xba
|
||||
INVALID, // 0xbb
|
||||
INVALID, // 0xbc
|
||||
INVALID, // 0xbd
|
||||
INVALID, // 0xbe
|
||||
INVALID, // 0xbf
|
||||
INVALID, // 0xc0
|
||||
INVALID, // 0xc1
|
||||
INVALID, // 0xc2
|
||||
INVALID, // 0xc3
|
||||
INVALID, // 0xc4
|
||||
INVALID, // 0xc5
|
||||
INVALID, // 0xc6
|
||||
INVALID, // 0xc7
|
||||
INVALID, // 0xc8
|
||||
INVALID, // 0xc9
|
||||
INVALID, // 0xca
|
||||
INVALID, // 0xcb
|
||||
INVALID, // 0xcc
|
||||
INVALID, // 0xcd
|
||||
INVALID, // 0xce
|
||||
INVALID, // 0xcf
|
||||
INVALID, // 0xd0
|
||||
INVALID, // 0xd1
|
||||
INVALID, // 0xd2
|
||||
INVALID, // 0xd3
|
||||
INVALID, // 0xd4
|
||||
INVALID, // 0xd5
|
||||
INVALID, // 0xd6
|
||||
INVALID, // 0xd7
|
||||
INVALID, // 0xd8
|
||||
INVALID, // 0xd9
|
||||
INVALID, // 0xda
|
||||
INVALID, // 0xdb
|
||||
INVALID, // 0xdc
|
||||
INVALID, // 0xdd
|
||||
INVALID, // 0xde
|
||||
INVALID, // 0xef
|
||||
INVALID, // 0xe0
|
||||
INVALID, // 0xe1
|
||||
INVALID, // 0xe2
|
||||
INVALID, // 0xe3
|
||||
INVALID, // 0xe4
|
||||
INVALID, // 0xe5
|
||||
INVALID, // 0xe6
|
||||
INVALID, // 0xe7
|
||||
INVALID, // 0xe8
|
||||
INVALID, // 0xe9
|
||||
INVALID, // 0xea
|
||||
INVALID, // 0xeb
|
||||
INVALID, // 0xec
|
||||
INVALID, // 0xed
|
||||
INVALID, // 0xee
|
||||
INVALID, // 0xef
|
||||
VALID, // 0xf0 - CREATE
|
||||
VALID, // 0xf1 - CALL
|
||||
VALID, // 0xf2 - CALLCODE
|
||||
VALID_AND_TERMINAL, // 0xf3 - RETURN
|
||||
VALID, // 0xf4 - DELEGATECALL
|
||||
VALID, // 0xf5 - CREATE2
|
||||
INVALID, // 0xf6
|
||||
INVALID, // 0xf7
|
||||
INVALID, // 0xf8
|
||||
INVALID, // 0xf9
|
||||
VALID, // 0xfa - STATICCALL
|
||||
INVALID, // 0xfb
|
||||
INVALID, // 0xfc
|
||||
VALID_AND_TERMINAL, // 0xfd - REVERT
|
||||
VALID_AND_TERMINAL, // 0xfe - INVALID
|
||||
VALID_AND_TERMINAL, // 0xff - SELFDESTRUCT
|
||||
}; |
||||
|
||||
private OpcodesV1() { |
||||
// static utility class
|
||||
} |
||||
|
||||
static long[] validateAndCalculateJumpDests(final Bytes code) { |
||||
final int size = code.size(); |
||||
final long[] bitmap = new long[(size >> 6) + 1]; |
||||
final byte[] rawCode = code.toArrayUnsafe(); |
||||
final int length = rawCode.length; |
||||
int attribute = INVALID; |
||||
for (int i = 0; i < length; ) { |
||||
long thisEntry = 0L; |
||||
final int entryPos = i >> 6; |
||||
final int max = Math.min(64, length - (entryPos << 6)); |
||||
int j = i & 0x3f; |
||||
for (; j < max; i++, j++) { |
||||
final int operationNum = rawCode[i] & 0xff; |
||||
attribute = opcodeAttributes[operationNum]; |
||||
if ((attribute & INVALID) == INVALID) { |
||||
return null; |
||||
} else if ((attribute & JUMPDEST) == JUMPDEST) { |
||||
thisEntry |= 1L << j; |
||||
} else if (operationNum > PushOperation.PUSH_BASE |
||||
&& operationNum <= PushOperation.PUSH_MAX) { |
||||
final int multiByteDataLen = operationNum - PushOperation.PUSH_BASE; |
||||
j += multiByteDataLen; |
||||
i += multiByteDataLen; |
||||
} |
||||
} |
||||
bitmap[entryPos] = thisEntry; |
||||
} |
||||
if ((attribute & TERMINAL) != TERMINAL) { |
||||
return null; |
||||
} |
||||
return bitmap; |
||||
} |
||||
} |
@ -0,0 +1,38 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
* |
||||
*/ |
||||
|
||||
package org.hyperledger.besu.evm.contractvalidation; |
||||
|
||||
import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; |
||||
import org.hyperledger.besu.evm.frame.MessageFrame; |
||||
|
||||
import java.util.Optional; |
||||
|
||||
public class CachedInvalidCodeRule implements ContractValidationRule { |
||||
|
||||
@Override |
||||
public Optional<ExceptionalHaltReason> validate(final MessageFrame frame) { |
||||
if (!frame.getCode().isValid()) { |
||||
return Optional.of(ExceptionalHaltReason.INVALID_CODE); |
||||
} else { |
||||
return Optional.empty(); |
||||
} |
||||
} |
||||
|
||||
public static ContractValidationRule of() { |
||||
return new CachedInvalidCodeRule(); |
||||
} |
||||
} |
@ -0,0 +1,113 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
* |
||||
*/ |
||||
|
||||
package org.hyperledger.besu.evm.code; |
||||
|
||||
import static org.hyperledger.besu.evm.frame.MessageFrame.Type.MESSAGE_CALL; |
||||
import static org.junit.jupiter.api.Assertions.assertNull; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.spy; |
||||
import static org.mockito.Mockito.times; |
||||
|
||||
import org.hyperledger.besu.datatypes.Address; |
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.datatypes.Wei; |
||||
import org.hyperledger.besu.evm.EVM; |
||||
import org.hyperledger.besu.evm.EvmSpecVersion; |
||||
import org.hyperledger.besu.evm.frame.BlockValues; |
||||
import org.hyperledger.besu.evm.frame.MessageFrame; |
||||
import org.hyperledger.besu.evm.gascalculator.IstanbulGasCalculator; |
||||
import org.hyperledger.besu.evm.internal.EvmConfiguration; |
||||
import org.hyperledger.besu.evm.operation.JumpDestOperation; |
||||
import org.hyperledger.besu.evm.operation.JumpOperation; |
||||
import org.hyperledger.besu.evm.operation.Operation.OperationResult; |
||||
import org.hyperledger.besu.evm.operation.OperationRegistry; |
||||
import org.hyperledger.besu.evm.worldstate.WorldUpdater; |
||||
|
||||
import java.util.ArrayDeque; |
||||
import javax.annotation.Nonnull; |
||||
|
||||
import org.apache.tuweni.bytes.Bytes; |
||||
import org.apache.tuweni.units.bigints.UInt256; |
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
import org.mockito.Mockito; |
||||
|
||||
public class CodeV0Test { |
||||
|
||||
private static final IstanbulGasCalculator gasCalculator = new IstanbulGasCalculator(); |
||||
|
||||
private static final int CURRENT_PC = 1; |
||||
private EVM evm; |
||||
|
||||
@Before |
||||
public void startUp() { |
||||
final OperationRegistry registry = new OperationRegistry(); |
||||
registry.put(new JumpOperation(gasCalculator)); |
||||
registry.put(new JumpDestOperation(gasCalculator)); |
||||
evm = new EVM(registry, gasCalculator, EvmConfiguration.DEFAULT, EvmSpecVersion.PARIS); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldReuseJumpDestMap() { |
||||
final JumpOperation operation = new JumpOperation(gasCalculator); |
||||
final Bytes jumpBytes = Bytes.fromHexString("0x6003565b00"); |
||||
final CodeV0 getsCached = |
||||
(CodeV0) spy(CodeFactory.createCode(jumpBytes, Hash.hash(jumpBytes), 0, false)); |
||||
MessageFrame frame = createJumpFrame(getsCached); |
||||
|
||||
OperationResult result = operation.execute(frame, evm); |
||||
assertNull(result.getHaltReason()); |
||||
Mockito.verify(getsCached, times(1)).calculateJumpDests(); |
||||
|
||||
// do it again to prove we don't recalculate, and we hit the cache
|
||||
|
||||
frame = createJumpFrame(getsCached); |
||||
|
||||
result = operation.execute(frame, evm); |
||||
assertNull(result.getHaltReason()); |
||||
Mockito.verify(getsCached, times(1)).calculateJumpDests(); |
||||
} |
||||
|
||||
@Nonnull |
||||
private MessageFrame createJumpFrame(final CodeV0 getsCached) { |
||||
final MessageFrame frame = |
||||
MessageFrame.builder() |
||||
.type(MESSAGE_CALL) |
||||
.messageFrameStack(new ArrayDeque<>()) |
||||
.worldUpdater(mock(WorldUpdater.class)) |
||||
.initialGas(10_000L) |
||||
.address(Address.ZERO) |
||||
.originator(Address.ZERO) |
||||
.contract(Address.ZERO) |
||||
.gasPrice(Wei.ZERO) |
||||
.inputData(Bytes.EMPTY) |
||||
.sender(Address.ZERO) |
||||
.value(Wei.ZERO) |
||||
.apparentValue(Wei.ZERO) |
||||
.code(getsCached) |
||||
.blockValues(mock(BlockValues.class)) |
||||
.depth(0) |
||||
.completer(f -> {}) |
||||
.miningBeneficiary(Address.ZERO) |
||||
.blockHashLookup(l -> Hash.EMPTY) |
||||
.build(); |
||||
|
||||
frame.setPC(CURRENT_PC); |
||||
frame.pushStackItem(UInt256.fromHexString("0x03")); |
||||
return frame; |
||||
} |
||||
} |
@ -0,0 +1,35 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
* |
||||
*/ |
||||
|
||||
package org.hyperledger.besu.evm.code; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
import org.apache.tuweni.bytes.Bytes; |
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
class CodeV1Test { |
||||
|
||||
@Test |
||||
void calculatesJumpDestMap() { |
||||
String codeHex = "0xEF000101000F006001600055600D5660026000555B00"; |
||||
final EOFLayout layout = EOFLayout.parseEOF(Bytes.fromHexString(codeHex)); |
||||
|
||||
long[] jumpDest = OpcodesV1.validateAndCalculateJumpDests(layout.getSections()[1]); |
||||
|
||||
assertThat(jumpDest).containsExactly(0x2000); |
||||
} |
||||
} |
@ -0,0 +1,147 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
* |
||||
*/ |
||||
|
||||
package org.hyperledger.besu.evm.code; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
import java.util.Arrays; |
||||
import java.util.Collection; |
||||
|
||||
import org.apache.tuweni.bytes.Bytes; |
||||
import org.junit.jupiter.params.ParameterizedTest; |
||||
import org.junit.jupiter.params.provider.MethodSource; |
||||
|
||||
public class EOFLayoutTest { |
||||
|
||||
public static Collection<Object[]> testCasesFromEIP3540() { |
||||
return Arrays.asList( |
||||
new Object[][] { |
||||
{"EF", "No magic", "EOF Container too small", null}, |
||||
{ |
||||
"EFFF01010002020004006000AABBCCDD", "Invalid magic", "EOF header byte 1 incorrect", null |
||||
}, |
||||
{"EF00", "No version", "EOF Container too small", null}, |
||||
{ |
||||
"EF0000010002020004006000AABBCCDD", "Invalid version", "Unsupported EOF Version 0", null |
||||
}, |
||||
{ |
||||
"EF0002010002020004006000AABBCCDD", "Invalid version", "Unsupported EOF Version 2", null |
||||
}, |
||||
{ |
||||
"EF00FF010002020004006000AABBCCDD", |
||||
"Invalid version", |
||||
"Unsupported EOF Version 255", |
||||
null |
||||
}, |
||||
{"EF0001", "No header", "Improper section headers", null}, |
||||
{"EF000100", "No code section", "Missing code (kind=1) section", null}, |
||||
{"EF000101", "No code section size", "Improper section headers", null}, |
||||
{"EF00010100", "Code section size incomplete", "Improper section headers", null}, |
||||
{"EF0001010002", "No section terminator", "Improper section headers", null}, |
||||
{ |
||||
"EF000101000200", "No code section contents", "Missing or incomplete section data", null |
||||
}, |
||||
{ |
||||
"EF00010100020060", |
||||
"Code section contents incomplete", |
||||
"Missing or incomplete section data", |
||||
null |
||||
}, |
||||
{ |
||||
"EF0001010002006000DEADBEEF", |
||||
"Trailing bytes after code section", |
||||
"Dangling data at end of container", |
||||
null |
||||
}, |
||||
{ |
||||
"EF00010100020100020060006000", |
||||
"Multiple code sections", |
||||
"Duplicate section number 1", |
||||
null |
||||
}, |
||||
{"EF000101000000", "Empty code section", "Empty section contents", null}, |
||||
{"EF000101000002000200AABB", "Empty code section", "Empty section contents", null}, |
||||
{ |
||||
"EF000102000401000200AABBCCDD6000", |
||||
"Data section preceding code section", |
||||
"Code section cannot follow data section", |
||||
null |
||||
}, |
||||
{ |
||||
"EF000102000400AABBCCDD", |
||||
"Data section without code section", |
||||
"Missing code (kind=1) section", |
||||
null |
||||
}, |
||||
{"EF000101000202", "No data section size", "Improper section headers", null}, |
||||
{"EF00010100020200", "Data section size incomplete", "Improper section headers", null}, |
||||
{"EF0001010002020004", "No section terminator", "Improper section headers", null}, |
||||
{ |
||||
"EF0001010002020004006000", |
||||
"No data section contents", |
||||
"Missing or incomplete section data", |
||||
null |
||||
}, |
||||
{ |
||||
"EF0001010002020004006000AABBCC", |
||||
"Data section contents incomplete", |
||||
"Missing or incomplete section data", |
||||
null |
||||
}, |
||||
{ |
||||
"EF0001010002020004006000AABBCCDDEE", |
||||
"Trailing bytes after data section", |
||||
"Dangling data at end of container", |
||||
null |
||||
}, |
||||
{ |
||||
"EF0001010002020004020004006000AABBCCDDAABBCCDD", |
||||
"Multiple data sections", |
||||
"Duplicate section number 2", |
||||
null |
||||
}, |
||||
{"EF0001010002020000006000", "Empty data section", "Empty section contents", null}, |
||||
{ |
||||
"EF0001010002030004006000AABBCCDD", |
||||
"Unknown section (id = 3)", |
||||
"EOF Section kind 3 not supported", |
||||
null |
||||
}, |
||||
{"EF0001010002006000", "Valid", null, "0x6000"}, |
||||
}); |
||||
} |
||||
|
||||
@ParameterizedTest(name = "{1}") |
||||
@MethodSource("testCasesFromEIP3540") |
||||
void test( |
||||
final String containerString, |
||||
final String description, |
||||
final String failureReason, |
||||
final String code) { |
||||
final Bytes container = Bytes.fromHexString(containerString); |
||||
final EOFLayout layout = EOFLayout.parseEOF(container); |
||||
System.out.println(description); |
||||
assertThat(layout.getInvalidReason()).isEqualTo(failureReason); |
||||
if (code != null) { |
||||
assertThat(layout.getSections()).hasSize(3); |
||||
assertThat(layout.getSections()[1]).isNotNull(); |
||||
assertThat(layout.getSections()[1].toHexString()).isEqualTo(code); |
||||
} else { |
||||
assertThat(layout.getSections()).isNull(); |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue