mirror of https://github.com/hyperledger/besu
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
parent
4a4b7900fa
commit
b6c0ee2b2b
@ -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; |
||||
}; |
||||
} |
||||
} |
@ -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); |
||||
} |
||||
} |
Loading…
Reference in new issue