Skip to content

Commit

Permalink
fix: Ensure that the pending creation customizer applies to the addre…
Browse files Browse the repository at this point in the history
…ss being created (#11213)

Signed-off-by: lukelee-sl <luke.lee@swirldslabs.com>
  • Loading branch information
lukelee-sl committed Jan 27, 2024
1 parent a723e9c commit 220d188
Show file tree
Hide file tree
Showing 11 changed files with 85 additions and 32 deletions.
@@ -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) {
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) {
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,11 @@ public void customizeSynthetic(final ContractCreateTransactionBody.Builder op) {
public HederaAccountCustomizer accountCustomizer() {
return accountCustomizer;
}

public boolean appliesTo(final Address address) {
if (customizerAppliesToAddress == null) {
throw new IllegalStateException("CustomizerAppliesToAddress is null");
}
return 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,8 @@ 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(aliases().resolveForEvm(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,8 @@ public void doStateTransitionOperation(
}

// --- Do the business logic ---
final ContractCustomizer hapiSenderCustomizer = fromHapiCreation(key, consensusTime, op);
final ContractCustomizer hapiSenderCustomizer =
fromHapiCreation(key, consensusTime, op, newContractMirrorAddress);
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)
}
}
}

0 comments on commit 220d188

Please sign in to comment.