Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Ensure that the pending creation customizer applies to the address being created #11213

Merged
merged 5 commits into from Jan 27, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2022-2023 Hedera Hashgraph, LLC
* Copyright (C) 2022-2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -35,6 +35,7 @@
import com.hederahashgraph.api.proto.java.ContractCreateTransactionBody;
import edu.umd.cs.findbugs.annotations.Nullable;
import java.time.Instant;
import org.hyperledger.besu.datatypes.Address;

/**
* Encapsulates a set of customizations to a smart contract. Primarily delegates to an {@link
Expand All @@ -45,14 +46,19 @@ public class ContractCustomizer {
// Null if the contract is immutable; then its key derives from its entity id
private final JKey cryptoAdminKey;
private final HederaAccountCustomizer accountCustomizer;
private final Address customizerAppliesToAddress;

public ContractCustomizer(final HederaAccountCustomizer accountCustomizer) {
this(null, accountCustomizer);
this(null, accountCustomizer, null);
}

public ContractCustomizer(final @Nullable JKey cryptoAdminKey, final HederaAccountCustomizer accountCustomizer) {
public ContractCustomizer(
final @Nullable JKey cryptoAdminKey,
final HederaAccountCustomizer accountCustomizer,
final @Nullable Address customizerAppliesToAddress) {
this.cryptoAdminKey = cryptoAdminKey;
this.accountCustomizer = accountCustomizer;
this.customizerAppliesToAddress = customizerAppliesToAddress;
}

/**
Expand All @@ -62,10 +68,14 @@ public ContractCustomizer(final @Nullable JKey cryptoAdminKey, final HederaAccou
* @param decodedKey the key implied by the HAPI operation
* @param consensusTime the consensus time of the ContractCreate
* @param op the details of the HAPI operation
* @param customizerAppliesToAddress the address of the contract being created, or null if not applicable
* @return an appropriate top-level customizer
*/
public static ContractCustomizer fromHapiCreation(
final JKey decodedKey, final Instant consensusTime, final ContractCreateTransactionBody op) {
final JKey decodedKey,
final Instant consensusTime,
final ContractCreateTransactionBody op,
final Address customizerAppliesToAddress) {
Nana-EC marked this conversation as resolved.
Show resolved Hide resolved
final var autoRenewPeriod = op.getAutoRenewPeriod().getSeconds();
final var expiry = consensusTime.getEpochSecond() + autoRenewPeriod;

Expand All @@ -86,7 +96,7 @@ public static ContractCustomizer fromHapiCreation(
customizer.customizeStakedId(op.getStakedIdCase().name(), op.getStakedAccountId(), op.getStakedNodeId());
}

return new ContractCustomizer(key, customizer);
return new ContractCustomizer(key, customizer, customizerAppliesToAddress);
}

/**
Expand All @@ -96,16 +106,19 @@ public static ContractCustomizer fromHapiCreation(
*
* @param sponsor the sending contract
* @param ledger the containing ledger
* @param customizerAppliesToAddress the address of the contract being created, or null if not applicable
* @return an appropriate child customizer
*/
public static ContractCustomizer fromSponsorContract(
final AccountID sponsor, final TransactionalLedger<AccountID, AccountProperty, HederaAccount> ledger) {
final AccountID sponsor,
final TransactionalLedger<AccountID, AccountProperty, HederaAccount> ledger,
final Address customizerAppliesToAddress) {
Nana-EC marked this conversation as resolved.
Show resolved Hide resolved
var key = (JKey) ledger.get(sponsor, KEY);
if (key instanceof JContractIDKey) {
key = null;
}
final var customizer = getAccountCustomizer(sponsor, ledger);
return new ContractCustomizer(key, customizer);
return new ContractCustomizer(key, customizer, customizerAppliesToAddress);
}

