Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -158,4 +158,16 @@ public interface CallParameter {
* @return an {@link Optional} containing the payload bytes, or empty if not specified
*/
Optional<Bytes> getPayload();

/**
* Returns the list of code delegation authorizations.
*
* <p>Each authorization represents a signed statement from an externally owned account (EOA)
* permitting a specific contract's code to be used as the EOA's code during transaction
* execution. The EOA remains the transaction sender, but its code is temporarily substituted with
* that of the authorized contract, enabling dynamic behavior similar to smart contract wallets.
*
* @return a {@link List} of {@link CodeDelegation} entries
*/
List<CodeDelegation> getCodeDelegationAuthorizations();
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import org.hyperledger.besu.datatypes.AccessListEntry;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.CodeDelegation;
import org.hyperledger.besu.datatypes.VersionedHash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.core.Transaction;
Expand Down Expand Up @@ -75,6 +76,11 @@ public abstract class CallParameter implements org.hyperledger.besu.datatypes.Ca
@Override
public abstract Optional<List<VersionedHash>> getBlobVersionedHashes();

@Override
@JsonProperty("authorizationList")
@JsonDeserialize(contentAs = org.hyperledger.besu.ethereum.core.CodeDelegation.class)
public abstract List<CodeDelegation> getCodeDelegationAuthorizations();

@Override
@JsonDeserialize(using = OptionalUnsignedLongDeserializer.class)
public abstract OptionalLong getNonce();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.BlobGas;
import org.hyperledger.besu.datatypes.CallParameter;
import org.hyperledger.besu.datatypes.CodeDelegation;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.StateOverride;
import org.hyperledger.besu.datatypes.StateOverrideMap;
Expand All @@ -50,6 +51,7 @@
import org.hyperledger.besu.evm.worldstate.WorldUpdater;

import java.math.BigInteger;
import java.util.List;
import java.util.Optional;
import java.util.OptionalLong;
import java.util.function.BiFunction;
Expand Down Expand Up @@ -616,6 +618,11 @@ private Optional<Transaction> buildTransaction(
transactionBuilder.maxFeePerBlobGas(maxFeePerBlobGas);
}

final List<CodeDelegation> authorizations = callParams.getCodeDelegationAuthorizations();
if (!authorizations.isEmpty()) {
transactionBuilder.codeDelegations(authorizations);
}

transactionBuilder.guessType();

if (transactionBuilder.getTransactionType().requiresChainId()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;

import java.math.BigInteger;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
Expand Down Expand Up @@ -116,4 +118,45 @@ public void extraParametersAreIgnored() throws JsonProcessingException {

assertThat(callParameter.getGas()).hasValue(150);
}

@Test
public void deserializesAuthorizationList() throws JsonProcessingException {
final String json =
"""
{
"authorizationList": [
{
"chainId": "0x1",
"address": "0x6b7879a5d747e30a3adb37a9e41c046928fce933",
"nonce": "0x82",
"v": "0x1",
"r": "0x462a70678128d9dd8f5b8010aaecddda1ba9ad767f0807c341f38d4dcb7eb893",
"s": "0x645f7afd51a86bafe8939c8498fc89769918a38213859843ad7b19ffd4273a48"
}
]
}
""";

final CallParameter callParameter = objectMapper.readValue(json, CallParameter.class);

assertThat(callParameter.getCodeDelegationAuthorizations())
.hasSize(1)
.first()
.satisfies(
auth -> {
assertThat(auth.chainId()).isEqualTo(BigInteger.ONE);
assertThat(auth.address().toHexString())
.isEqualTo("0x6b7879a5d747e30a3adb37a9e41c046928fce933");
assertThat(auth.nonce()).isEqualTo(130L);
assertThat(auth.v()).isEqualTo((byte) 0x1);
assertThat(auth.r())
.isEqualTo(
new BigInteger(
"462a70678128d9dd8f5b8010aaecddda1ba9ad767f0807c341f38d4dcb7eb893", 16));
assertThat(auth.s())
.isEqualTo(
new BigInteger(
"645f7afd51a86bafe8939c8498fc89769918a38213859843ad7b19ffd4273a48", 16));
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.BlockHeaderFunctions;
import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture;
import org.hyperledger.besu.ethereum.core.CodeDelegation;
import org.hyperledger.besu.ethereum.core.Difficulty;
import org.hyperledger.besu.ethereum.core.MiningConfiguration;
import org.hyperledger.besu.ethereum.core.MutableWorldState;
Expand All @@ -59,9 +60,11 @@
import org.hyperledger.besu.evm.account.Account;
import org.hyperledger.besu.evm.account.MutableAccount;
import org.hyperledger.besu.evm.gascalculator.FrontierGasCalculator;
import org.hyperledger.besu.evm.tracing.OperationTracer;
import org.hyperledger.besu.evm.worldstate.WorldUpdater;

import java.math.BigInteger;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalLong;
Expand Down Expand Up @@ -923,6 +926,45 @@ public void shouldSimulateLegacyTransactionWhenBaseFeeNotZero() {
assertThat(result.get().isSuccessful()).isTrue();
}

@Test
public void shouldGuessDelegateCodeTransactionTypeWhenAuthorizationsPresent() {
final CodeDelegation delegation =
new CodeDelegation(BigInteger.ONE, Address.fromHexString("0x1"), 42L, FAKE_SIGNATURE);

final CallParameter callParameter =
codeDelegationTransactionCallParamterBuilder(List.of(delegation)).build();

final BlockHeader blockHeader = mockBlockchainAndWorldState(callParameter);

mockProtocolSpecForProcessWithWorldUpdater();

final Transaction expectedTx =
Transaction.builder()
.chainId(BigInteger.ONE)
.nonce(1L)
.gasLimit(blockHeader.getGasLimit())
.sender(callParameter.getSender().orElseThrow())
.to(callParameter.getTo().orElseThrow())
.value(callParameter.getValue().orElseThrow())
.payload(callParameter.getPayload().orElseThrow())
.signature(FAKE_SIGNATURE)
.codeDelegations(List.of(delegation))
.guessType()
.build();

mockProcessorStatusForTransaction(expectedTx, Status.SUCCESSFUL);

final Optional<TransactionSimulatorResult> result =
uncappedTransactionSimulator.process(
callParameter,
TransactionValidationParams.transactionSimulator(),
OperationTracer.NO_TRACING,
blockHeader);

assertThat(result).isPresent();
assertThat(result.get().transaction().getType()).isEqualTo(TransactionType.DELEGATE_CODE);
}

private BlockHeader mockBlockchainAndWorldState(final CallParameter callParameter) {
final BlockHeader blockHeader =
mockBlockHeader(Hash.ZERO, 1L, Wei.ONE, DEFAULT_BLOCK_GAS_LIMIT);
Expand Down Expand Up @@ -1057,6 +1099,11 @@ private ImmutableCallParameter.Builder eip1559TransactionCallParameterBuilder()
.maxPriorityFeePerGas(Wei.ZERO);
}

private ImmutableCallParameter.Builder codeDelegationTransactionCallParamterBuilder(
final List<CodeDelegation> delegations) {
return legacyTransactionCallParameterBuilder().codeDelegationAuthorizations(delegations);
}

private CallParameter blobTransactionCallParameter() {
BlobsWithCommitments bwc = new BlobTestFixture().createBlobsWithCommitments(3);
return eip1559TransactionCallParameterBuilder()
Expand Down