updated EXTDELEGATECALL won't work with EOA/empty

Signed-off-by: Danno Ferrin <danno@numisight.com>
mega-eof
Danno Ferrin 6 months ago
parent 5f29832d09
commit 3c2c43fc44
  1. 16
      ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/eof/EOFReferenceTestTools.java
  2. 32
      evm/src/main/java/org/hyperledger/besu/evm/Code.java
  3. 2
      evm/src/main/java/org/hyperledger/besu/evm/code/CodeFactory.java
  4. 42
      evm/src/main/java/org/hyperledger/besu/evm/code/CodeInvalid.java
  5. 28
      evm/src/main/java/org/hyperledger/besu/evm/code/CodeV0.java
  6. 122
      evm/src/main/java/org/hyperledger/besu/evm/code/CodeV1Validation.java
  7. 3
      evm/src/main/java/org/hyperledger/besu/evm/code/EOFLayout.java
  8. 2
      evm/src/main/java/org/hyperledger/besu/evm/fluent/EVMExecutor.java
  9. 2
      evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCreateOperation.java
  10. 21
      evm/src/main/java/org/hyperledger/besu/evm/operation/RelativeJumpVectorOperation.java
  11. 17
      evm/src/test/java/org/hyperledger/besu/evm/gascalculator/PragueEOFGasCalculatorTest.java

