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