Correct EXT*CALL Gas

Getting EXTCALL and legacy call to work in the same code flow is too
complex. Re-wrote the gas calculation to be ext*call specific.

Signed-off-by: Danno Ferrin <danno@numisight.com>
mega-eof
Danno Ferrin 5 months ago
parent 4a4b7900fa
commit b6c0ee2b2b
  1. 30
      evm/src/main/java/org/hyperledger/besu/evm/gascalculator/FrontierGasCalculator.java
  2. 28
      evm/src/main/java/org/hyperledger/besu/evm/gascalculator/GasCalculator.java
  3. 12
      evm/src/main/java/org/hyperledger/besu/evm/gascalculator/PragueEOFGasCalculator.java
  4. 39
      evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCallOperation.java
  5. 196
      evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractExtCallOperation.java
  6. 69
      evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCallOperation.java
  7. 62
      evm/src/main/java/org/hyperledger/besu/evm/operation/ExtDelegateCallOperation.java
  8. 62
      evm/src/main/java/org/hyperledger/besu/evm/operation/ExtStaticCallOperation.java
  9. 9
      evm/src/test/java/org/hyperledger/besu/evm/gascalculator/PragueEOFGasCalculatorTest.java
  10. 274
      evm/src/test/java/org/hyperledger/besu/evm/operations/ExtCallOperationTest.java
  11. 257
      evm/src/test/java/org/hyperledger/besu/evm/operations/ExtDelegateCallOperationTest.java
  12. 185
      evm/src/test/java/org/hyperledger/besu/evm/operations/ExtStaticCallOperationTest.java
  13. 7
      evm/src/test/java/org/hyperledger/besu/evm/testutils/TestMessageFrameBuilder.java

