From b5fdcc096ffbb970641125f0453fd594ca22bc4d Mon Sep 17 00:00:00 2001 From: daniellehrner Date: Thu, 16 Jan 2025 07:20:20 +0100 Subject: [PATCH] Pectra devnet 5: 7702 changes (#8118) * first draft for 7702 changes for pectra-devnet5 Signed-off-by: Daniel Lehrner * spotless, javadoc Signed-off-by: Daniel Lehrner * fixed get code in call operations Signed-off-by: Daniel Lehrner * allow code delegation chain id to be up to 2^256-1 Signed-off-by: Daniel Lehrner * set code for delegated accounts correctly in the initialFrame of the tx processing Signed-off-by: Daniel Lehrner * delegated accounts return empty bytes for the code if the target does not exist Signed-off-by: Daniel Lehrner --------- Signed-off-by: Daniel Lehrner Co-authored-by: Simon Dudley --- .../mainnet/MainnetTransactionProcessor.java | 11 +++- .../mainnet/MainnetTransactionValidator.java | 11 ++-- ...java => AbstractDelegatedCodeAccount.java} | 50 +++++++--------- .../hyperledger/besu/evm/account/Account.java | 11 ---- .../besu/evm/account/AccountState.java | 19 ++++++ .../evm/account/DelegatedCodeAccount.java | 16 +---- .../account/MutableDelegatedCodeAccount.java | 16 +---- .../evm/operation/AbstractCallOperation.java | 46 +++++++++++---- .../operation/AbstractExtCallOperation.java | 28 +++++---- .../evm/operation/ExtCodeCopyOperation.java | 24 ++++---- .../evm/operation/ExtCodeHashOperation.java | 23 ++++---- .../evm/operation/ExtCodeSizeOperation.java | 25 ++++---- .../evm/worldstate/DelegateCodeHelper.java | 59 +++++++++++++++++++ .../DelegatedCodeGasCostHelper.java | 32 ++-------- .../evm/worldstate/DelegatedCodeService.java | 19 ++---- 15 files changed, 213 insertions(+), 177 deletions(-) rename evm/src/main/java/org/hyperledger/besu/evm/account/{BaseDelegatedCodeAccount.java => AbstractDelegatedCodeAccount.java} (65%) create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/worldstate/DelegateCodeHelper.java diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java index 708dd33a29f..46c71b435f0 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java @@ -438,7 +438,16 @@ public TransactionProcessingResult processTransaction( .inputData(transaction.getPayload()) .code( maybeContract - .map(c -> messageCallProcessor.getCodeFromEVM(c.getCodeHash(), c.getCode())) + .map( + c -> { + if (c.hasDelegatedCode()) { + return messageCallProcessor.getCodeFromEVM( + c.getDelegatedCodeHash().get(), c.getDelegatedCode().get()); + } + + return messageCallProcessor.getCodeFromEVM( + c.getCodeHash(), c.getCode()); + }) .orElse(CodeV0.EMPTY_CODE)) .accessListWarmAddresses(warmAddressList) .build(); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidator.java index 2b9fea0f3d0..200fd167ce5 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidator.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.ethereum.mainnet; import static org.hyperledger.besu.evm.account.Account.MAX_NONCE; +import static org.hyperledger.besu.evm.worldstate.DelegateCodeHelper.hasDelegatedCode; import org.hyperledger.besu.crypto.SECPSignature; import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; @@ -32,7 +33,6 @@ import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; import org.hyperledger.besu.evm.account.Account; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.evm.worldstate.DelegatedCodeService; import java.math.BigInteger; import java.util.List; @@ -52,8 +52,6 @@ */ public class MainnetTransactionValidator implements TransactionValidator { - public static final BigInteger TWO_POW_8 = BigInteger.TWO.pow(8); - public static final BigInteger TWO_POW_64 = BigInteger.TWO.pow(64); public static final BigInteger TWO_POW_256 = BigInteger.TWO.pow(256); private final GasCalculator gasCalculator; @@ -166,9 +164,9 @@ private static ValidationResult validateCodeDelegation .map( codeDelegations -> { for (CodeDelegation codeDelegation : codeDelegations) { - if (codeDelegation.chainId().compareTo(TWO_POW_64) >= 0) { + if (codeDelegation.chainId().compareTo(TWO_POW_256) >= 0) { throw new IllegalArgumentException( - "Invalid 'chainId' value, should be < 2^64 but got " + "Invalid 'chainId' value, should be < 2^256 but got " + codeDelegation.chainId()); } @@ -329,8 +327,7 @@ public ValidationResult validateForSender( } private static boolean canSendTransaction(final Account sender, final Hash codeHash) { - return codeHash.equals(Hash.EMPTY) - || DelegatedCodeService.hasDelegatedCode(sender.getUnprocessedCode()); + return codeHash.equals(Hash.EMPTY) || hasDelegatedCode(sender.getCode()); } private ValidationResult validateTransactionSignature( diff --git a/evm/src/main/java/org/hyperledger/besu/evm/account/BaseDelegatedCodeAccount.java b/evm/src/main/java/org/hyperledger/besu/evm/account/AbstractDelegatedCodeAccount.java similarity index 65% rename from evm/src/main/java/org/hyperledger/besu/evm/account/BaseDelegatedCodeAccount.java rename to evm/src/main/java/org/hyperledger/besu/evm/account/AbstractDelegatedCodeAccount.java index 4ee28145def..2318d48650c 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/account/BaseDelegatedCodeAccount.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/account/AbstractDelegatedCodeAccount.java @@ -16,7 +16,6 @@ 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 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 @@ protected BaseDelegatedCodeAccount( * * @return the delegated code. */ - protected Bytes getCode() { + @Override + public Optional getDelegatedCode() { return resolveDelegatedCode(); } @@ -54,27 +54,9 @@ protected Bytes getCode() { * * @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 getDelegatedCodeHash() { + return getDelegatedCode().map(Hash::hash); } /** @@ -82,19 +64,27 @@ protected long getDelegatedNonce() { * * @return the address of the delegated code. */ - protected Optional
delegatedCodeAddress() { + @Override + public Optional
delegatedCodeAddress() { return Optional.of(delegatedCodeAddress); } + @Override + public boolean hasDelegatedCode() { + return true; + } + private Optional getDelegatedAccount() { return Optional.ofNullable(worldUpdater.getAccount(delegatedCodeAddress)); } - private Bytes resolveDelegatedCode() { - if (gasCalculator.isPrecompile(delegatedCodeAddress)) { - return Bytes.EMPTY; + private Optional resolveDelegatedCode() { + final Optional 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()); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/account/Account.java b/evm/src/main/java/org/hyperledger/besu/evm/account/Account.java index 8a65adf617c..f0196c82766 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/account/Account.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/account/Account.java @@ -19,8 +19,6 @@ import java.util.Optional; -import org.apache.tuweni.bytes.Bytes; - /** * A world state account. * @@ -71,13 +69,4 @@ default Optional
delegatedCodeAddress() { 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(); - } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/account/AccountState.java b/evm/src/main/java/org/hyperledger/besu/evm/account/AccountState.java index a1bd0da1826..235f61fbe40 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/account/AccountState.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/account/AccountState.java @@ -18,6 +18,7 @@ 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 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 getDelegatedCodeHash() { + return Optional.empty(); + } + /** * Whether the account has (non empty) EVM bytecode associated to it. * diff --git a/evm/src/main/java/org/hyperledger/besu/evm/account/DelegatedCodeAccount.java b/evm/src/main/java/org/hyperledger/besu/evm/account/DelegatedCodeAccount.java index 0ac969abade..3252dab9c21 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/account/DelegatedCodeAccount.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/account/DelegatedCodeAccount.java @@ -28,7 +28,7 @@ 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 Wei getBalance() { @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 UInt256 getOriginalStorageValue(final UInt256 key) { @Override public boolean isEmpty() { - return getDelegatedNonce() == 0 && getDelegatedBalance().isZero() && !hasCode(); + return wrappedAccount.isEmpty(); } @Override @@ -119,9 +114,4 @@ public NavigableMap storageEntriesFrom( final Bytes32 startKeyHash, final int limit) { return wrappedAccount.storageEntriesFrom(startKeyHash, limit); } - - @Override - public boolean hasDelegatedCode() { - return true; - } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/account/MutableDelegatedCodeAccount.java b/evm/src/main/java/org/hyperledger/besu/evm/account/MutableDelegatedCodeAccount.java index 20894b4f286..9b98d596430 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/account/MutableDelegatedCodeAccount.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/account/MutableDelegatedCodeAccount.java @@ -29,7 +29,7 @@ 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 Wei getBalance() { @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 UInt256 getOriginalStorageValue(final UInt256 key) { @Override public boolean isEmpty() { - return getDelegatedNonce() == 0 && getDelegatedBalance().isZero() && !hasCode(); + return wrappedAccount.isEmpty(); } @Override @@ -156,9 +151,4 @@ public Map getUpdatedStorage() { public void becomeImmutable() { wrappedAccount.becomeImmutable(); } - - @Override - public boolean hasDelegatedCode() { - return true; - } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCallOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCallOperation.java index 932bb4c3915..d2cde85a62f 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCallOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCallOperation.java @@ -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 OperationResult execute(final MessageFrame frame, final EVM evm) { final Account contract = frame.getWorldUpdater().get(to); - if (contract != null) { - final DelegatedCodeGasCostHelper.Result result = - deductDelegatedCodeGasCost(frame, gasCalculator(), contract); - if (result.status() != DelegatedCodeGasCostHelper.Status.SUCCESS) { + 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( - result.gasCost(), ExceptionalHaltReason.INSUFFICIENT_GAS); + delegatedCodeResolutionGas, ExceptionalHaltReason.INSUFFICIENT_GAS); } + + frame.decrementRemainingGas(delegatedCodeResolutionGas); } final Account account = frame.getWorldUpdater().get(frame.getRecipientAddress()); @@ -218,10 +228,7 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { 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 @@ Bytes getCallResultStackItem(final MessageFrame childFrame) { 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()); + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractExtCallOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractExtCallOperation.java index 2c80007c769..1040cb1a86f 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractExtCallOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractExtCallOperation.java @@ -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,13 +125,24 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { 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) { + 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( - result.gasCost(), ExceptionalHaltReason.INSUFFICIENT_GAS); + delegatedCodeResolutionGas, ExceptionalHaltReason.INSUFFICIENT_GAS); } + + frame.decrementRemainingGas(delegatedCodeResolutionGas); } boolean accountCreation = (contract == null || contract.isEmpty()) && !zeroValue; @@ -154,10 +163,7 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { 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()) { diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeCopyOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeCopyOperation.java index ed1e4697778..848cfaaeaa0 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeCopyOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeCopyOperation.java @@ -16,7 +16,6 @@ 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.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 OperationResult execute(final MessageFrame frame, final EVM evm) { 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 OperationResult execute(final MessageFrame frame, final EVM evm) { 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(); + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeHashOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeHashOperation.java index 9a8cfe83865..9157c55aa05 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeHashOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeHashOperation.java @@ -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.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 OperationResult execute(final MessageFrame frame, final EVM evm) { 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 OperationResult execute(final MessageFrame frame, final EVM evm) { 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(); + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeSizeOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeSizeOperation.java index c2795f364b0..8b8ff172542 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeSizeOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeSizeOperation.java @@ -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.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 OperationResult execute(final MessageFrame frame, final EVM evm) { } 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 OperationResult execute(final MessageFrame frame, final EVM evm) { 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(); + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/DelegateCodeHelper.java b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/DelegateCodeHelper.java new file mode 100644 index 00000000000..5331bdd2757 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/DelegateCodeHelper.java @@ -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; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/DelegatedCodeGasCostHelper.java b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/DelegatedCodeGasCostHelper.java index 3cdd2dc205d..480dc8fcb67 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/DelegatedCodeGasCostHelper.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/DelegatedCodeGasCostHelper.java @@ -33,22 +33,6 @@ private 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 record Result(long gasCost, Status status) {} * @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( diff --git a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/DelegatedCodeService.java b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/DelegatedCodeService.java index 770d0b79ddc..d919b9dc691 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/DelegatedCodeService.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/DelegatedCodeService.java @@ -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 @@ /** 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 void processDelegatedCodeAuthorization( * @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 MutableAccount processMutableAccount( 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())); }