@ -84,13 +84,13 @@ public class EOFReferenceTestTools {
}
public static void executeTest(
final String fork, final Bytes code, final EOFTestCaseSpec.TestResult results) {
final String fork, final Bytes code, final EOFTestCaseSpec.TestResult expected) {
EvmSpecVersion evmVersion = EvmSpecVersion.fromName(fork);
assertThat(evmVersion).isNotNull();
// hardwire in the magic byte transaction checks
if (evmVersion.getMaxEofVersion() < 1) {
assertThat(results.exception()).isEqualTo("EOF_InvalidCode");
assertThat(expected.exception()).isEqualTo("EOF_InvalidCode");
} else {
EOFLayout layout = EOFLayout.parseEOF(code);
@ -101,12 +101,12 @@ public class EOFReferenceTestTools {
() ->
EOFLayout.parseEOF(code).prettyPrint()
+ "\nExpected exception :"
+ results.exception()
+ expected.exception()
+ " actual exception :"
+ (parsedCode.isValid()
? null
: ((CodeInvalid) parsedCode).getInvalidReason()))
.isEqualTo(results.result());
.isEqualTo(expected.result());
if (parsedCode instanceof CodeV1 codeV1) {
var deepValidate = CodeV1Validation.validate(codeV1.getEofLayout());
assertThat(deepValidate)
@ -114,13 +114,13 @@ public class EOFReferenceTestTools {
() ->
codeV1.prettyPrint()
+ "\nExpected exception :"
+ results.exception()
+ expected.exception()
+ " actual exception :"
+ (parsedCode.isValid() ? null : deepValidate))
.isNull();
}
if (results.result()) {
if (expected.result()) {
System.out.println(code);
System.out.println(layout.writeContainer(null));
assertThat(code)
@ -132,10 +132,10 @@ public class EOFReferenceTestTools {
.withFailMessage(
() ->
"Expected exception - "
+ results.exception()
+ expected.exception()
+ " actual exception - "
+ (layout.isValid() ? null : layout.invalidReason()))
.isEqualTo(results.result());
.isEqualTo(expected.result());
}
}
}

@ -37,9 +37,7 @@ public interface Code {
*
* @return size of code in bytes.
*/
default int getDataSize() {
return 0;
}
int getDataSize();
/**
* Get the bytes for the entire container, for example what EXTCODECOPY would want. For V0 it is
@ -100,9 +98,7 @@ public interface Code {
*
* @return The subcontainer count or zero if not supported;
*/
default int getSubcontainerCount() {
return 0;
}
int getSubcontainerCount();
/**
* Returns the subcontainer at the selected index. If the container doesn't exist or is invalid,
@ -113,9 +109,7 @@ public interface Code {
* container, pass null.
* @return Either the subcontainer, or empty.
*/
default Optional<Code> getSubContainer(final int index, final Bytes auxData) {
return Optional.empty();
}
Optional<Code> getSubContainer(final int index, final Bytes auxData);
/**
* Loads data from the appropriate data section
@ -124,9 +118,7 @@ public interface Code {
* @param length how many bytes to copy
* @return A slice of the code containing the requested data
*/
default Bytes getData(final int offset, final int length) {
return Bytes.EMPTY;
}
Bytes getData(final int offset, final int length);
/**
* Read a signed 16-bit big-endian integer
@ -134,9 +126,7 @@ public interface Code {
* @param startIndex the index to start reading the integer in the code
* @return a java int representing the 16-bit signed integer.
*/
default int readBigEndianI16(final int startIndex) {
return 0;
}
int readBigEndianI16(final int startIndex);
/**
* Read an unsigned 16 bit big-endian integer
@ -144,9 +134,7 @@ public interface Code {
* @param startIndex the index to start reading the integer in the code
* @return a java int representing the 16-bit unsigned integer.
*/
default int readBigEndianU16(final int startIndex) {
return 0;
}
int readBigEndianU16(final int startIndex);
/**
* Read an unsigned 8-bit integer
@ -154,16 +142,12 @@ public interface Code {
* @param startIndex the index to start reading the integer in the code
* @return a java int representing the 8-bit unsigned integer.
*/
default int readU8(final int startIndex) {
return 0;
}
int readU8(final int startIndex);
/**
* A more readable representation of the hex bytes, including whitespace and comments after hashes
*
* @return The pretty printed code
*/
default String prettyPrint() {
return getBytes().toString();
}
String prettyPrint();
}

@ -102,7 +102,7 @@ public final class CodeFactory {
final EOFLayout layout = EOFLayout.parseEOF(bytes, !createTransaction);
if (createTransaction) {
layout.createMode().set(INITCODE);
layout.containerMode().set(INITCODE);
}
return createCode(layout, createTransaction);
} else {

@ -16,7 +16,9 @@ package org.hyperledger.besu.evm.code;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.evm.Code;
import org.hyperledger.besu.evm.internal.Words;
import java.util.Optional;
import java.util.function.Supplier;
import com.google.common.base.Suppliers;
@ -59,6 +61,11 @@ public class CodeInvalid implements Code {
return codeBytes.size();
}
@Override
public int getDataSize() {
return 0;
}
@Override
public Bytes getBytes() {
return codeBytes;
@ -93,4 +100,39 @@ public class CodeInvalid implements Code {
public int getEofVersion() {
return Integer.MAX_VALUE;
}
@Override
public int getSubcontainerCount() {
return 0;
}
@Override
public Optional<Code> getSubContainer(final int index, final Bytes auxData) {
return Optional.empty();
}
@Override
public Bytes getData(final int offset, final int length) {
return Bytes.EMPTY;
}
@Override
public int readBigEndianI16(final int index) {
return Words.readBigEndianI16(index, codeBytes.toArrayUnsafe());
}
@Override
public int readBigEndianU16(final int index) {
return Words.readBigEndianU16(index, codeBytes.toArrayUnsafe());
}
@Override
public int readU8(final int index) {
return codeBytes.toArrayUnsafe()[index] & 0xff;
}
@Override
public String prettyPrint() {
return codeBytes.toHexString();
}
}

@ -19,6 +19,7 @@ import org.hyperledger.besu.evm.Code;
import org.hyperledger.besu.evm.internal.Words;
import org.hyperledger.besu.evm.operation.JumpDestOperation;
import java.util.Optional;
import java.util.function.Supplier;
import com.google.common.base.MoreObjects;
@ -58,7 +59,7 @@ public class CodeV0 implements Code {
* 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.
* @return True if the object is equal to this, otherwise false.
*/
@Override
public boolean equals(final Object other) {
@ -84,6 +85,11 @@ public class CodeV0 implements Code {
return bytes.size();
}
@Override
public int getDataSize() {
return 0;
}
@Override
public Bytes getBytes() {
return bytes;
@ -137,6 +143,21 @@ public class CodeV0 implements Code {
return 0;
}
@Override
public int getSubcontainerCount() {
return 0;
}
@Override
public Optional<Code> getSubContainer(final int index, final Bytes auxData) {
return Optional.empty();
}
@Override
public Bytes getData(final int offset, final int length) {
return Bytes.EMPTY;
}
/**
* Calculate jump destination.
*
@ -310,4 +331,9 @@ public class CodeV0 implements Code {
public int readU8(final int index) {
return bytes.toArrayUnsafe()[index] & 0xff;
}
@Override
public String prettyPrint() {
return bytes.toHexString();
}
}

@ -124,7 +124,7 @@ public final class CodeV1Validation {
final byte[] rawCode = code.toArrayUnsafe();
OpcodeInfo opcodeInfo = V1_OPCODES[0xfe];
int pos = 0;
EOFContainerMode eofContainerMode = eofLayout.createMode().get();
EOFContainerMode eofContainerMode = eofLayout.containerMode().get();
boolean hasReturningOpcode = false;
while (pos < size) {
final int operationNum = rawCode[pos] & 0xff;
@ -139,7 +139,7 @@ public final class CodeV1Validation {
case StopOperation.OPCODE, ReturnOperation.OPCODE:
if (eofContainerMode == null) {
eofContainerMode = RUNTIME;
eofLayout.createMode().set(RUNTIME);
eofLayout.containerMode().set(RUNTIME);
} else if (!eofContainerMode.equals(RUNTIME)) {
return format(
"%s is only a valid opcode in containers used for runtime operations.",
@ -272,9 +272,9 @@ public final class CodeV1Validation {
opcodeInfo.name(), subcontainerNum, pos - opcodeInfo.pcAdvance());
}
EOFLayout subContainer = eofLayout.getSubcontainer(subcontainerNum);
var subcontainerMode = subContainer.createMode().get();
var subcontainerMode = subContainer.containerMode().get();
if (subcontainerMode == null) {
subContainer.createMode().set(INITCODE);
subContainer.containerMode().set(INITCODE);
} else if (subcontainerMode == RUNTIME) {
return format(
"subcontainer %d cannot be used both as initcode and runtime", subcontainerNum);
@ -291,7 +291,7 @@ public final class CodeV1Validation {
case ReturnContractOperation.OPCODE:
if (eofContainerMode == null) {
eofContainerMode = INITCODE;
eofLayout.createMode().set(INITCODE);
eofLayout.containerMode().set(INITCODE);
} else if (!eofContainerMode.equals(INITCODE)) {
return format(
"%s is only a valid opcode in containers used for initcode", opcodeInfo.name());
@ -308,9 +308,9 @@ public final class CodeV1Validation {
opcodeInfo.name(), returnedContractNum, pos - opcodeInfo.pcAdvance());
}
EOFLayout returnedContract = eofLayout.getSubcontainer(returnedContractNum);
var returnedContractMode = returnedContract.createMode().get();
var returnedContractMode = returnedContract.containerMode().get();
if (returnedContractMode == null) {
returnedContract.createMode().set(RUNTIME);
returnedContract.containerMode().set(RUNTIME);
} else if (returnedContractMode.equals(INITCODE)) {
return format(
"subcontainer %d cannot be used both as initcode and runtime", returnedContractNum);
@ -398,8 +398,8 @@ public final class CodeV1Validation {
int unusedBytes = codeLength;
int currentPC = 0;
int current_min = initialStackHeight;
int current_max = initialStackHeight;
int currentMin = initialStackHeight;
int currentMax = initialStackHeight;
while (currentPC < codeLength) {
int thisOp = code[currentPC] & 0xff;
@ -460,66 +460,66 @@ public final class CodeV1Validation {
codeSectionToValidate, currentPC);
}
if (stackInputs > current_min) {
if (stackInputs > currentMin) {
return format(
"Operation 0x%02X requires stack of %d but may only have %d items",
thisOp, stackInputs, current_min);
thisOp, stackInputs, currentMin);
}
int stackDelta = stackOutputs - stackInputs;
current_max = current_max + stackDelta;
current_min = current_min + stackDelta;
if (current_max + sectionStackUsed - stackOutputs > MAX_STACK_HEIGHT) {
currentMax = currentMax + stackDelta;
currentMin = currentMin + stackDelta;
if (currentMax + sectionStackUsed - stackOutputs > MAX_STACK_HEIGHT) {
return "Stack height exceeds 1024";
}
unusedBytes -= pcAdvance;
maxStackHeight = max(maxStackHeight, current_max);
maxStackHeight = max(maxStackHeight, currentMax);
switch (thisOp) {
case RelativeJumpOperation.OPCODE:
int jValue = readBigEndianI16(currentPC + 1, code);
int targetPC = nextPC + jValue;
if (targetPC > currentPC) {
stack_min[targetPC] = min(stack_min[targetPC], current_min);
stack_max[targetPC] = max(stack_max[targetPC], current_max);
stack_min[targetPC] = min(stack_min[targetPC], currentMin);
stack_max[targetPC] = max(stack_max[targetPC], currentMax);
} else {
if (stack_min[targetPC] != current_min) {
if (stack_min[targetPC] != currentMin) {
return format(
"Stack minimum violation on backwards jump from %d to %d, %d != %d",
currentPC, targetPC, stack_min[currentPC], current_max);
currentPC, targetPC, stack_min[currentPC], currentMax);
}
if (stack_max[targetPC] != current_max) {
if (stack_max[targetPC] != currentMax) {
return format(
"Stack maximum violation on backwards jump from %d to %d, %d != %d",
currentPC, targetPC, stack_max[currentPC], current_max);
currentPC, targetPC, stack_max[currentPC], currentMax);
}
}
// terminal op, reset current_min and current_max to forward set values
// terminal op, reset currentMin and currentMax to forward set values
if (nextPC < codeLength) {
current_max = stack_max[nextPC];
current_min = stack_min[nextPC];
currentMax = stack_max[nextPC];
currentMin = stack_min[nextPC];
}
break;
case RelativeJumpIfOperation.OPCODE:
stack_max[nextPC] = max(stack_max[nextPC], current_max);
stack_min[nextPC] = min(stack_min[nextPC], current_min);
stack_max[nextPC] = max(stack_max[nextPC], currentMax);
stack_min[nextPC] = min(stack_min[nextPC], currentMin);
int jiValue = readBigEndianI16(currentPC + 1, code);
int targetPCi = nextPC + jiValue;
if (targetPCi > currentPC) {
stack_min[targetPCi] = min(stack_min[targetPCi], current_min);
stack_max[targetPCi] = max(stack_max[targetPCi], current_max);
stack_min[targetPCi] = min(stack_min[targetPCi], currentMin);
stack_max[targetPCi] = max(stack_max[targetPCi], currentMax);
} else {
if (stack_min[targetPCi] != current_min) {
if (stack_min[targetPCi] != currentMin) {
return format(
"Stack minimum violation on backwards jump from %d to %d, %d != %d",
currentPC, targetPCi, stack_min[currentPC], current_min);
currentPC, targetPCi, stack_min[currentPC], currentMin);
}
if (stack_max[targetPCi] != current_max) {
if (stack_max[targetPCi] != currentMax) {
return format(
"Stack maximum violation on backwards jump from %d to %d, %d != %d",
currentPC, targetPCi, stack_max[currentPC], current_max);
currentPC, targetPCi, stack_max[currentPC], currentMax);
}
}
break;
@ -528,88 +528,88 @@ public final class CodeV1Validation {
unusedBytes -= immediateDataSize + 2;
int tableEnd = immediateDataSize + currentPC + 4;
nextPC = tableEnd;
stack_max[nextPC] = max(stack_max[nextPC], current_max);
stack_min[nextPC] = min(stack_min[nextPC], current_min);
stack_max[nextPC] = max(stack_max[nextPC], currentMax);
stack_min[nextPC] = min(stack_min[nextPC], currentMin);
for (int i = currentPC + 2; i < tableEnd; i += 2) {
int vValue = readBigEndianI16(i, code);
int targetPCv = tableEnd + vValue;
if (targetPCv > currentPC) {
stack_min[targetPCv] = min(stack_min[targetPCv], current_min);
stack_max[targetPCv] = max(stack_max[targetPCv], current_max);
stack_min[targetPCv] = min(stack_min[targetPCv], currentMin);
stack_max[targetPCv] = max(stack_max[targetPCv], currentMax);
} else {
if (stack_min[targetPCv] != current_min) {
if (stack_min[targetPCv] != currentMin) {
return format(
"Stack minimum violation on backwards jump from %d to %d, %d != %d",
currentPC, targetPCv, stack_min[currentPC], current_min);
currentPC, targetPCv, stack_min[currentPC], currentMin);
}
if (stack_max[targetPCv] != current_max) {
if (stack_max[targetPCv] != currentMax) {
return format(
"Stack maximum violation on backwards jump from %d to %d, %d != %d",
currentPC, targetPCv, stack_max[currentPC], current_max);
currentPC, targetPCv, stack_max[currentPC], currentMax);
}
}
}
break;
case RetFOperation.OPCODE:
int returnStackItems = toValidate.getOutputs();
if (current_min != current_max) {
if (currentMin != currentMax) {
return format(
"RETF in section %d has a stack range (%d/%d)and must have only one stack value",
codeSectionToValidate, current_min, current_max);
codeSectionToValidate, currentMin, currentMax);
}
if (stack_min[currentPC] != returnStackItems
|| stack_min[currentPC] != stack_max[currentPC]) {
return format(
"RETF in section %d calculated height %d does not match configured return stack %d, min height %d, and max height %d",
codeSectionToValidate,
current_min,
currentMin,
returnStackItems,
stack_min[currentPC],
stack_max[currentPC]);
}
// terminal op, reset current_min and current_max to forward set values
// terminal op, reset currentMin and currentMax to forward set values
if (nextPC < codeLength) {
current_max = stack_max[nextPC];
current_min = stack_min[nextPC];
currentMax = stack_max[nextPC];
currentMin = stack_min[nextPC];
}
break;
case JumpFOperation.OPCODE:
int jumpFTargetSectionNum = readBigEndianI16(currentPC + 1, code);
workList.put(jumpFTargetSectionNum);
CodeSection targetCs = eofLayout.getCodeSection(jumpFTargetSectionNum);
if (current_max + targetCs.getMaxStackHeight() - targetCs.getInputs()
if (currentMax + targetCs.getMaxStackHeight() - targetCs.getInputs()
> MAX_STACK_HEIGHT) {
return format(
"JUMPF at section %d pc %d would exceed maximum stack with %d items",
codeSectionToValidate,
currentPC,
current_max + targetCs.getMaxStackHeight() - targetCs.getInputs());
currentMax + targetCs.getMaxStackHeight() - targetCs.getInputs());
}
if (targetCs.isReturning()) {
if (current_min != current_max) {
if (currentMin != currentMax) {
return format(
"JUMPF at section %d pc %d has a variable stack height %d/%d",
codeSectionToValidate, currentPC, current_min, current_max);
codeSectionToValidate, currentPC, currentMin, currentMax);
}
if (current_max != toValidate.outputs + targetCs.inputs - targetCs.outputs) {
if (currentMax != toValidate.outputs + targetCs.inputs - targetCs.outputs) {
return format(
"JUMPF at section %d pc %d has incompatible stack height for returning section %d (%d != %d + %d - %d)",
codeSectionToValidate,
currentPC,
jumpFTargetSectionNum,
current_max,
currentMax,
toValidate.outputs,
targetCs.inputs,
targetCs.outputs);
}
} else {
if (current_min < targetCs.getInputs()) {
if (currentMin < targetCs.getInputs()) {
return format(
"JUMPF at section %d pc %d has insufficient minimum stack height for non returning section %d (%d != %d)",
codeSectionToValidate,
currentPC,
jumpFTargetSectionNum,
current_min,
currentMin,
targetCs.inputs);
}
}
@ -619,19 +619,19 @@ public final class CodeV1Validation {
ReturnOperation.OPCODE,
RevertOperation.OPCODE,
InvalidOperation.OPCODE:
// terminal op, reset current_min and current_max to forward set values
// terminal op, reset currentMin and currentMax to forward set values
if (nextPC < codeLength) {
current_max = stack_max[nextPC];
current_min = stack_min[nextPC];
currentMax = stack_max[nextPC];
currentMin = stack_min[nextPC];
}
break;
default:
// Ordinary operations, update stack for next operation
if (nextPC < codeLength) {
current_max = max(stack_max[nextPC], current_max);
stack_max[nextPC] = current_max;
current_min = min(stack_min[nextPC], current_min);
stack_min[nextPC] = min(stack_min[nextPC], current_min);
currentMax = max(stack_max[nextPC], currentMax);
stack_max[nextPC] = currentMax;
currentMin = min(stack_min[nextPC], currentMin);
stack_min[nextPC] = min(stack_min[nextPC], currentMin);
}
break;
}

@ -45,6 +45,7 @@ import org.apache.tuweni.bytes.Bytes;
* be larger than the data in the data field. Zero if invalid.
* @param data The data hard coded in the container. Empty if invalid.
* @param invalidReason If the raw container is invalid, the reason it is invalid. Null if valid.
* @param containerMode The mode of the container (runtime or initcode, if known)
*/
public record EOFLayout(
Bytes container,
@ -54,7 +55,7 @@ public record EOFLayout(
int dataLength,
Bytes data,
String invalidReason,
AtomicReference<EOFContainerMode> createMode) {
AtomicReference<EOFContainerMode> containerMode) {
enum EOFContainerMode {
UNKNOWN,

@ -508,7 +508,7 @@ public class EVMExecutor {
}
/**
* Instantiate Osaka evm executor.
* Instantiate PragueEOF evm executor.
*
* @param chainId the chain ID
* @param evmConfiguration the evm configuration

@ -130,7 +130,7 @@ public abstract class AbstractCreateOperation extends AbstractOperation {
}
/**
* How many bytes does thsi operation occupy?
* How many bytes does this operation occupy?
*
* @return The number of bytes the operation and immediate arguments occupy
*/

@ -51,26 +51,29 @@ public class RelativeJumpVectorOperation extends AbstractFixedCostOperation {
} catch (ArithmeticException | IllegalArgumentException ae) {
offsetCase = Integer.MAX_VALUE;
}
final int vectorSize = code.readU8(frame.getPC() + 1);
final int vectorSize = getVectorSize(code.getBytes(), frame.getPC() + 1);
int jumpDelta =
(offsetCase < vectorSize)
? code.readBigEndianI16(
frame.getPC() + 2 + offsetCase * 2) // lookup delta if offset is in vector
: 0; // if offsetCase is outside the vector the jump delta is zero / next opcode.
return new OperationResult(
gasCost,
null,
1
+ 2 * vectorSize
+ ((offsetCase > vectorSize)
? 0
: code.readBigEndianI16(frame.getPC() + 2 + offsetCase * 2))
+ 3);
2 // Opcode + length immediate
+ 2 * vectorSize // vector size
+ jumpDelta);
}
/**
* Gets vector size.
* Gets vector size. Vector size is one greater than length immediate, because (a) zero length
* tables are useless and (b) it allows for 256 byte tables
*
* @param code the code
* @param offsetCountByteIndex the offset count byte index
* @return the vector size
*/
public static int getVectorSize(final Bytes code, final int offsetCountByteIndex) {
return (code.get(offsetCountByteIndex) & 0xff) + 1;
return (code.toArrayUnsafe()[offsetCountByteIndex] & 0xff) + 1;
}
}

@ -14,8 +14,21 @@
*/
package org.hyperledger.besu.evm.gascalculator;
public class PragueEOFGasCalculatorTest {
import static org.assertj.core.api.Assertions.assertThat;
// EXTCALL tests will show up here...
import org.hyperledger.besu.datatypes.Address;
import org.junit.jupiter.api.Test;
class PragueEOFGasCalculatorTest {
@Test
void testPrecompileSize() {
PragueEOFGasCalculator subject = new PragueEOFGasCalculator();
assertThat(subject.isPrecompile(Address.precompiled(0x14))).isFalse();
assertThat(subject.isPrecompile(Address.BLS12_MAP_FP2_TO_G2)).isTrue();
}
// EXTCALL gas tests will show up here once EXTCALL gass schedule is finalized...
}

Loading…
Cancel
Save