public static ContractCustomizer fromSponsorContractWithoutKey(
Expand Down Expand Up @@ -162,4 +175,8 @@ public void customizeSynthetic(final ContractCreateTransactionBody.Builder op) {
public HederaAccountCustomizer accountCustomizer() {
return accountCustomizer;
}

public boolean appliesTo(final Address address) {
return customizerAppliesToAddress == null || customizerAppliesToAddress.equals(address);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2022-2023 Hedera Hashgraph, LLC
* Copyright (C) 2022-2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -50,7 +50,7 @@ public TransactionBody customize(
final TransactionBody synthCreate, final AccountID callerId, final boolean inheritKey) {
ContractCustomizer customizer;
if (inheritKey) {
customizer = ContractCustomizer.fromSponsorContract(callerId, accountsLedger);
customizer = ContractCustomizer.fromSponsorContract(callerId, accountsLedger, null);
} else {
customizer = ContractCustomizer.fromSponsorContractWithoutKey(callerId, accountsLedger);
}
Expand Down
Expand Up @@ -121,7 +121,7 @@ public MutableAccount getOrCreate(final Address address) {
return account;
}
// if the customizer is set, that means we're creating a contract
if (hasPendingCreationCustomizer()) {
if (hasPendingCreationCustomizer() && customizerForPendingCreation().appliesTo(address)) {
return createAccount(address);
}
// if the customizer is not set, that means we're creating a ghost account that will not be persisted
Expand Down
Expand Up @@ -185,7 +185,7 @@ public Address newContractAddress(final Address sponsorAddressOrAlias) {
final var newAddress = worldState.newContractAddress(sponsor);
numAllocatedIds++;
final var sponsorId = accountIdFromEvmAddress(sponsor);
pendingCreationCustomizer = customizerFactory.apply(sponsorId, trackingAccounts());
pendingCreationCustomizer = customizerFactory.apply(sponsorId, trackingAccounts(), newAddress);
if (!dynamicProperties.areContractAutoAssociationsEnabled()) {
pendingCreationCustomizer.accountCustomizer().maxAutomaticAssociations(0);
}
Expand Down Expand Up @@ -284,7 +284,8 @@ private boolean isMissingTarget(final Address alias) {

@FunctionalInterface
interface CustomizerFactory {
ContractCustomizer apply(AccountID id, TransactionalLedger<AccountID, AccountProperty, HederaAccount> ledger);
ContractCustomizer apply(
AccountID id, TransactionalLedger<AccountID, AccountProperty, HederaAccount> ledger, Address address);
}

// --- Only used by unit tests
Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2020-2023 Hedera Hashgraph, LLC
* Copyright (C) 2020-2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -197,7 +197,7 @@ public void doStateTransitionOperation(
}

// --- Do the business logic ---
final ContractCustomizer hapiSenderCustomizer = fromHapiCreation(key, consensusTime, op);
final ContractCustomizer hapiSenderCustomizer = fromHapiCreation(key, consensusTime, op, newContractAddress);
worldState.setHapiSenderCustomizer(hapiSenderCustomizer);
TransactionProcessingResult result;
try {
Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2022-2023 Hedera Hashgraph, LLC
* Copyright (C) 2022-2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -54,6 +54,7 @@
import com.hederahashgraph.api.proto.java.Duration;
import java.time.Instant;
import java.util.Map;
import org.hyperledger.besu.datatypes.Address;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
Expand All @@ -62,6 +63,8 @@

@ExtendWith(MockitoExtension.class)
class ContractCustomizerTest {
private static final Address newAddress = Address.fromHexString("0xabc");

@Mock
private HederaAccountCustomizer accountCustomizer;

Expand All @@ -88,7 +91,7 @@ void worksWithNoCryptoAdminKey() {
void usesContractIdKeyIfInheritingFromJustCompletedHollowAccount() {
final var captor = ArgumentCaptor.forClass(JKey.class);

subject = new ContractCustomizer(EMPTY_KEY, accountCustomizer);
subject = new ContractCustomizer(EMPTY_KEY, accountCustomizer, newAddress);

subject.customize(newContractId, ledger);

Expand All @@ -102,7 +105,7 @@ void usesContractIdKeyIfInheritingFromJustCompletedHollowAccount() {
void worksWithCryptoAdminKey() {
final var captor = ArgumentCaptor.forClass(JKey.class);

subject = new ContractCustomizer(cryptoAdminKey, accountCustomizer);
subject = new ContractCustomizer(cryptoAdminKey, accountCustomizer, newAddress);

subject.customize(newContractId, ledger);

Expand All @@ -124,7 +127,7 @@ void worksFromSponsorCustomizerWithCryptoKey() {
given(ledger.get(sponsorId, STAKED_ID)).willReturn(stakedId);
given(ledger.get(sponsorId, DECLINE_REWARD)).willReturn(declineReward);

final var subject = ContractCustomizer.fromSponsorContract(sponsorId, ledger);
final var subject = ContractCustomizer.fromSponsorContract(sponsorId, ledger, newAddress);

assertCustomizesWithCryptoKey(subject, true);
}
Expand All @@ -142,7 +145,7 @@ void worksFromImmutableSponsorCustomizer() {
given(ledger.get(sponsorId, STAKED_ID)).willReturn(stakedId);
given(ledger.get(sponsorId, DECLINE_REWARD)).willReturn(declineReward);

final var subject = ContractCustomizer.fromSponsorContract(sponsorId, ledger);
final var subject = ContractCustomizer.fromSponsorContract(sponsorId, ledger, newAddress);

assertCustomizesWithImmutableKey(subject);
}
Expand All @@ -156,7 +159,7 @@ void worksWithParsedStandinKeyAndExplicityProxy() {
.setMemo(memo)
.build();

final var subject = ContractCustomizer.fromHapiCreation(STANDIN_CONTRACT_ID_KEY, consensusNow, op);
final var subject = ContractCustomizer.fromHapiCreation(STANDIN_CONTRACT_ID_KEY, consensusNow, op, newAddress);

assertCustomizesWithImmutableKey(subject);
}
Expand All @@ -171,7 +174,7 @@ void worksForStakedId() {
.setDeclineReward(true)
.build();

final var subject = ContractCustomizer.fromHapiCreation(STANDIN_CONTRACT_ID_KEY, consensusNow, op);
final var subject = ContractCustomizer.fromHapiCreation(STANDIN_CONTRACT_ID_KEY, consensusNow, op, newAddress);

final var captor = ArgumentCaptor.forClass(JKey.class);

Expand Down Expand Up @@ -210,7 +213,7 @@ void worksWithCryptoKeyAndNoExplicitProxy() {
.setMemo(memo)
.build();

final var subject = ContractCustomizer.fromHapiCreation(cryptoAdminKey, consensusNow, op);
final var subject = ContractCustomizer.fromHapiCreation(cryptoAdminKey, consensusNow, op, newAddress);

assertCustomizesWithCryptoKey(subject);
}
Expand All @@ -224,7 +227,7 @@ void worksWithAutoRenewAccount() {
.setMemo(memo)
.build();

final var subject = ContractCustomizer.fromHapiCreation(cryptoAdminKey, consensusNow, op);
final var subject = ContractCustomizer.fromHapiCreation(cryptoAdminKey, consensusNow, op, newAddress);

assertCustomizesWithCryptoKey(subject);
verify(ledger).set(newContractId, AUTO_RENEW_ACCOUNT_ID, autoRenewAccount);
Expand All @@ -238,7 +241,7 @@ void worksWithoutAutoRenewAccount() {
.setMemo(memo)
.build();

final var subject = ContractCustomizer.fromHapiCreation(cryptoAdminKey, consensusNow, op);
final var subject = ContractCustomizer.fromHapiCreation(cryptoAdminKey, consensusNow, op, newAddress);

assertCustomizesWithCryptoKey(subject);
// Should not set auto-renew account to missing entity id
Expand All @@ -253,15 +256,15 @@ void worksWithAutoAssociationSlots() {
.setMemo(memo)
.build();

final var subject = ContractCustomizer.fromHapiCreation(cryptoAdminKey, consensusNow, op);
final var subject = ContractCustomizer.fromHapiCreation(cryptoAdminKey, consensusNow, op, newAddress);

assertCustomizesWithCryptoKey(subject);
verify(ledger).set(newContractId, MAX_AUTOMATIC_ASSOCIATIONS, 10);
}

@Test
void customizesSyntheticWithCryptoKey() {
final var subject = new ContractCustomizer(cryptoAdminKey, accountCustomizer);
final var subject = new ContractCustomizer(cryptoAdminKey, accountCustomizer, newAddress);
final var op = ContractCreateTransactionBody.newBuilder();

subject.customizeSynthetic(op);
Expand All @@ -273,7 +276,7 @@ void customizesSyntheticWithCryptoKey() {
@Test
void customizesSyntheticWithNegStakedId() {
given(accountCustomizer.getChanges()).willReturn(Map.of(STAKED_ID, -1L));
final var subject = new ContractCustomizer(cryptoAdminKey, accountCustomizer);
final var subject = new ContractCustomizer(cryptoAdminKey, accountCustomizer, newAddress);
final var op = ContractCreateTransactionBody.newBuilder();
willCallRealMethod().given(accountCustomizer).customizeSynthetic(op);

Expand All @@ -288,7 +291,7 @@ void customizesSyntheticWithNegStakedId() {
@Test
void customizesSyntheticWithPositiveStakedId() {
given(accountCustomizer.getChanges()).willReturn(Map.of(STAKED_ID, 10L));
final var subject = new ContractCustomizer(cryptoAdminKey, accountCustomizer);
final var subject = new ContractCustomizer(cryptoAdminKey, accountCustomizer, newAddress);
final var op = ContractCreateTransactionBody.newBuilder();
willCallRealMethod().given(accountCustomizer).customizeSynthetic(op);

Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021-2023 Hedera Hashgraph, LLC
* Copyright (C) 2021-2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -212,7 +212,7 @@ void linksAliasWhenReservingNewContractId() {
given(trackingLedgers.aliases()).willReturn(aliases);
given(trackingLedgers.accounts()).willReturn(accountsLedger);
given(aliases.resolveForEvm(sponsor)).willReturn(sponsor);
given(customizerFactory.apply(any(), any())).willReturn(customizer);
given(customizerFactory.apply(any(), any(), any())).willReturn(customizer);
given(customizer.accountCustomizer()).willReturn(accountCustomizer);

final var created = subject.newAliasedContractAddress(sponsor, alias);
Expand Down Expand Up @@ -323,7 +323,8 @@ void canSponsorWithAlias() {

final var sponsoredAddr = Address.wrap(Bytes.wrap(EntityIdUtils.asEvmAddress(sponsoredId)));
given(worldState.newContractAddress(sponsorAddr)).willReturn(sponsoredAddr);
given(customizerFactory.apply(sponsorAid, accountsLedger)).willReturn(customizer);
given(customizerFactory.apply(sponsorAid, accountsLedger, sponsoredAddr))
.willReturn(customizer);

final var allocated = subject.newContractAddress(alias);
final var allocatedAid = EntityIdUtils.accountIdFromEvmAddress(allocated.toArrayUnsafe());
Expand Down
Expand Up @@ -146,7 +146,8 @@ public List<HapiSpec> getSpecsInSuite() {
contractWithAutoRenewNeedSignatures(),
newAccountsCanUsePureContractIdKey(),
createContractWithStakingFields(),
disallowCreationsOfEmptyInitCode());
disallowCreationsOfEmptyInitCode(),
createCallInConstructor());
}

@Override
Expand Down Expand Up @@ -334,6 +335,17 @@ final HapiSpec createEmptyConstructor() {
.then(contractCreate(EMPTY_CONSTRUCTOR_CONTRACT).hasKnownStatus(SUCCESS));
}

@HapiTest
final HapiSpec createCallInConstructor() {
final var txn = "txn";
return defaultHapiSpec("callInConstructor")
.given(uploadInitCode("CallInConstructor"))
.when()
.then(
contractCreate("CallInConstructor").via(txn).hasKnownStatus(SUCCESS),
getTxnRecord(txn).logged());
}

@HapiTest
final HapiSpec revertedTryExtCallHasNoSideEffects() {
final var balance = 3_000;
Expand Down
@@ -0,0 +1 @@
6080604052348015600f57600080fd5b5060006a636f6e736f6c652e6c6f679050600080600080845afa5050603f8060386000396000f3fe6080604052600080fdfea264697066735822122024b2dfd69e1895e746366dda7c12cd24a789553d191ed25953318ba87ad8288e64736f6c63430008100033
@@ -0,0 +1 @@
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"}]
@@ -0,0 +1,12 @@
pragma solidity ^0.8.0;

contract CallInConstructor {
address constant CONSOLE_ADDRESS = address(0x000000000000000000636F6e736F6c652e6c6f67);

constructor() {
address somebodyToCall = CONSOLE_ADDRESS;
assembly {
let r := staticcall(gas(), somebodyToCall, 0, 0, 0, 0)
}
}
}