@ -123,7 +123,9 @@ public class FrontierGasCalculator implements GasCalculator {
private static final long SELF_DESTRUCT_REFUND_AMOUNT = 24_000L;
/** Default constructor. */
public FrontierGasCalculator() {}
public FrontierGasCalculator() {
// Default Constructor, for JavaDoc lint
}
@Override
public long transactionIntrinsicGasCost(final Bytes payload, final boolean isContractCreate) {
@ -215,21 +217,13 @@ public class FrontierGasCalculator implements GasCalculator {
return CALL_OPERATION_BASE_GAS_COST;
}
/**
* Returns the gas cost to transfer funds in a call operation.
*
* @return the gas cost to transfer funds in a call operation
*/
long callValueTransferGasCost() {
@Override
public long callValueTransferGasCost() {
return CALL_VALUE_TRANSFER_GAS_COST;
}
/**
* Returns the gas cost to create a new account.
*
* @return the gas cost to create a new account
*/
long newAccountGasCost() {
@Override
public long newAccountGasCost() {
return NEW_ACCOUNT_GAS_COST;
}
@ -310,6 +304,16 @@ public class FrontierGasCalculator implements GasCalculator {
}
}
@Override
public long getMinRetainedGas() {
return 0;
}
@Override
public long getMinCalleeGas() {
return 0;
}
/**
* Returns the amount of gas the CREATE operation will consume.
*

@ -144,6 +144,20 @@ public interface GasCalculator {
*/
long callOperationBaseGasCost();
/**
* Returns the gas cost to transfer funds in a call operation.
*
* @return the gas cost to transfer funds in a call operation
*/
long callValueTransferGasCost();
/**
* Returns the gas cost to create a new account.
*
* @return the gas cost to create a new account
*/
long newAccountGasCost();
/**
* Returns the gas cost for one of the various CALL operations.
*
@ -256,6 +270,20 @@ public interface GasCalculator {
*/
long gasAvailableForChildCall(MessageFrame frame, long stipend, boolean transfersValue);
/**
* For EXT*CALL, the minimum amount of gas the parent must retain. First described in EIP-7069
*
* @return MIN_RETAINED_GAS
*/
long getMinRetainedGas();
/**
* For EXT*CALL, the minimum amount of gas that a child must receive. First described in EIP-7069
*
* @return MIN_CALLEE_GAS
*/
long getMinCalleeGas();
/**
* Returns the amount of gas the CREATE operation will consume.
*

@ -28,6 +28,9 @@ import static org.hyperledger.besu.datatypes.Address.BLS12_MAP_FP2_TO_G2;
*/
public class PragueEOFGasCalculator extends PragueGasCalculator {
static final long MIN_RETAINED_GAS = 5_000;
static final long MIN_CALLEE_GAS = 2300;
/** Instantiates a new Prague Gas Calculator. */
public PragueEOFGasCalculator() {
this(BLS12_MAP_FP2_TO_G2.toArrayUnsafe()[19]);
@ -42,6 +45,13 @@ public class PragueEOFGasCalculator extends PragueGasCalculator {
super(maxPrecompile);
}
// EXTCALL costing will show up here...
@Override
public long getMinRetainedGas() {
return MIN_RETAINED_GAS;
}
@Override
public long getMinCalleeGas() {
return MIN_CALLEE_GAS;
}
}

@ -24,6 +24,7 @@ import org.hyperledger.besu.evm.account.Account;
import org.hyperledger.besu.evm.code.CodeV0;
import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.frame.MessageFrame.State;
import org.hyperledger.besu.evm.gascalculator.GasCalculator;
import org.apache.tuweni.bytes.Bytes;
@ -42,9 +43,6 @@ public abstract class AbstractCallOperation extends AbstractOperation {
static final Bytes LEGACY_SUCCESS_STACK_ITEM = BYTES_ONE;
static final Bytes LEGACY_FAILURE_STACK_ITEM = Bytes.EMPTY;
static final Bytes EOF1_SUCCESS_STACK_ITEM = Bytes.EMPTY;
static final Bytes EOF1_EXCEPTION_STACK_ITEM = BYTES_ONE;
static final Bytes EOF1_FAILURE_STACK_ITEM = Bytes.of(2);
/**
* Instantiates a new Abstract call operation.
@ -219,16 +217,6 @@ public abstract class AbstractCallOperation extends AbstractOperation {
return new OperationResult(cost, ExceptionalHaltReason.INVALID_CODE, 0);
}
// delegate calls to prior EOF versions are prohibited
if (isDelegate() && frame.getCode().getEofVersion() > code.getEofVersion()) {
// "Light failure" - Push failure and continue execution
frame.popStackItems(getStackItemsConsumed());
frame.pushStackItem(EOF1_EXCEPTION_STACK_ITEM);
// see note in stack depth check about incrementing cost
frame.incrementRemainingGas(cost);
return new OperationResult(cost, null, 1);
}
MessageFrame.builder()
.parentMessageFrame(frame)
.type(MessageFrame.Type.MESSAGE_CALL)
@ -323,24 +311,19 @@ public abstract class AbstractCallOperation extends AbstractOperation {
frame.popStackItems(getStackItemsConsumed());
Bytes resultItem;
if (frame.getCode().getEofVersion() == 1) {
if (childFrame.getState() == MessageFrame.State.COMPLETED_SUCCESS) {
resultItem = EOF1_SUCCESS_STACK_ITEM;
} else if (childFrame.getState() == MessageFrame.State.EXCEPTIONAL_HALT) {
resultItem = EOF1_EXCEPTION_STACK_ITEM;
} else {
resultItem = EOF1_FAILURE_STACK_ITEM;
}
} else {
if (childFrame.getState() == MessageFrame.State.COMPLETED_SUCCESS) {
resultItem = LEGACY_SUCCESS_STACK_ITEM;
} else {
resultItem = LEGACY_FAILURE_STACK_ITEM;
}
}
resultItem = getCallResultStackItem(childFrame);
frame.pushStackItem(resultItem);
final int currentPC = frame.getPC();
frame.setPC(currentPC + 1);
}
Bytes getCallResultStackItem(final MessageFrame childFrame) {
if (childFrame.getState() == State.COMPLETED_SUCCESS) {
return LEGACY_SUCCESS_STACK_ITEM;
} else {
return LEGACY_FAILURE_STACK_ITEM;
}
}
}

@ -0,0 +1,196 @@
/*
* 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.operation;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.evm.Code;
import org.hyperledger.besu.evm.EVM;
import org.hyperledger.besu.evm.account.Account;
import org.hyperledger.besu.evm.code.CodeV0;
import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.gascalculator.GasCalculator;
import org.hyperledger.besu.evm.internal.Words;
import javax.annotation.Nonnull;
import org.apache.tuweni.bytes.Bytes;
/**
* A skeleton class for implementing call operations.
*
* <p>A call operation creates a child message call from the current message context, allows it to
* execute, and then updates the current message context based on its execution.
*/
public abstract class AbstractExtCallOperation extends AbstractCallOperation {
static final int STACK_TO = 0;
public static final Bytes EOF1_SUCCESS_STACK_ITEM = Bytes.EMPTY;
public static final Bytes EOF1_EXCEPTION_STACK_ITEM = BYTES_ONE;
public static final Bytes EOF1_FAILURE_STACK_ITEM = Bytes.of(2);
/**
* Instantiates a new Abstract call operation.
*
* @param opcode the opcode
* @param name the name
* @param stackItemsConsumed the stack items consumed
* @param stackItemsProduced the stack items produced
* @param gasCalculator the gas calculator
*/
AbstractExtCallOperation(
final int opcode,
final String name,
final int stackItemsConsumed,
final int stackItemsProduced,
final GasCalculator gasCalculator) {
super(opcode, name, stackItemsConsumed, stackItemsProduced, gasCalculator);
}
@Override
protected Address to(final MessageFrame frame) {
return Words.toAddress(frame.getStackItem(STACK_TO));
}
@Override
protected long gas(final MessageFrame frame) {
return Long.MAX_VALUE;
}
@Override
protected long outputDataOffset(final MessageFrame frame) {
return 0;
}
@Override
protected long outputDataLength(final MessageFrame frame) {
return 0;
}
@Override
public long gasAvailableForChildCall(final MessageFrame frame) {
throw new UnsupportedOperationException("EXTCALL does not use gasAvailableForChildCall");
}
@Override
public OperationResult execute(final MessageFrame frame, final EVM evm) {
final Bytes toBytes = frame.getStackItem(STACK_TO).trimLeadingZeros();
final Wei value = value(frame);
final boolean zeroValue = value.isZero();
long inputOffset = inputDataOffset(frame);
long inputLength = inputDataLength(frame);
if (!zeroValue && isStatic(frame)) {
return new OperationResult(
gasCalculator().callValueTransferGasCost(), ExceptionalHaltReason.ILLEGAL_STATE_CHANGE);
}
if (toBytes.size() > Address.SIZE) {
return new OperationResult(
gasCalculator().memoryExpansionGasCost(frame, inputOffset, inputLength)
+ (zeroValue ? 0 : gasCalculator().callValueTransferGasCost())
+ gasCalculator().getColdAccountAccessCost(),
ExceptionalHaltReason.ADDRESS_OUT_OF_RANGE);
}
Address to = Words.toAddress(toBytes);
final Account contract = frame.getWorldUpdater().get(to);
boolean accountCreation = contract == null && !zeroValue;
long cost =
gasCalculator().memoryExpansionGasCost(frame, inputOffset, inputLength)
+ (zeroValue ? 0 : gasCalculator().callValueTransferGasCost())
+ (frame.warmUpAddress(to)
? gasCalculator().getWarmStorageReadCost()
: gasCalculator().getColdAccountAccessCost())
+ (accountCreation ? gasCalculator().newAccountGasCost() : 0);
long currentGas = frame.getRemainingGas() - cost;
if (currentGas < 0) {
return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS);
}
final Code code =
contract == null
? CodeV0.EMPTY_CODE
: evm.getCode(contract.getCodeHash(), contract.getCode());
// invalid code results in a quick exit
if (!code.isValid()) {
return new OperationResult(cost, ExceptionalHaltReason.INVALID_CODE, 0);
}
// last exceptional failure, prepare for call or soft failures
frame.clearReturnData();
// delegate calls to prior EOF versions are prohibited
if (isDelegate() && frame.getCode().getEofVersion() != code.getEofVersion()) {
return softFailure(frame, cost);
}
long retainedGas = Math.max(currentGas / 64, gasCalculator().getMinRetainedGas());
long childGas = currentGas - retainedGas;
final Account account = frame.getWorldUpdater().get(frame.getRecipientAddress());
final Wei balance = (zeroValue || account == null) ? Wei.ZERO : account.getBalance();
// There myst be a minimum gas for a call to have access to.
if (childGas < gasCalculator().getMinRetainedGas()) {
return softFailure(frame, cost);
}
// transferring value you don't have is not a halting exception, just a failure
if (!zeroValue && (value.compareTo(balance) > 0)) {
return softFailure(frame, cost);
}
// stack too deep, for large gas systems.
if (frame.getDepth() >= 1024) {
return softFailure(frame, cost);
}
// all checks passed, do the call
final Bytes inputData = frame.readMutableMemory(inputOffset, inputLength);
MessageFrame.builder()
.parentMessageFrame(frame)
.type(MessageFrame.Type.MESSAGE_CALL)
.initialGas(childGas)
.address(address(frame))
.contract(to)
.inputData(inputData)
.sender(sender(frame))
.value(value(frame))
.apparentValue(apparentValue(frame))
.code(code)
.isStatic(isStatic(frame))
.completer(child -> complete(frame, child))
.build();
frame.setState(MessageFrame.State.CODE_SUSPENDED);
return new OperationResult(cost + childGas, null, 0);
}
private @Nonnull OperationResult softFailure(final MessageFrame frame, final long cost) {
frame.popStackItems(getStackItemsConsumed());
frame.pushStackItem(EOF1_EXCEPTION_STACK_ITEM);
return new OperationResult(cost, null);
}
@Override
Bytes getCallResultStackItem(final MessageFrame childFrame) {
return switch (childFrame.getState()) {
case COMPLETED_SUCCESS -> EOF1_SUCCESS_STACK_ITEM;
case EXCEPTIONAL_HALT -> EOF1_EXCEPTION_STACK_ITEM;
default -> EOF1_FAILURE_STACK_ITEM;
};
}
}

@ -18,17 +18,12 @@ import static org.hyperledger.besu.evm.internal.Words.clampedToLong;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.evm.EVM;
import org.hyperledger.besu.evm.account.Account;
import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.gascalculator.GasCalculator;
import org.hyperledger.besu.evm.internal.Words;
/** The Call operation. */
public class ExtCallOperation extends AbstractCallOperation {
public class ExtCallOperation extends AbstractExtCallOperation {
static final int STACK_TO = 0;
static final int STACK_VALUE = 1;
static final int STACK_INPUT_OFFSET = 2;
static final int STACK_INPUT_LENGTH = 3;
@ -42,16 +37,6 @@ public class ExtCallOperation extends AbstractCallOperation {
super(0xF8, "EXTCALL", 4, 1, gasCalculator);
}
@Override
protected long gas(final MessageFrame frame) {
return Long.MAX_VALUE;
}
@Override
protected Address to(final MessageFrame frame) {
return Words.toAddress(frame.getStackItem(STACK_TO));
}
@Override
protected Wei value(final MessageFrame frame) {
return Wei.wrap(frame.getStackItem(STACK_VALUE));
@ -72,16 +57,6 @@ public class ExtCallOperation extends AbstractCallOperation {
return clampedToLong(frame.getStackItem(STACK_INPUT_LENGTH));
}
@Override
protected long outputDataOffset(final MessageFrame frame) {
return 0;
}
@Override
protected long outputDataLength(final MessageFrame frame) {
return 0;
}
@Override
protected Address address(final MessageFrame frame) {
return to(frame);
@ -91,46 +66,4 @@ public class ExtCallOperation extends AbstractCallOperation {
protected Address sender(final MessageFrame frame) {
return frame.getRecipientAddress();
}
@Override
public long gasAvailableForChildCall(final MessageFrame frame) {
return gasCalculator().gasAvailableForChildCall(frame, gas(frame), !value(frame).isZero());
}
@Override
public long cost(final MessageFrame frame, final boolean accountIsWarm) {
final long inputDataOffset = inputDataOffset(frame);
final long inputDataLength = inputDataLength(frame);
final Account recipient = frame.getWorldUpdater().get(address(frame));
return gasCalculator()
.callOperationGasCost(
frame,
Long.MAX_VALUE,
inputDataOffset,
inputDataLength,
0,
0,
value(frame),
recipient,
to(frame),
accountIsWarm);
}
@Override
public OperationResult execute(final MessageFrame frame, final EVM evm) {
if (frame.isStatic() && !value(frame).isZero()) {
Address to = to(frame);
final boolean accountIsWarm = frame.warmUpAddress(to) || gasCalculator().isPrecompile(to);
return new OperationResult(
cost(frame, accountIsWarm), ExceptionalHaltReason.ILLEGAL_STATE_CHANGE);
}
var to = frame.getStackItem(STACK_TO);
if (to.trimLeadingZeros().size() > Address.SIZE) {
return new OperationResult(cost(frame, false), ExceptionalHaltReason.ADDRESS_OUT_OF_RANGE);
}
return super.execute(frame, evm);
}
}

@ -18,17 +18,12 @@ import static org.hyperledger.besu.evm.internal.Words.clampedToLong;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.evm.EVM;
import org.hyperledger.besu.evm.account.Account;
import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.gascalculator.GasCalculator;
import org.hyperledger.besu.evm.internal.Words;
/** The Delegate call operation. */
public class ExtDelegateCallOperation extends AbstractCallOperation {
public class ExtDelegateCallOperation extends AbstractExtCallOperation {
static final int STACK_TO = 0;
static final int STACK_INPUT_OFFSET = 1;
static final int STACK_INPUT_LENGTH = 2;
@ -41,16 +36,6 @@ public class ExtDelegateCallOperation extends AbstractCallOperation {
super(0xF9, "EXTDELEGATECALL", 3, 1, gasCalculator);
}
@Override
protected long gas(final MessageFrame frame) {
return Long.MAX_VALUE;
}
@Override
protected Address to(final MessageFrame frame) {
return Words.toAddress(frame.getStackItem(STACK_TO));
}
@Override
protected Wei value(final MessageFrame frame) {
return Wei.ZERO;
@ -71,16 +56,6 @@ public class ExtDelegateCallOperation extends AbstractCallOperation {
return clampedToLong(frame.getStackItem(STACK_INPUT_LENGTH));
}
@Override
protected long outputDataOffset(final MessageFrame frame) {
return 0;
}
@Override
protected long outputDataLength(final MessageFrame frame) {
return 0;
}
@Override
protected Address address(final MessageFrame frame) {
return frame.getRecipientAddress();
@ -91,43 +66,8 @@ public class ExtDelegateCallOperation extends AbstractCallOperation {
return frame.getSenderAddress();
}
@Override
public long gasAvailableForChildCall(final MessageFrame frame) {
return gasCalculator().gasAvailableForChildCall(frame, gas(frame), false);
}
@Override
protected boolean isDelegate() {
return true;
}
@Override
public long cost(final MessageFrame frame, final boolean accountIsWarm) {
final long inputDataOffset = inputDataOffset(frame);
final long inputDataLength = inputDataLength(frame);
final Account recipient = frame.getWorldUpdater().get(address(frame));
return gasCalculator()
.callOperationGasCost(
frame,
Long.MAX_VALUE,
inputDataOffset,
inputDataLength,
0,
0,
Wei.ZERO,
recipient,
to(frame),
accountIsWarm);
}
@Override
public OperationResult execute(final MessageFrame frame, final EVM evm) {
var to = frame.getStackItem(STACK_TO);
if (to.trimLeadingZeros().size() > Address.SIZE) {
return new OperationResult(cost(frame, false), ExceptionalHaltReason.ADDRESS_OUT_OF_RANGE);
}
return super.execute(frame, evm);
}
}

@ -18,17 +18,12 @@ import static org.hyperledger.besu.evm.internal.Words.clampedToLong;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.evm.EVM;
import org.hyperledger.besu.evm.account.Account;
import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.gascalculator.GasCalculator;
import org.hyperledger.besu.evm.internal.Words;
/** The Static call operation. */
public class ExtStaticCallOperation extends AbstractCallOperation {
public class ExtStaticCallOperation extends AbstractExtCallOperation {
static final int STACK_TO = 0;
static final int STACK_INPUT_OFFSET = 1;
static final int STACK_INPUT_LENGTH = 2;
@ -41,16 +36,6 @@ public class ExtStaticCallOperation extends AbstractCallOperation {
super(0xFB, "EXTSTATICCALL", 3, 1, gasCalculator);
}
@Override
protected long gas(final MessageFrame frame) {
return Long.MAX_VALUE;
}
@Override
protected Address to(final MessageFrame frame) {
return Words.toAddress(frame.getStackItem(STACK_TO));
}
@Override
protected Wei value(final MessageFrame frame) {
return Wei.ZERO;
@ -71,16 +56,6 @@ public class ExtStaticCallOperation extends AbstractCallOperation {
return clampedToLong(frame.getStackItem(STACK_INPUT_LENGTH));
}
@Override
protected long outputDataOffset(final MessageFrame frame) {
return 0;
}
@Override
protected long outputDataLength(final MessageFrame frame) {
return 0;
}
@Override
protected Address address(final MessageFrame frame) {
return to(frame);
@ -91,43 +66,8 @@ public class ExtStaticCallOperation extends AbstractCallOperation {
return frame.getRecipientAddress();
}
@Override
public long gasAvailableForChildCall(final MessageFrame frame) {
return gasCalculator().gasAvailableForChildCall(frame, gas(frame), !value(frame).isZero());
}
@Override
protected boolean isStatic(final MessageFrame frame) {
return true;
}
@Override
public long cost(final MessageFrame frame, final boolean accountIsWarm) {
final long inputDataOffset = inputDataOffset(frame);
final long inputDataLength = inputDataLength(frame);
final Account recipient = frame.getWorldUpdater().get(address(frame));
return gasCalculator()
.callOperationGasCost(
frame,
Long.MAX_VALUE,
inputDataOffset,
inputDataLength,
0,
0,
value(frame),
recipient,
to(frame),
accountIsWarm);
}
@Override
public OperationResult execute(final MessageFrame frame, final EVM evm) {
var to = frame.getStackItem(STACK_TO);
if (to.trimLeadingZeros().size() > Address.SIZE) {
return new OperationResult(cost(frame, false), ExceptionalHaltReason.ADDRESS_OUT_OF_RANGE);
}
return super.execute(frame, evm);
}
}

@ -29,6 +29,13 @@ class PragueEOFGasCalculatorTest {
assertThat(subject.isPrecompile(Address.BLS12_MAP_FP2_TO_G2)).isTrue();
}
// EXTCALL gas tests will show up here once EXTCALL gass schedule is finalized...
@Test
void testNewConstants() {
CancunGasCalculator cancunGas = new CancunGasCalculator();
PragueEOFGasCalculator praugeGasCalculator = new PragueEOFGasCalculator();
assertThat(praugeGasCalculator.getMinCalleeGas()).isGreaterThan(cancunGas.getMinCalleeGas());
assertThat(praugeGasCalculator.getMinRetainedGas())
.isGreaterThan(cancunGas.getMinRetainedGas());
}
}

@ -0,0 +1,274 @@
/*
* 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.operations;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.evm.Code;
import org.hyperledger.besu.evm.EVM;
import org.hyperledger.besu.evm.account.MutableAccount;
import org.hyperledger.besu.evm.code.CodeFactory;
import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.gascalculator.PragueEOFGasCalculator;
import org.hyperledger.besu.evm.operation.AbstractExtCallOperation;
import org.hyperledger.besu.evm.operation.ExtCallOperation;
import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder;
import org.hyperledger.besu.evm.worldstate.WorldUpdater;
import java.util.List;
import org.apache.tuweni.bytes.Bytes;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
public class ExtCallOperationTest {
private final WorldUpdater worldUpdater = mock(WorldUpdater.class);
private final MutableAccount account = mock(MutableAccount.class);
private final EVM evm = mock(EVM.class);
public static final Code SIMPLE_EOF =
CodeFactory.createCode(Bytes.fromHexString("0xEF00010100040200010001040000000080000000"), 1);
public static final Code INVALID_EOF =
CodeFactory.createCode(Bytes.fromHexString("0xEF00010100040200010001040000000080000023"), 1);
private static final Address CONTRACT_ADDRESS = Address.fromHexString("0xc0de");
static Iterable<Arguments> data() {
return List.of(
Arguments.of(
"gas",
99,
100,
99,
ExceptionalHaltReason.INSUFFICIENT_GAS,
CONTRACT_ADDRESS,
true,
true),
Arguments.of(
"gas",
5000,
100,
5000,
null,
AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM,
true,
true),
Arguments.of(
"gas",
7300,
100,
7300,
null,
AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM,
true,
true),
Arguments.of(
"Cold Address",
7300,
2600,
7300,
null,
AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM,
true,
false),
Arguments.of("gas", 64000, 59000, 58900, null, CONTRACT_ADDRESS, true, true),
Arguments.of("gas", 384100, 378100, 378000, null, CONTRACT_ADDRESS, true, true),
Arguments.of(
"Invalid code",
384100,
100,
384100,
ExceptionalHaltReason.INVALID_CODE,
CONTRACT_ADDRESS,
false,
true));
}
@ParameterizedTest(name = "{index}: {0} {1}")
@MethodSource("data")
void gasTest(
final String name,
final long parentGas,
final long chargedGas,
final long childGas,
final ExceptionalHaltReason haltReason,
final Bytes stackItem,
final boolean validCode,
final boolean warmAddress) {
final ExtCallOperation operation = new ExtCallOperation(new PragueEOFGasCalculator());
final var messageFrame =
new TestMessageFrameBuilder()
.initialGas(parentGas)
.pushStackItem(CONTRACT_ADDRESS) // canary for non-returning
.pushStackItem(Bytes.EMPTY)
.pushStackItem(Bytes.EMPTY)
.pushStackItem(Bytes.EMPTY)
.pushStackItem(CONTRACT_ADDRESS)
.worldUpdater(worldUpdater)
.build();
if (warmAddress) {
messageFrame.warmUpAddress(CONTRACT_ADDRESS);
}
when(account.getBalance()).thenReturn(Wei.ZERO);
when(worldUpdater.get(any())).thenReturn(account);
when(worldUpdater.getAccount(any())).thenReturn(account);
when(worldUpdater.updater()).thenReturn(worldUpdater);
when(evm.getCode(any(), any())).thenReturn(validCode ? SIMPLE_EOF : INVALID_EOF);
var result = operation.execute(messageFrame, evm);
assertThat(result.getGasCost()).isEqualTo(chargedGas);
assertThat(result.getHaltReason()).isEqualTo(haltReason);
MessageFrame childFrame = messageFrame.getMessageFrameStack().getFirst();
assertThat(childFrame.getRemainingGas()).isEqualTo(childGas);
MessageFrame parentFrame = messageFrame.getMessageFrameStack().getLast();
assertThat(parentFrame.getStackItem(0)).isEqualTo(stackItem);
}
static Iterable<Arguments> valueData() {
return List.of(
Arguments.of(
"enough value",
40000,
35000,
25900,
null,
CONTRACT_ADDRESS,
Wei.of(100),
Wei.of(200),
false),
Arguments.of(
"static context",
40000,
9000,
40000,
ExceptionalHaltReason.ILLEGAL_STATE_CHANGE,
CONTRACT_ADDRESS,
Wei.of(100),
Wei.of(200),
true),
Arguments.of(
"not enough value",
40000,
9100,
40000,
null,
AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM,
Wei.of(1000),
Wei.of(200),
false),
Arguments.of(
"too little gas",
5000,
9100,
5000,
ExceptionalHaltReason.INSUFFICIENT_GAS,
CONTRACT_ADDRESS,
Wei.of(100),
Wei.of(200),
false));
}
@ParameterizedTest(name = "{index}: {0} {1}")
@MethodSource("valueData")
void callWithValueTest(
final String name,
final long parentGas,
final long chargedGas,
final long childGas,
final ExceptionalHaltReason haltReason,
final Bytes stackItem,
final Wei valueSent,
final Wei valueWeiHave,
final boolean isStatic) {
final ExtCallOperation operation = new ExtCallOperation(new PragueEOFGasCalculator());
final var messageFrame =
new TestMessageFrameBuilder()
.initialGas(parentGas)
.pushStackItem(CONTRACT_ADDRESS) // canary for non-returning
.pushStackItem(Bytes.EMPTY)
.pushStackItem(Bytes.EMPTY)
.pushStackItem(valueSent)
.pushStackItem(CONTRACT_ADDRESS)
.worldUpdater(worldUpdater)
.isStatic(isStatic)
.build();
messageFrame.warmUpAddress(CONTRACT_ADDRESS);
when(account.getBalance()).thenReturn(valueWeiHave);
when(worldUpdater.get(any())).thenReturn(account);
when(worldUpdater.getAccount(any())).thenReturn(account);
when(worldUpdater.updater()).thenReturn(worldUpdater);
when(evm.getCode(any(), any())).thenReturn(SIMPLE_EOF);
var result = operation.execute(messageFrame, evm);
assertThat(result.getGasCost()).isEqualTo(chargedGas);
assertThat(result.getHaltReason()).isEqualTo(haltReason);
MessageFrame childFrame = messageFrame.getMessageFrameStack().getFirst();
assertThat(childFrame.getRemainingGas()).isEqualTo(childGas);
MessageFrame parentFrame = messageFrame.getMessageFrameStack().getLast();
assertThat(parentFrame.getStackItem(0)).isEqualTo(stackItem);
}
@Test
void overflowTest() {
final ExtCallOperation operation = new ExtCallOperation(new PragueEOFGasCalculator());
final var messageFrame =
new TestMessageFrameBuilder()
.initialGas(400000)
.pushStackItem(CONTRACT_ADDRESS) // canary for non-returning
.pushStackItem(Bytes.EMPTY)
.pushStackItem(Bytes.EMPTY)
.pushStackItem(Bytes.EMPTY)
.pushStackItem(CONTRACT_ADDRESS)
.worldUpdater(worldUpdater)
.build();
messageFrame.warmUpAddress(CONTRACT_ADDRESS);
when(account.getBalance()).thenReturn(Wei.ZERO);
when(worldUpdater.get(any())).thenReturn(account);
when(worldUpdater.getAccount(any())).thenReturn(account);
when(worldUpdater.updater()).thenReturn(worldUpdater);
when(evm.getCode(any(), any())).thenReturn(SIMPLE_EOF);
while (messageFrame.getDepth() < 1024) {
messageFrame.getMessageFrameStack().add(messageFrame);
}
var result = operation.execute(messageFrame, evm);
assertThat(result.getGasCost()).isEqualTo(100);
assertThat(result.getHaltReason()).isNull();
MessageFrame childFrame = messageFrame.getMessageFrameStack().getFirst();
assertThat(childFrame.getRemainingGas()).isEqualTo(400000L);
MessageFrame parentFrame = messageFrame.getMessageFrameStack().getLast();
assertThat(parentFrame.getStackItem(0))
.isEqualTo(AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM);
}
}

@ -0,0 +1,257 @@
/*
* 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.operations;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.evm.Code;
import org.hyperledger.besu.evm.EVM;
import org.hyperledger.besu.evm.account.MutableAccount;
import org.hyperledger.besu.evm.code.CodeFactory;
import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.gascalculator.PragueEOFGasCalculator;
import org.hyperledger.besu.evm.operation.AbstractExtCallOperation;
import org.hyperledger.besu.evm.operation.ExtDelegateCallOperation;
import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder;
import org.hyperledger.besu.evm.worldstate.WorldUpdater;
import java.util.List;
import org.apache.tuweni.bytes.Bytes;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
public class ExtDelegateCallOperationTest {
private final WorldUpdater worldUpdater = mock(WorldUpdater.class);
private final MutableAccount account = mock(MutableAccount.class);
// private final MutableAccount targetAccount = mock(MutableAccount.class);
private final EVM evm = mock(EVM.class);
public static final Code SIMPLE_EOF =
CodeFactory.createCode(Bytes.fromHexString("0xEF00010100040200010001040000000080000000"), 1);
public static final Code SIMPLE_LEGACY = CodeFactory.createCode(Bytes.fromHexString("0x00"), 1);
public static final Code EMPTY_CODE = CodeFactory.createCode(Bytes.fromHexString(""), 1);
public static final Code INVALID_EOF =
CodeFactory.createCode(Bytes.fromHexString("0xEF00010100040200010001040000000080000023"), 1);
private static final Address CONTRACT_ADDRESS = Address.fromHexString("0xc0de");
static Iterable<Arguments> data() {
return List.of(
Arguments.of(
"gas",
99,
100,
99,
ExceptionalHaltReason.INSUFFICIENT_GAS,
CONTRACT_ADDRESS,
true,
true),
Arguments.of(
"gas",
5000,
100,
5000,
null,
AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM,
true,
true),
Arguments.of(
"gas",
7300,
100,
7300,
null,
AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM,
true,
true),
Arguments.of(
"Cold Address",
7300,
2600,
7300,
null,
AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM,
true,
false),
Arguments.of("gas", 64000, 59000, 58900, null, CONTRACT_ADDRESS, true, true),
Arguments.of("gas", 384100, 378100, 378000, null, CONTRACT_ADDRESS, true, true),
Arguments.of(
"Invalid code",
384100,
100,
384100,
ExceptionalHaltReason.INVALID_CODE,
CONTRACT_ADDRESS,
false,
true));
}
@ParameterizedTest(name = "{index}: {0} {1}")
@MethodSource("data")
void gasTest(
final String name,
final long parentGas,
final long chargedGas,
final long childGas,
final ExceptionalHaltReason haltReason,
final Bytes stackItem,
final boolean validCode,
final boolean warmAddress) {
final ExtDelegateCallOperation operation =
new ExtDelegateCallOperation(new PragueEOFGasCalculator());
final var messageFrame =
new TestMessageFrameBuilder()
.initialGas(parentGas)
.pushStackItem(CONTRACT_ADDRESS) // canary for non-returning
.pushStackItem(Bytes.EMPTY)
.pushStackItem(Bytes.EMPTY)
.pushStackItem(CONTRACT_ADDRESS)
.worldUpdater(worldUpdater)
.build();
if (warmAddress) {
messageFrame.warmUpAddress(CONTRACT_ADDRESS);
}
when(account.getBalance()).thenReturn(Wei.ZERO);
when(worldUpdater.get(any())).thenReturn(account);
when(worldUpdater.getAccount(any())).thenReturn(account);
when(worldUpdater.updater()).thenReturn(worldUpdater);
when(evm.getCode(any(), any())).thenReturn(validCode ? SIMPLE_EOF : INVALID_EOF);
var result = operation.execute(messageFrame, evm);
assertThat(result.getGasCost()).isEqualTo(chargedGas);
assertThat(result.getHaltReason()).isEqualTo(haltReason);
MessageFrame childFrame = messageFrame.getMessageFrameStack().getFirst();
assertThat(childFrame.getRemainingGas()).isEqualTo(childGas);
MessageFrame parentFrame = messageFrame.getMessageFrameStack().getLast();
assertThat(parentFrame.getStackItem(0)).isEqualTo(stackItem);
}
static Iterable<Arguments> delegateData() {
return List.of(
Arguments.of("EOF", 40000, 35000, 34900L, null, CONTRACT_ADDRESS),
Arguments.of(
"Legacy", 40000, 100, 40000, null, AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM),
Arguments.of(
"Empty",
40000,
100,
40000,
null,
AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM,
CONTRACT_ADDRESS),
Arguments.of(
"EOA", 5000, 100, 5000, null, AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM));
}
@ParameterizedTest(name = "{index}: {0}")
@MethodSource("delegateData")
void callTypes(
final String name,
final long parentGas,
final long chargedGas,
final long childGas,
final ExceptionalHaltReason haltReason,
final Bytes stackItem) {
final ExtDelegateCallOperation operation =
new ExtDelegateCallOperation(new PragueEOFGasCalculator());
final var messageFrame =
new TestMessageFrameBuilder()
.code(SIMPLE_EOF)
.initialGas(parentGas)
.pushStackItem(CONTRACT_ADDRESS) // canary for non-returning
.pushStackItem(Bytes.EMPTY)
.pushStackItem(Bytes.EMPTY)
.pushStackItem(CONTRACT_ADDRESS)
.worldUpdater(worldUpdater)
.build();
messageFrame.warmUpAddress(CONTRACT_ADDRESS);
when(account.getBalance()).thenReturn(Wei.ZERO);
when(worldUpdater.get(TestMessageFrameBuilder.DEFAUT_ADDRESS)).thenReturn(account);
when(worldUpdater.getAccount(TestMessageFrameBuilder.DEFAUT_ADDRESS)).thenReturn(account);
when(account.getBalance()).thenReturn(Wei.ZERO);
when(worldUpdater.get(CONTRACT_ADDRESS)).thenReturn("Empty".equals(name) ? null : account);
when(worldUpdater.getAccount(CONTRACT_ADDRESS))
.thenReturn("Empty".equals(name) ? null : account);
when(evm.getCode(any(), any()))
.thenReturn(
switch (name) {
case "EOF" -> SIMPLE_EOF;
case "Legacy" -> SIMPLE_LEGACY;
default -> EMPTY_CODE;
});
when(worldUpdater.updater()).thenReturn(worldUpdater);
var result = operation.execute(messageFrame, evm);
assertThat(result.getGasCost()).isEqualTo(chargedGas);
assertThat(result.getHaltReason()).isEqualTo(haltReason);
MessageFrame childFrame = messageFrame.getMessageFrameStack().getFirst();
assertThat(childFrame.getRemainingGas()).isEqualTo(childGas);
MessageFrame parentFrame = messageFrame.getMessageFrameStack().getLast();
assertThat(parentFrame.getStackItem(0)).isEqualTo(stackItem);
}
@Test
void overflowTest() {
final ExtDelegateCallOperation operation =
new ExtDelegateCallOperation(new PragueEOFGasCalculator());
final var messageFrame =
new TestMessageFrameBuilder()
.initialGas(400000)
.pushStackItem(CONTRACT_ADDRESS) // canary for non-returning
.pushStackItem(Bytes.EMPTY)
.pushStackItem(Bytes.EMPTY)
.pushStackItem(CONTRACT_ADDRESS)
.worldUpdater(worldUpdater)
.build();
messageFrame.warmUpAddress(CONTRACT_ADDRESS);
when(account.getBalance()).thenReturn(Wei.ZERO);
when(worldUpdater.get(any())).thenReturn(account);
when(worldUpdater.getAccount(any())).thenReturn(account);
when(worldUpdater.updater()).thenReturn(worldUpdater);
when(evm.getCode(any(), any())).thenReturn(SIMPLE_EOF);
while (messageFrame.getDepth() < 1024) {
messageFrame.getMessageFrameStack().add(messageFrame);
}
var result = operation.execute(messageFrame, evm);
assertThat(result.getGasCost()).isEqualTo(100);
assertThat(result.getHaltReason()).isNull();
MessageFrame childFrame = messageFrame.getMessageFrameStack().getFirst();
assertThat(childFrame.getRemainingGas()).isEqualTo(400000L);
MessageFrame parentFrame = messageFrame.getMessageFrameStack().getLast();
assertThat(parentFrame.getStackItem(0))
.isEqualTo(AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM);
}
}

@ -0,0 +1,185 @@
/*
* 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.operations;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.evm.Code;
import org.hyperledger.besu.evm.EVM;
import org.hyperledger.besu.evm.account.MutableAccount;
import org.hyperledger.besu.evm.code.CodeFactory;
import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.gascalculator.PragueEOFGasCalculator;
import org.hyperledger.besu.evm.operation.AbstractExtCallOperation;
import org.hyperledger.besu.evm.operation.ExtStaticCallOperation;
import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder;
import org.hyperledger.besu.evm.worldstate.WorldUpdater;
import java.util.List;
import org.apache.tuweni.bytes.Bytes;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
public class ExtStaticCallOperationTest {
private final WorldUpdater worldUpdater = mock(WorldUpdater.class);
private final MutableAccount account = mock(MutableAccount.class);
private final EVM evm = mock(EVM.class);
public static final Code SIMPLE_EOF =
CodeFactory.createCode(Bytes.fromHexString("0xEF00010100040200010001040000000080000000"), 1);
public static final Code INVALID_EOF =
CodeFactory.createCode(Bytes.fromHexString("0xEF00010100040200010001040000000080000023"), 1);
private static final Address CONTRACT_ADDRESS = Address.fromHexString("0xc0de");
static Iterable<Arguments> data() {
return List.of(
Arguments.of(
"gas",
99,
100,
99,
ExceptionalHaltReason.INSUFFICIENT_GAS,
CONTRACT_ADDRESS,
true,
true),
Arguments.of(
"gas",
5000,
100,
5000,
null,
AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM,
true,
true),
Arguments.of(
"gas",
7300,
100,
7300,
null,
AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM,
true,
true),
Arguments.of(
"Cold Address",
7300,
2600,
7300,
null,
AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM,
true,
false),
Arguments.of("gas", 64000, 59000, 58900, null, CONTRACT_ADDRESS, true, true),
Arguments.of("gas", 384100, 378100, 378000, null, CONTRACT_ADDRESS, true, true),
Arguments.of(
"Invalid code",
384100,
100,
384100,
ExceptionalHaltReason.INVALID_CODE,
CONTRACT_ADDRESS,
false,
true));
}
@ParameterizedTest(name = "{index}: {0} {1}")
@MethodSource("data")
void gasTest(
final String name,
final long parentGas,
final long chargedGas,
final long childGas,
final ExceptionalHaltReason haltReason,
final Bytes stackItem,
final boolean validCode,
final boolean warmAddress) {
final ExtStaticCallOperation operation =
new ExtStaticCallOperation(new PragueEOFGasCalculator());
final var messageFrame =
new TestMessageFrameBuilder()
.initialGas(parentGas)
.pushStackItem(CONTRACT_ADDRESS) // canary for non-returning
.pushStackItem(Bytes.EMPTY)
.pushStackItem(Bytes.EMPTY)
.pushStackItem(CONTRACT_ADDRESS)
.worldUpdater(worldUpdater)
.build();
if (warmAddress) {
messageFrame.warmUpAddress(CONTRACT_ADDRESS);
}
when(account.getBalance()).thenReturn(Wei.ZERO);
when(worldUpdater.get(any())).thenReturn(account);
when(worldUpdater.getAccount(any())).thenReturn(account);
when(worldUpdater.updater()).thenReturn(worldUpdater);
when(evm.getCode(any(), any())).thenReturn(validCode ? SIMPLE_EOF : INVALID_EOF);
var result = operation.execute(messageFrame, evm);
assertThat(result.getGasCost()).isEqualTo(chargedGas);
assertThat(result.getHaltReason()).isEqualTo(haltReason);
MessageFrame childFrame = messageFrame.getMessageFrameStack().getFirst();
assertThat(childFrame.getRemainingGas()).isEqualTo(childGas);
MessageFrame parentFrame = messageFrame.getMessageFrameStack().getLast();
assertThat(parentFrame.getStackItem(0)).isEqualTo(stackItem);
}
@Test
void overflowTest() {
final ExtStaticCallOperation operation =
new ExtStaticCallOperation(new PragueEOFGasCalculator());
final var messageFrame =
new TestMessageFrameBuilder()
.initialGas(400000)
.pushStackItem(CONTRACT_ADDRESS) // canary for non-returning
.pushStackItem(Bytes.EMPTY)
.pushStackItem(Bytes.EMPTY)
.pushStackItem(CONTRACT_ADDRESS)
.worldUpdater(worldUpdater)
.build();
messageFrame.warmUpAddress(CONTRACT_ADDRESS);
when(account.getBalance()).thenReturn(Wei.ZERO);
when(worldUpdater.get(any())).thenReturn(account);
when(worldUpdater.getAccount(any())).thenReturn(account);
when(worldUpdater.updater()).thenReturn(worldUpdater);
when(evm.getCode(any(), any())).thenReturn(SIMPLE_EOF);
while (messageFrame.getDepth() < 1024) {
messageFrame.getMessageFrameStack().add(messageFrame);
}
var result = operation.execute(messageFrame, evm);
assertThat(result.getGasCost()).isEqualTo(100);
assertThat(result.getHaltReason()).isNull();
MessageFrame childFrame = messageFrame.getMessageFrameStack().getFirst();
assertThat(childFrame.getRemainingGas()).isEqualTo(400000L);
MessageFrame parentFrame = messageFrame.getMessageFrameStack().getLast();
assertThat(parentFrame.getStackItem(0))
.isEqualTo(AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM);
}
}

@ -56,6 +56,7 @@ public class TestMessageFrameBuilder {
private final List<Bytes> stackItems = new ArrayList<>();
private Optional<BlockHashLookup> blockHashLookup = Optional.empty();
private Bytes memory = Bytes.EMPTY;
private boolean isStatic = false;
public TestMessageFrameBuilder worldUpdater(final WorldUpdater worldUpdater) {
this.worldUpdater = Optional.of(worldUpdater);
@ -142,6 +143,11 @@ public class TestMessageFrameBuilder {
return this;
}
public TestMessageFrameBuilder isStatic(final boolean isStatic) {
this.isStatic = isStatic;
return this;
}
public MessageFrame build() {
final MessageFrame frame =
MessageFrame.builder()
@ -163,6 +169,7 @@ public class TestMessageFrameBuilder {
.miningBeneficiary(Address.ZERO)
.blockHashLookup(blockHashLookup.orElse(number -> Hash.hash(Words.longBytes(number))))
.maxStackSize(maxStackSize)
.isStatic(isStatic)
.build();
frame.setPC(pc);
frame.setSection(section);

Loading…
Cancel
Save