Merge branch 'main' into zkbesu

This commit is contained in:
Fabio Di Fabio
2025-01-20 11:20:00 +01:00
397 changed files with 1445 additions and 1057 deletions

View File

@@ -16,7 +16,6 @@ package org.hyperledger.besu.evm.account;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.evm.gascalculator.GasCalculator;
import org.hyperledger.besu.evm.worldstate.WorldUpdater;
@@ -24,14 +23,14 @@ import java.util.Optional;
import org.apache.tuweni.bytes.Bytes;
class BaseDelegatedCodeAccount {
abstract class AbstractDelegatedCodeAccount implements Account {
private final WorldUpdater worldUpdater;
private final GasCalculator gasCalculator;
/** The address of the account that has delegated code to be loaded into it. */
protected final Address delegatedCodeAddress;
protected BaseDelegatedCodeAccount(
protected AbstractDelegatedCodeAccount(
final WorldUpdater worldUpdater,
final Address delegatedCodeAddress,
final GasCalculator gasCalculator) {
@@ -45,7 +44,8 @@ class BaseDelegatedCodeAccount {
*
* @return the delegated code.
*/
protected Bytes getCode() {
@Override
public Optional<Bytes> getDelegatedCode() {
return resolveDelegatedCode();
}
@@ -54,27 +54,9 @@ class BaseDelegatedCodeAccount {
*
* @return the hash of the delegated code.
*/
protected Hash getCodeHash() {
final Bytes code = getCode();
return (code == null || code.isEmpty()) ? Hash.EMPTY : Hash.hash(code);
}
/**
* Returns the balance of the delegated account.
*
* @return the balance of the delegated account.
*/
protected Wei getDelegatedBalance() {
return getDelegatedAccount().map(Account::getBalance).orElse(Wei.ZERO);
}
/**
* Returns the nonce of the delegated account.
*
* @return the nonce of the delegated account.
*/
protected long getDelegatedNonce() {
return getDelegatedAccount().map(Account::getNonce).orElse(Account.DEFAULT_NONCE);
@Override
public Optional<Hash> getDelegatedCodeHash() {
return getDelegatedCode().map(Hash::hash);
}
/**
@@ -82,19 +64,27 @@ class BaseDelegatedCodeAccount {
*
* @return the address of the delegated code.
*/
protected Optional<Address> delegatedCodeAddress() {
@Override
public Optional<Address> delegatedCodeAddress() {
return Optional.of(delegatedCodeAddress);
}
@Override
public boolean hasDelegatedCode() {
return true;
}
private Optional<Account> getDelegatedAccount() {
return Optional.ofNullable(worldUpdater.getAccount(delegatedCodeAddress));
}
private Bytes resolveDelegatedCode() {
if (gasCalculator.isPrecompile(delegatedCodeAddress)) {
return Bytes.EMPTY;
private Optional<Bytes> resolveDelegatedCode() {
final Optional<Account> maybeDelegatedAccount = getDelegatedAccount();
if (gasCalculator.isPrecompile(delegatedCodeAddress) || maybeDelegatedAccount.isEmpty()) {
return Optional.of(Bytes.EMPTY);
}
return getDelegatedAccount().map(Account::getUnprocessedCode).orElse(Bytes.EMPTY);
return Optional.of(maybeDelegatedAccount.get().getCode());
}
}

View File

@@ -19,8 +19,6 @@ import org.hyperledger.besu.datatypes.Wei;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes;
/**
* A world state account.
*
@@ -71,13 +69,4 @@ public interface Account extends AccountState {
default boolean hasDelegatedCode() {
return false;
}
/**
* Returns the code as it is stored in the trie even if it's a delegated code account.
*
* @return the code as it is stored in the trie.
*/
default Bytes getUnprocessedCode() {
return getCode();
}
}

View File

@@ -18,6 +18,7 @@ import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.Wei;
import java.util.NavigableMap;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
@@ -74,6 +75,15 @@ public interface AccountState {
*/
Bytes getCode();
/**
* The optional EVM bytecode if the account has set a 7702 code delegation.
*
* @return the delegated code (which may be empty).
*/
default Optional<Bytes> getDelegatedCode() {
return Optional.empty();
}
/**
* The hash of the EVM bytecode associated with this account.
*
@@ -81,6 +91,15 @@ public interface AccountState {
*/
Hash getCodeHash();
/**
* The optional hash of the delegated EVM bytecode if the account has set a 7702 code delegation.
*
* @return the hash of the delegated code (which may be empty).
*/
default Optional<Hash> getDelegatedCodeHash() {
return Optional.empty();
}
/**
* Whether the account has (non empty) EVM bytecode associated to it.
*

View File

@@ -28,7 +28,7 @@ import org.apache.tuweni.bytes.Bytes32;
import org.apache.tuweni.units.bigints.UInt256;
/** Wraps an EOA account and includes delegated code to be run on behalf of it. */
public class DelegatedCodeAccount extends BaseDelegatedCodeAccount implements Account {
public class DelegatedCodeAccount extends AbstractDelegatedCodeAccount implements Account {
private final Account wrappedAccount;
@@ -81,17 +81,12 @@ public class DelegatedCodeAccount extends BaseDelegatedCodeAccount implements Ac
@Override
public Bytes getCode() {
return super.getCode();
}
@Override
public Bytes getUnprocessedCode() {
return wrappedAccount.getCode();
}
@Override
public Hash getCodeHash() {
return super.getCodeHash();
return wrappedAccount.getCodeHash();
}
@Override
@@ -106,7 +101,7 @@ public class DelegatedCodeAccount extends BaseDelegatedCodeAccount implements Ac
@Override
public boolean isEmpty() {
return getDelegatedNonce() == 0 && getDelegatedBalance().isZero() && !hasCode();
return wrappedAccount.isEmpty();
}
@Override
@@ -119,9 +114,4 @@ public class DelegatedCodeAccount extends BaseDelegatedCodeAccount implements Ac
final Bytes32 startKeyHash, final int limit) {
return wrappedAccount.storageEntriesFrom(startKeyHash, limit);
}
@Override
public boolean hasDelegatedCode() {
return true;
}
}

View File

@@ -29,7 +29,7 @@ import org.apache.tuweni.bytes.Bytes32;
import org.apache.tuweni.units.bigints.UInt256;
/** Wraps an EOA account and includes delegated code to be run on behalf of it. */
public class MutableDelegatedCodeAccount extends BaseDelegatedCodeAccount
public class MutableDelegatedCodeAccount extends AbstractDelegatedCodeAccount
implements MutableAccount {
private final MutableAccount wrappedAccount;
@@ -83,17 +83,12 @@ public class MutableDelegatedCodeAccount extends BaseDelegatedCodeAccount
@Override
public Bytes getCode() {
return super.getCode();
}
@Override
public Bytes getUnprocessedCode() {
return wrappedAccount.getCode();
}
@Override
public Hash getCodeHash() {
return super.getCodeHash();
return wrappedAccount.getCodeHash();
}
@Override
@@ -108,7 +103,7 @@ public class MutableDelegatedCodeAccount extends BaseDelegatedCodeAccount
@Override
public boolean isEmpty() {
return getDelegatedNonce() == 0 && getDelegatedBalance().isZero() && !hasCode();
return wrappedAccount.isEmpty();
}
@Override
@@ -156,9 +151,4 @@ public class MutableDelegatedCodeAccount extends BaseDelegatedCodeAccount
public void becomeImmutable() {
wrappedAccount.becomeImmutable();
}
@Override
public boolean hasDelegatedCode() {
return true;
}
}

View File

@@ -39,7 +39,8 @@ public class FrontierGasCalculator implements GasCalculator {
private static final long TX_DATA_NON_ZERO_COST = 68L;
private static final long TX_BASE_COST = 21_000L;
/** Minimum base cost that every transaction needs to pay */
protected static final long TX_BASE_COST = 21_000L;
private static final long TX_CREATE_EXTRA_COST = 0L;
@@ -129,18 +130,82 @@ public class FrontierGasCalculator implements GasCalculator {
}
@Override
public long transactionIntrinsicGasCost(final Bytes payload, final boolean isContractCreate) {
public long transactionIntrinsicGasCost(
final Bytes payload, final boolean isContractCreation, final long baselineGas) {
final long dynamicIntrinsicGasCost =
dynamicIntrinsicGasCost(payload, isContractCreation, baselineGas);
if (dynamicIntrinsicGasCost == Long.MIN_VALUE || dynamicIntrinsicGasCost == Long.MAX_VALUE) {
return dynamicIntrinsicGasCost;
}
return clampedAdd(getMinimumTransactionCost(), dynamicIntrinsicGasCost);
}
/**
* Calculates the dynamic part of the intrinsic gas cost
*
* @param payload the call data payload
* @param isContractCreation whether the transaction is a contract creation
* @param baselineGas how much gas is used by access lists and code delegations
* @return the dynamic part of the intrinsic gas cost
*/
protected long dynamicIntrinsicGasCost(
final Bytes payload, final boolean isContractCreation, final long baselineGas) {
final int payloadSize = payload.size();
final long zeroBytes = zeroBytes(payload);
long cost = clampedAdd(callDataCost(payloadSize, zeroBytes), baselineGas);
if (cost == Long.MIN_VALUE || cost == Long.MAX_VALUE) {
return cost;
}
if (isContractCreation) {
cost = clampedAdd(cost, contractCreationCost(payloadSize));
if (cost == Long.MIN_VALUE || cost == Long.MAX_VALUE) {
return cost;
}
}
return cost;
}
/**
* Calculates the cost of the call data
*
* @param payloadSize the total size of the payload
* @param zeroBytes the number of zero bytes in the payload
* @return the cost of the call data
*/
protected long callDataCost(final long payloadSize, final long zeroBytes) {
return clampedAdd(
TX_DATA_NON_ZERO_COST * (payloadSize - zeroBytes), TX_DATA_ZERO_COST * zeroBytes);
}
/**
* Counts the zero bytes in the payload
*
* @param payload the payload
* @return the number of zero bytes in the payload
*/
protected static long zeroBytes(final Bytes payload) {
int zeros = 0;
for (int i = 0; i < payload.size(); i++) {
if (payload.get(i) == 0) {
++zeros;
zeros += 1;
}
}
final int nonZeros = payload.size() - zeros;
return zeros;
}
final long cost = TX_BASE_COST + TX_DATA_ZERO_COST * zeros + TX_DATA_NON_ZERO_COST * nonZeros;
return isContractCreate ? (cost + txCreateExtraGasCost()) : cost;
/**
* Returns the gas cost for contract creation transactions
*
* @param ignored the size of the contract creation code (ignored in Frontier)
* @return the gas cost for contract creation transactions
*/
protected long contractCreationCost(final int ignored) {
return txCreateExtraGasCost();
}
/**
@@ -152,6 +217,11 @@ public class FrontierGasCalculator implements GasCalculator {
return TX_CREATE_EXTRA_COST;
}
@Override
public long transactionFloorCost(final Bytes payload) {
return 0L;
}
@Override
public long codeDepositGasCost(final int codeSize) {
return CODE_DEPOSIT_BYTE_COST * codeSize;
@@ -558,11 +628,6 @@ public class FrontierGasCalculator implements GasCalculator {
return clampedAdd(clampedMultiply(MEMORY_WORD_GAS_COST, length), base);
}
@Override
public long getMaximumTransactionCost(final int size) {
return TX_BASE_COST + TX_DATA_NON_ZERO_COST * size;
}
@Override
public long getMinimumTransactionCost() {
return TX_BASE_COST;
@@ -572,20 +637,21 @@ public class FrontierGasCalculator implements GasCalculator {
public long calculateGasRefund(
final Transaction transaction,
final MessageFrame initialFrame,
final long codeDelegationRefund) {
final long selfDestructRefund =
getSelfDestructRefundAmount() * initialFrame.getSelfDestructs().size();
final long baseRefundGas =
initialFrame.getGasRefund() + selfDestructRefund + codeDelegationRefund;
return refunded(transaction, initialFrame.getRemainingGas(), baseRefundGas);
final long ignoredCodeDelegationRefund) {
final long refundAllowance = calculateRefundAllowance(transaction, initialFrame);
return initialFrame.getRemainingGas() + refundAllowance;
}
private long refunded(
final Transaction transaction, final long gasRemaining, final long gasRefund) {
private long calculateRefundAllowance(
final Transaction transaction, final MessageFrame initialFrame) {
final long selfDestructRefund =
getSelfDestructRefundAmount() * initialFrame.getSelfDestructs().size();
final long executionRefund = initialFrame.getGasRefund() + selfDestructRefund;
// Integer truncation takes care of the floor calculation needed after the divide.
final long maxRefundAllowance =
(transaction.getGasLimit() - gasRemaining) / getMaxRefundQuotient();
final long refundAllowance = Math.min(maxRefundAllowance, gasRefund);
return gasRemaining + refundAllowance;
(transaction.getGasLimit() - initialFrame.getRemainingGas()) / getMaxRefundQuotient();
return Math.min(executionRefund, maxRefundAllowance);
}
}

View File

@@ -543,10 +543,21 @@ public interface GasCalculator {
* encoded binary representation when stored on-chain.
*
* @param transactionPayload The encoded transaction, as bytes
* @param isContractCreate Is this transaction a contract creation transaction?
* @param isContractCreation Is this transaction a contract creation transaction?
* @param baselineGas The gas used by access lists and code delegation authorizations
* @return the transaction's intrinsic gas cost
*/
long transactionIntrinsicGasCost(Bytes transactionPayload, boolean isContractCreate);
long transactionIntrinsicGasCost(
Bytes transactionPayload, boolean isContractCreation, long baselineGas);
/**
* Returns the floor gas cost of a transaction payload, i.e. the minimum gas cost that a
* transaction will be charged based on its calldata. Introduced in EIP-7623 in Prague.
*
* @param transactionPayload The encoded transaction, as bytes
* @return the transaction's floor gas cost
*/
long transactionFloorCost(final Bytes transactionPayload);
/**
* Returns the gas cost of the explicitly declared access list.
@@ -580,15 +591,6 @@ public interface GasCalculator {
return 2;
}
/**
* Maximum Cost of a Transaction of a certain length.
*
* @param size the length of the transaction, in bytes
* @return the maximum gas cost
*/
// what would be the gas for a PMT with hash of all non-zeros
long getMaximumTransactionCost(int size);
/**
* Minimum gas cost of a transaction.
*

View File

@@ -14,9 +14,10 @@
*/
package org.hyperledger.besu.evm.gascalculator;
import static org.hyperledger.besu.evm.internal.Words.clampedAdd;
import java.util.function.Supplier;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.units.bigints.UInt256;
/** The Istanbul gas calculator. */
@@ -24,7 +25,6 @@ public class IstanbulGasCalculator extends PetersburgGasCalculator {
private static final long TX_DATA_ZERO_COST = 4L;
private static final long ISTANBUL_TX_DATA_NON_ZERO_COST = 16L;
private static final long TX_BASE_COST = 21_000L;
private static final long SLOAD_GAS = 800L;
private static final long BALANCE_OPERATION_GAS_COST = 700L;
@@ -42,19 +42,9 @@ public class IstanbulGasCalculator extends PetersburgGasCalculator {
public IstanbulGasCalculator() {}
@Override
public long transactionIntrinsicGasCost(final Bytes payload, final boolean isContractCreation) {
int zeros = 0;
for (int i = 0; i < payload.size(); i++) {
if (payload.get(i) == 0) {
++zeros;
}
}
final int nonZeros = payload.size() - zeros;
final long cost =
TX_BASE_COST + (TX_DATA_ZERO_COST * zeros) + (ISTANBUL_TX_DATA_NON_ZERO_COST * nonZeros);
return isContractCreation ? (cost + txCreateExtraGasCost()) : cost;
protected long callDataCost(final long payloadSize, final long zeroBytes) {
return clampedAdd(
ISTANBUL_TX_DATA_NON_ZERO_COST * (payloadSize - zeroBytes), TX_DATA_ZERO_COST * zeroBytes);
}
@Override
@@ -136,9 +126,4 @@ public class IstanbulGasCalculator extends PetersburgGasCalculator {
public long extCodeHashOperationGasCost() {
return EXTCODE_HASH_COST;
}
@Override
public long getMaximumTransactionCost(final int size) {
return TX_BASE_COST + (ISTANBUL_TX_DATA_NON_ZERO_COST * size);
}
}

View File

@@ -15,8 +15,13 @@
package org.hyperledger.besu.evm.gascalculator;
import static org.hyperledger.besu.datatypes.Address.BLS12_MAP_FP2_TO_G2;
import static org.hyperledger.besu.evm.internal.Words.clampedAdd;
import org.hyperledger.besu.datatypes.CodeDelegation;
import org.hyperledger.besu.datatypes.Transaction;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.apache.tuweni.bytes.Bytes;
/**
* Gas Calculator for Prague
@@ -26,6 +31,8 @@ import org.hyperledger.besu.datatypes.CodeDelegation;
* </UL>
*/
public class PragueGasCalculator extends CancunGasCalculator {
private static final long TOTAL_COST_FLOOR_PER_TOKEN = 10L;
final long existingAccountGasRefund;
/**
@@ -68,4 +75,47 @@ public class PragueGasCalculator extends CancunGasCalculator {
public long calculateDelegateCodeGasRefund(final long alreadyExistingAccounts) {
return existingAccountGasRefund * alreadyExistingAccounts;
}
@Override
public long calculateGasRefund(
final Transaction transaction,
final MessageFrame initialFrame,
final long codeDelegationRefund) {
final long refundAllowance =
calculateRefundAllowance(transaction, initialFrame, codeDelegationRefund);
final long executionGasUsed =
transaction.getGasLimit() - initialFrame.getRemainingGas() - refundAllowance;
final long transactionFloorCost = transactionFloorCost(transaction.getPayload());
final long totalGasUsed = Math.max(executionGasUsed, transactionFloorCost);
return transaction.getGasLimit() - totalGasUsed;
}
private long calculateRefundAllowance(
final Transaction transaction,
final MessageFrame initialFrame,
final long codeDelegationRefund) {
final long selfDestructRefund =
getSelfDestructRefundAmount() * initialFrame.getSelfDestructs().size();
final long executionRefund =
initialFrame.getGasRefund() + selfDestructRefund + codeDelegationRefund;
// Integer truncation takes care of the floor calculation needed after the divide.
final long maxRefundAllowance =
(transaction.getGasLimit() - initialFrame.getRemainingGas()) / getMaxRefundQuotient();
return Math.min(executionRefund, maxRefundAllowance);
}
@Override
public long transactionFloorCost(final Bytes transactionPayload) {
return clampedAdd(
getMinimumTransactionCost(),
tokensInCallData(transactionPayload.size(), zeroBytes(transactionPayload))
* TOTAL_COST_FLOOR_PER_TOKEN);
}
private long tokensInCallData(final long payloadSize, final long zeroBytes) {
// as defined in https://eips.ethereum.org/EIPS/eip-7623#specification
return clampedAdd(zeroBytes, (payloadSize - zeroBytes) * 4);
}
}

View File

@@ -14,11 +14,8 @@
*/
package org.hyperledger.besu.evm.gascalculator;
import static org.hyperledger.besu.evm.internal.Words.clampedAdd;
import static org.hyperledger.besu.evm.internal.Words.numWords;
import org.apache.tuweni.bytes.Bytes;
/** The Shanghai gas calculator. */
public class ShanghaiGasCalculator extends LondonGasCalculator {
@@ -39,13 +36,8 @@ public class ShanghaiGasCalculator extends LondonGasCalculator {
}
@Override
public long transactionIntrinsicGasCost(final Bytes payload, final boolean isContractCreation) {
long intrinsicGasCost = super.transactionIntrinsicGasCost(payload, isContractCreation);
if (isContractCreation) {
return clampedAdd(intrinsicGasCost, initcodeCost(payload.size()));
} else {
return intrinsicGasCost;
}
protected long contractCreationCost(final int initCodeLength) {
return txCreateExtraGasCost() + initcodeCost(initCodeLength);
}
@Override

View File

@@ -15,7 +15,6 @@
package org.hyperledger.besu.evm.operation;
import static org.hyperledger.besu.evm.internal.Words.clampedToLong;
import static org.hyperledger.besu.evm.worldstate.DelegatedCodeGasCostHelper.deductDelegatedCodeGasCost;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Wei;
@@ -192,13 +191,24 @@ public abstract class AbstractCallOperation extends AbstractOperation {
final Account contract = frame.getWorldUpdater().get(to);
if (contract != null) {
final DelegatedCodeGasCostHelper.Result result =
deductDelegatedCodeGasCost(frame, gasCalculator(), contract);
if (result.status() != DelegatedCodeGasCostHelper.Status.SUCCESS) {
return new Operation.OperationResult(
result.gasCost(), ExceptionalHaltReason.INSUFFICIENT_GAS);
if (contract != null && contract.hasDelegatedCode()) {
if (contract.getDelegatedCode().isEmpty()) {
throw new RuntimeException("A delegated code account must have delegated code");
}
if (contract.getDelegatedCodeHash().isEmpty()) {
throw new RuntimeException("A delegated code account must have a delegated code hash");
}
final long delegatedCodeResolutionGas =
DelegatedCodeGasCostHelper.delegatedCodeGasCost(frame, gasCalculator(), contract);
if (frame.getRemainingGas() < delegatedCodeResolutionGas) {
return new Operation.OperationResult(
delegatedCodeResolutionGas, ExceptionalHaltReason.INSUFFICIENT_GAS);
}
frame.decrementRemainingGas(delegatedCodeResolutionGas);
}
final Account account = frame.getWorldUpdater().get(frame.getRecipientAddress());
@@ -218,10 +228,7 @@ public abstract class AbstractCallOperation extends AbstractOperation {
final Bytes inputData = frame.readMutableMemory(inputDataOffset(frame), inputDataLength(frame));
final Code code =
contract == null
? CodeV0.EMPTY_CODE
: evm.getCode(contract.getCodeHash(), contract.getCode());
final Code code = getCode(evm, contract);
// invalid code results in a quick exit
if (!code.isValid()) {
@@ -337,4 +344,23 @@ public abstract class AbstractCallOperation extends AbstractOperation {
return LEGACY_FAILURE_STACK_ITEM;
}
}
/**
* Gets the code from the contract or EOA with delegated code.
*
* @param evm the evm
* @param account the account which needs to be retrieved
* @return the code
*/
protected static Code getCode(final EVM evm, final Account account) {
if (account == null) {
return CodeV0.EMPTY_CODE;
}
if (account.hasDelegatedCode()) {
return evm.getCode(account.getDelegatedCodeHash().get(), account.getDelegatedCode().get());
}
return evm.getCode(account.getCodeHash(), account.getCode());
}
}

View File

@@ -15,14 +15,12 @@
package org.hyperledger.besu.evm.operation;
import static org.hyperledger.besu.evm.internal.Words.clampedAdd;
import static org.hyperledger.besu.evm.worldstate.DelegatedCodeGasCostHelper.deductDelegatedCodeGasCost;
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;
@@ -127,16 +125,27 @@ public abstract class AbstractExtCallOperation extends AbstractCallOperation {
Address to = Words.toAddress(toBytes);
final Account contract = frame.getWorldUpdater().get(to);
if (contract != null) {
final DelegatedCodeGasCostHelper.Result result =
deductDelegatedCodeGasCost(frame, gasCalculator, contract);
if (result.status() != DelegatedCodeGasCostHelper.Status.SUCCESS) {
return new Operation.OperationResult(
result.gasCost(), ExceptionalHaltReason.INSUFFICIENT_GAS);
if (contract != null && contract.hasDelegatedCode()) {
if (contract.getDelegatedCode().isEmpty()) {
throw new RuntimeException("A delegated code account must have delegated code");
}
if (contract.getDelegatedCodeHash().isEmpty()) {
throw new RuntimeException("A delegated code account must have a delegated code hash");
}
final long delegatedCodeResolutionGas =
DelegatedCodeGasCostHelper.delegatedCodeGasCost(frame, gasCalculator(), contract);
if (frame.getRemainingGas() < delegatedCodeResolutionGas) {
return new Operation.OperationResult(
delegatedCodeResolutionGas, ExceptionalHaltReason.INSUFFICIENT_GAS);
}
frame.decrementRemainingGas(delegatedCodeResolutionGas);
}
boolean accountCreation = contract == null && !zeroValue;
boolean accountCreation = (contract == null || contract.isEmpty()) && !zeroValue;
long cost =
clampedAdd(
clampedAdd(
@@ -154,10 +163,7 @@ public abstract class AbstractExtCallOperation extends AbstractCallOperation {
currentGas -= cost;
frame.expandMemory(inputOffset, inputLength);
final Code code =
contract == null
? CodeV0.EMPTY_CODE
: evm.getCode(contract.getCodeHash(), contract.getCode());
final Code code = getCode(evm, contract);
// invalid code results in a quick exit
if (!code.isValid()) {

View File

@@ -16,7 +16,6 @@ package org.hyperledger.besu.evm.operation;
import static org.hyperledger.besu.evm.internal.Words.clampedAdd;
import static org.hyperledger.besu.evm.internal.Words.clampedToLong;
import static org.hyperledger.besu.evm.worldstate.DelegatedCodeGasCostHelper.deductDelegatedCodeGasCost;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.evm.EVM;
@@ -26,7 +25,7 @@ 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 org.hyperledger.besu.evm.worldstate.DelegatedCodeGasCostHelper;
import org.hyperledger.besu.evm.worldstate.DelegateCodeHelper;
import org.apache.tuweni.bytes.Bytes;
@@ -96,16 +95,7 @@ public class ExtCodeCopyOperation extends AbstractOperation {
final Account account = frame.getWorldUpdater().get(address);
if (account != null) {
final DelegatedCodeGasCostHelper.Result result =
deductDelegatedCodeGasCost(frame, gasCalculator(), account);
if (result.status() != DelegatedCodeGasCostHelper.Status.SUCCESS) {
return new Operation.OperationResult(
result.gasCost(), ExceptionalHaltReason.INSUFFICIENT_GAS);
}
}
final Bytes code = account != null ? account.getCode() : Bytes.EMPTY;
final Bytes code = getCode(account);
if (enableEIP3540
&& code.size() >= 2
@@ -118,4 +108,14 @@ public class ExtCodeCopyOperation extends AbstractOperation {
return new OperationResult(cost, null);
}
private static Bytes getCode(final Account account) {
if (account == null) {
return Bytes.EMPTY;
}
return account.hasDelegatedCode()
? DelegateCodeHelper.getDelegatedCodeForRead()
: account.getCode();
}
}

View File

@@ -14,8 +14,6 @@
*/
package org.hyperledger.besu.evm.operation;
import static org.hyperledger.besu.evm.worldstate.DelegatedCodeGasCostHelper.deductDelegatedCodeGasCost;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.evm.EVM;
@@ -27,7 +25,7 @@ import org.hyperledger.besu.evm.gascalculator.GasCalculator;
import org.hyperledger.besu.evm.internal.OverflowException;
import org.hyperledger.besu.evm.internal.UnderflowException;
import org.hyperledger.besu.evm.internal.Words;
import org.hyperledger.besu.evm.worldstate.DelegatedCodeGasCostHelper;
import org.hyperledger.besu.evm.worldstate.DelegateCodeHelper;
import org.apache.tuweni.bytes.Bytes;
@@ -85,19 +83,10 @@ public class ExtCodeHashOperation extends AbstractOperation {
final Account account = frame.getWorldUpdater().get(address);
if (account != null) {
final DelegatedCodeGasCostHelper.Result result =
deductDelegatedCodeGasCost(frame, gasCalculator(), account);
if (result.status() != DelegatedCodeGasCostHelper.Status.SUCCESS) {
return new Operation.OperationResult(
result.gasCost(), ExceptionalHaltReason.INSUFFICIENT_GAS);
}
}
if (account == null || account.isEmpty()) {
frame.pushStackItem(Bytes.EMPTY);
} else {
final Bytes code = account.getCode();
final Bytes code = getCode(account);
if (enableEIP3540
&& code.size() >= 2
&& code.get(0) == EOFLayout.EOF_PREFIX_BYTE
@@ -115,4 +104,12 @@ public class ExtCodeHashOperation extends AbstractOperation {
return new OperationResult(cost(true), ExceptionalHaltReason.TOO_MANY_STACK_ITEMS);
}
}
private static Bytes getCode(final Account account) {
if (!account.hasDelegatedCode()) {
return account.getCode();
}
return DelegateCodeHelper.getDelegatedCodeForRead();
}
}

View File

@@ -14,8 +14,6 @@
*/
package org.hyperledger.besu.evm.operation;
import static org.hyperledger.besu.evm.worldstate.DelegatedCodeGasCostHelper.deductDelegatedCodeGasCost;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.evm.EVM;
import org.hyperledger.besu.evm.account.Account;
@@ -26,7 +24,7 @@ import org.hyperledger.besu.evm.gascalculator.GasCalculator;
import org.hyperledger.besu.evm.internal.OverflowException;
import org.hyperledger.besu.evm.internal.UnderflowException;
import org.hyperledger.besu.evm.internal.Words;
import org.hyperledger.besu.evm.worldstate.DelegatedCodeGasCostHelper;
import org.hyperledger.besu.evm.worldstate.DelegateCodeHelper;
import org.apache.tuweni.bytes.Bytes;
@@ -82,20 +80,11 @@ public class ExtCodeSizeOperation extends AbstractOperation {
} else {
final Account account = frame.getWorldUpdater().get(address);
if (account != null) {
final DelegatedCodeGasCostHelper.Result result =
deductDelegatedCodeGasCost(frame, gasCalculator(), account);
if (result.status() != DelegatedCodeGasCostHelper.Status.SUCCESS) {
return new Operation.OperationResult(
result.gasCost(), ExceptionalHaltReason.INSUFFICIENT_GAS);
}
}
Bytes codeSize;
if (account == null) {
codeSize = Bytes.EMPTY;
} else {
final Bytes code = account.getCode();
final Bytes code = getCode(account);
if (enableEIP3540
&& code.size() >= 2
&& code.get(0) == EOFLayout.EOF_PREFIX_BYTE
@@ -114,4 +103,14 @@ public class ExtCodeSizeOperation extends AbstractOperation {
return new OperationResult(cost(true), ExceptionalHaltReason.TOO_MANY_STACK_ITEMS);
}
}
private static Bytes getCode(final Account account) {
if (account == null) {
return Bytes.EMPTY;
}
return account.hasDelegatedCode()
? DelegateCodeHelper.getDelegatedCodeForRead()
: account.getCode();
}
}

View File

@@ -0,0 +1,59 @@
/*
* 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.worldstate;
import org.hyperledger.besu.datatypes.Address;
import org.apache.tuweni.bytes.Bytes;
/** Helper class for 7702 delegated code interactions */
public class DelegateCodeHelper {
/**
* The designator that is returned when a ExtCode* operation calls a contract with delegated code
*/
public static final Bytes DELEGATED_CODE_DESIGNATOR = Bytes.fromHexString("ef01");
/** The prefix that is used to identify delegated code */
public static final Bytes DELEGATED_CODE_PREFIX = Bytes.fromHexString("ef0100");
/** The size of the delegated code */
public static final int DELEGATED_CODE_SIZE = DELEGATED_CODE_PREFIX.size() + Address.SIZE;
/** create a new DelegateCodeHelper */
public DelegateCodeHelper() {
// empty
}
/**
* Returns if the provided code is delegated code.
*
* @param code the code to check.
* @return {@code true} if the code is delegated code, {@code false} otherwise.
*/
public static boolean hasDelegatedCode(final Bytes code) {
return code != null
&& code.size() == DELEGATED_CODE_SIZE
&& code.slice(0, DELEGATED_CODE_PREFIX.size()).equals(DELEGATED_CODE_PREFIX);
}
/**
* Returns the delegated code designator
*
* @return the hardcoded designator for delegated code: ef01
*/
public static Bytes getDelegatedCodeForRead() {
return DELEGATED_CODE_DESIGNATOR;
}
}

View File

@@ -33,22 +33,6 @@ public class DelegatedCodeGasCostHelper {
// empty constructor
}
/** The status of the operation. */
public enum Status {
/** The operation failed due to insufficient gas. */
INSUFFICIENT_GAS,
/** The operation was successful. */
SUCCESS
}
/**
* The result of the operation.
*
* @param gasCost the gas cost
* @param status of the operation
*/
public record Result(long gasCost, Status status) {}
/**
* Deducts the gas cost for delegated code resolution.
*
@@ -57,26 +41,18 @@ public class DelegatedCodeGasCostHelper {
* @param account the account
* @return the gas cost and result of the operation
*/
public static Result deductDelegatedCodeGasCost(
public static long delegatedCodeGasCost(
final MessageFrame frame, final GasCalculator gasCalculator, final Account account) {
if (!account.hasDelegatedCode()) {
return new Result(0, Status.SUCCESS);
return 0;
}
if (account.delegatedCodeAddress().isEmpty()) {
throw new RuntimeException("A delegated code account must have a delegated code address");
}
final long delegatedCodeResolutionGas =
calculateDelegatedCodeResolutionGas(
frame, gasCalculator, account.delegatedCodeAddress().get());
if (frame.getRemainingGas() < delegatedCodeResolutionGas) {
return new Result(delegatedCodeResolutionGas, Status.INSUFFICIENT_GAS);
}
frame.decrementRemainingGas(delegatedCodeResolutionGas);
return new Result(delegatedCodeResolutionGas, Status.SUCCESS);
return calculateDelegatedCodeResolutionGas(
frame, gasCalculator, account.delegatedCodeAddress().get());
}
private static long calculateDelegatedCodeResolutionGas(

View File

@@ -14,6 +14,9 @@
*/
package org.hyperledger.besu.evm.worldstate;
import static org.hyperledger.besu.evm.worldstate.DelegateCodeHelper.DELEGATED_CODE_PREFIX;
import static org.hyperledger.besu.evm.worldstate.DelegateCodeHelper.hasDelegatedCode;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.evm.account.Account;
import org.hyperledger.besu.evm.account.DelegatedCodeAccount;
@@ -25,8 +28,6 @@ import org.apache.tuweni.bytes.Bytes;
/** A service that manages the code injection of delegated code. */
public class DelegatedCodeService {
private static final Bytes DELEGATED_CODE_PREFIX = Bytes.fromHexString("ef0100");
private static final int DELEGATED_CODE_SIZE = DELEGATED_CODE_PREFIX.size() + Address.SIZE;
private final GasCalculator gasCalculator;
@@ -64,7 +65,7 @@ public class DelegatedCodeService {
* @return {@code true} if the account can set delegated code, {@code false} otherwise.
*/
public boolean canSetDelegatedCode(final Account account) {
return account.getCode().isEmpty() || hasDelegatedCode(account.getUnprocessedCode());
return account.getCode().isEmpty() || hasDelegatedCode(account.getCode());
}
/**
@@ -102,18 +103,6 @@ public class DelegatedCodeService {
worldUpdater, account, resolveDelegatedAddress(account.getCode()), gasCalculator);
}
/**
* Returns if the provided code is delegated code.
*
* @param code the code to check.
* @return {@code true} if the code is delegated code, {@code false} otherwise.
*/
public static boolean hasDelegatedCode(final Bytes code) {
return code != null
&& code.size() == DELEGATED_CODE_SIZE
&& code.slice(0, DELEGATED_CODE_PREFIX.size()).equals(DELEGATED_CODE_PREFIX);
}
private Address resolveDelegatedAddress(final Bytes code) {
return Address.wrap(code.slice(DELEGATED_CODE_PREFIX.size()));
}

View File

@@ -51,11 +51,11 @@ public class FrontierGasCalculatorTest {
// Assert
assertThat(refund)
.isEqualTo(6500L); // 5000 (remaining) + min(1500 (total refund), 19000 (max allowance))
.isEqualTo(6000L); // 5000 (remaining) + min(1000 (execution refund), 47500 (max allowance))
}
@Test
void shouldCalculateRefundWithMultipleSelfDestructs() {
void shouldCalculateRefundWithMultipleSelfDestructsAndIgnoreCodeDelegation() {
// Arrange
Set<Address> selfDestructs = new HashSet<>();
selfDestructs.add(Address.wrap(Bytes.random(20)));
@@ -71,7 +71,8 @@ public class FrontierGasCalculatorTest {
// Assert
assertThat(refund)
.isEqualTo(52500L); // 5000 (remaining) + min(47500 (total refund), 49500 (max allowance))
.isEqualTo(
52500L); // 5000 (remaining) + min(49500 (execution refund), 47500 (max allowance))
}
@Test
@@ -87,7 +88,8 @@ public class FrontierGasCalculatorTest {
// Assert
assertThat(refund)
.isEqualTo(60000L); // 20000 (remaining) + min(101000 (total refund), 40000 (max allowance))
.isEqualTo(
60000L); // 20000 (remaining) + min(101000 (execution refund), 40000 (max allowance))
}
@Test
@@ -99,9 +101,23 @@ public class FrontierGasCalculatorTest {
when(transaction.getGasLimit()).thenReturn(100000L);
// Act
long refund = gasCalculator.calculateGasRefund(transaction, messageFrame, 0L);
long refund =
gasCalculator.calculateGasRefund(
transaction,
messageFrame,
0L); // 0 (remaining) + min(0 (execution refund), 50000 (max allowance))
// Assert
assertThat(refund).isEqualTo(0L);
}
@Test
void transactionFloorCostShouldAlwaysBeZero() {
assertThat(gasCalculator.transactionFloorCost(Bytes.EMPTY)).isEqualTo(0L);
assertThat(gasCalculator.transactionFloorCost(Bytes.random(256))).isEqualTo(0L);
assertThat(gasCalculator.transactionFloorCost(Bytes.repeat((byte) 0x0, Integer.MAX_VALUE)))
.isEqualTo(0L);
assertThat(gasCalculator.transactionFloorCost(Bytes.repeat((byte) 0x1, Integer.MAX_VALUE)))
.isEqualTo(0L);
}
}

View File

@@ -15,22 +15,36 @@
package org.hyperledger.besu.evm.gascalculator;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Transaction;
import org.hyperledger.besu.evm.frame.MessageFrame;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.tuweni.bytes.Bytes;
import org.assertj.core.api.AssertionsForClassTypes;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@ExtendWith(MockitoExtension.class)
class PragueGasCalculatorTest {
private static final long TARGET_BLOB_GAS_PER_BLOCK_PRAGUE = 0xC0000;
private final PragueGasCalculator pragueGasCalculator = new PragueGasCalculator();
@Mock private Transaction transaction;
@Mock private MessageFrame messageFrame;
@Test
void testPrecompileSize() {
@@ -66,4 +80,125 @@ class PragueGasCalculatorTest {
pragueGasCalculator.getBlobGasPerBlob()),
Arguments.of(sixBlobTargetGas, newTargetCount, sixBlobTargetGas));
}
@Test
void shouldCalculateRefundWithCodeDelegationAndNoSelfDestructs() {
// Arrange
when(messageFrame.getSelfDestructs()).thenReturn(Collections.emptySet());
when(messageFrame.getGasRefund()).thenReturn(1000L);
when(messageFrame.getRemainingGas()).thenReturn(5000L);
when(transaction.getGasLimit()).thenReturn(100000L);
when(transaction.getPayload()).thenReturn(Bytes.EMPTY);
// Act
long refund = pragueGasCalculator.calculateGasRefund(transaction, messageFrame, 500L);
// Assert
// execution refund = 1000 + 0 (self destructs) + 500 (code delegation) = 1500
AssertionsForClassTypes.assertThat(refund)
.isEqualTo(6500L); // 5000 (remaining) + min(1500 (execution refund), 19000 (max allowance))
}
@Test
void shouldCalculateRefundWithMultipleSelfDestructs() {
// Arrange
Set<Address> selfDestructs = new HashSet<>();
selfDestructs.add(Address.wrap(Bytes.random(20)));
selfDestructs.add(Address.wrap(Bytes.random(20)));
when(messageFrame.getSelfDestructs()).thenReturn(selfDestructs);
when(messageFrame.getGasRefund()).thenReturn(1000L);
when(messageFrame.getRemainingGas()).thenReturn(5000L);
when(transaction.getGasLimit()).thenReturn(100000L);
when(transaction.getPayload()).thenReturn(Bytes.EMPTY);
// Act
long refund = pragueGasCalculator.calculateGasRefund(transaction, messageFrame, 1000L);
// Assert
// execution refund = 1000 + 0 (self destructs EIP-3529) + 1000 (code delegation) = 2000
AssertionsForClassTypes.assertThat(refund)
.isEqualTo(7000L); // 5000 (remaining) + min(2000 (execution refund), 1900 (max allowance))
}
@Test
void shouldRespectMaxRefundAllowance() {
// Arrange
when(messageFrame.getSelfDestructs()).thenReturn(Collections.emptySet());
when(messageFrame.getGasRefund()).thenReturn(100000L);
when(messageFrame.getRemainingGas()).thenReturn(20000L);
when(transaction.getGasLimit()).thenReturn(100000L);
when(transaction.getPayload()).thenReturn(Bytes.EMPTY);
// Act
long refund = pragueGasCalculator.calculateGasRefund(transaction, messageFrame, 1000L);
// Assert
// execution refund = 100000 + 1000 (code delegation) = 101000
AssertionsForClassTypes.assertThat(refund)
.isEqualTo(
36000L); // 20000 (remaining) + min(101000 (execution refund), 16000 (max allowance))
}
@Test
void shouldHandleZeroValuesCorrectly() {
// Arrange
when(messageFrame.getSelfDestructs()).thenReturn(Collections.emptySet());
when(messageFrame.getGasRefund()).thenReturn(0L);
when(messageFrame.getRemainingGas()).thenReturn(0L);
when(transaction.getGasLimit()).thenReturn(100000L);
when(transaction.getPayload()).thenReturn(Bytes.EMPTY);
// Act
long refund =
pragueGasCalculator.calculateGasRefund(
transaction,
messageFrame,
0L); // 0 (remaining) + min(0 (execution refund), 20000 (max allowance))
// Assert
AssertionsForClassTypes.assertThat(refund).isEqualTo(0L);
}
@Test
void shouldRespectTransactionFloorCost() {
// Arrange
when(messageFrame.getSelfDestructs()).thenReturn(Collections.emptySet());
when(messageFrame.getGasRefund()).thenReturn(100000L);
when(messageFrame.getRemainingGas()).thenReturn(90000L);
when(transaction.getGasLimit()).thenReturn(100000L);
when(transaction.getPayload()).thenReturn(Bytes.EMPTY);
// Act
long refund = pragueGasCalculator.calculateGasRefund(transaction, messageFrame, 1000L);
// Assert
// refund allowance = 16000
// execution gas used = 100000 (gas limit) - 20000 (remaining gas) - 16000 (refund allowance) =
// 64000
// floor cost = 21000 (base cost) + 0 (tokensInCallData * floor cost per token) = 21000
AssertionsForClassTypes.assertThat(refund)
.isEqualTo(
79000L); // 100000 (gas limit) - max(8000 (execution gas used), 21000 (floor cost))
}
@Test
void transactionFloorCostShouldBeAtLeastTransactionBaseCost() {
// floor cost = 21000 (base cost) + 0
AssertionsForClassTypes.assertThat(pragueGasCalculator.transactionFloorCost(Bytes.EMPTY))
.isEqualTo(21000);
// floor cost = 21000 (base cost) + 256 (tokensInCallData) * 10 (cost per token)
AssertionsForClassTypes.assertThat(
pragueGasCalculator.transactionFloorCost(Bytes.repeat((byte) 0x0, 256)))
.isEqualTo(23560L);
// floor cost = 21000 (base cost) + 256 * 4 (tokensInCallData) * 10 (cost per token)
AssertionsForClassTypes.assertThat(
pragueGasCalculator.transactionFloorCost(Bytes.repeat((byte) 0x1, 256)))
.isEqualTo(31240L);
// floor cost = 21000 (base cost) + 5 + (6 * 4) (tokensInCallData) * 10 (cost per token)
AssertionsForClassTypes.assertThat(
pragueGasCalculator.transactionFloorCost(
Bytes.fromHexString("0x0001000100010001000101")))
.isEqualTo(21290L);
}
}