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

feat: HIP 904 SetUnlimitedAutoAssociations System Contract Implementation #16141

Merged
merged 8 commits into from
Oct 28, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.hederaaccountnumalias.HederaAccountNumAliasTranslator;
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.isauthorizedraw.IsAuthorizedRawTranslator;
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.isvalidalias.IsValidAliasTranslator;
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.setunlimitedautoassociations.SetUnlimitedAutoAssociationsTranslator;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoSet;
Expand Down Expand Up @@ -99,4 +100,13 @@ static CallTranslator<HasCallAttempt> provideIsAuthorizedRawTranslator(
@NonNull final IsAuthorizedRawTranslator translator) {
return translator;
}

@Provides
@Singleton
@IntoSet
@Named("HasTranslators")
static CallTranslator<HasCallAttempt> provideSetUnlimitedAutoAssociationsTranslator(
@NonNull final SetUnlimitedAutoAssociationsTranslator translator) {
return translator;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright (C) 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.
* 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.
*/

package com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.setunlimitedautoassociations;

import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes.encodedRc;
import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes.standardized;
import static java.util.Objects.requireNonNull;

import com.hedera.hapi.node.base.AccountID;
import com.hedera.hapi.node.transaction.TransactionBody;
import com.hedera.node.app.service.contract.impl.exec.gas.DispatchType;
import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy;
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCall;
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.HasCallAttempt;
import com.hedera.node.app.service.contract.impl.records.ContractCallStreamBuilder;
import edu.umd.cs.findbugs.annotations.NonNull;
import org.hyperledger.besu.evm.frame.MessageFrame;

public class SetUnlimitedAutoAssociationsCall extends AbstractCall {

private final AccountID sender;
private final TransactionBody transactionBody;
private final VerificationStrategy verificationStrategy;

public SetUnlimitedAutoAssociationsCall(
@NonNull final HasCallAttempt attempt, @NonNull final TransactionBody transactionBody) {
super(attempt.systemContractGasCalculator(), attempt.enhancement(), false);
this.sender = attempt.senderId();
this.transactionBody = requireNonNull(transactionBody);
this.verificationStrategy = attempt.defaultVerificationStrategy();
}

@NonNull
@Override
public PricedResult execute(@NonNull final MessageFrame frame) {
requireNonNull(frame);
final var recordBuilder = systemContractOperations()
.dispatch(transactionBody, verificationStrategy, sender, ContractCallStreamBuilder.class);

final var gasRequirement = gasCalculator.gasRequirement(transactionBody, DispatchType.CRYPTO_UPDATE, sender);
return completionWith(gasRequirement, recordBuilder, encodedRc(standardized(recordBuilder.status())));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright (C) 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.
* 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.
*/

package com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.setunlimitedautoassociations;

import static java.util.Objects.requireNonNull;

import com.esaulpaugh.headlong.abi.Function;
import com.hedera.hapi.node.token.CryptoUpdateTransactionBody;
import com.hedera.hapi.node.transaction.TransactionBody;
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator;
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call;
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.HasCallAttempt;
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes;
import com.hedera.node.config.data.ContractsConfig;
import edu.umd.cs.findbugs.annotations.NonNull;
import javax.inject.Inject;
import javax.inject.Singleton;

@Singleton
public class SetUnlimitedAutoAssociationsTranslator extends AbstractCallTranslator<HasCallAttempt> {

public static final Function SET_UNLIMITED_AUTO_ASSOC =
new Function("setUnlimitedAutomaticAssociations(bool)", ReturnTypes.INT_64);
stoyanov-st marked this conversation as resolved.
Show resolved Hide resolved

private static final int UNLIMITED_AUTO_ASSOCIATIONS = -1;
private static final int NO_AUTO_ASSOCIATIONS = 0;

@Inject
public SetUnlimitedAutoAssociationsTranslator() {
// Dagger2
}

@Override
public boolean matches(@NonNull final HasCallAttempt attempt) {
final var setUnlimitedAutoAssocEnabled = attempt.configuration()
.getConfigData(ContractsConfig.class)
.systemContractSetUnlimitedAutoAssociationsEnabled();
return attempt.isSelectorIfConfigEnabled(SET_UNLIMITED_AUTO_ASSOC, setUnlimitedAutoAssocEnabled);
}

@Override
public Call callFrom(@NonNull final HasCallAttempt attempt) {
requireNonNull(attempt);
final var call = SET_UNLIMITED_AUTO_ASSOC.decodeCall(attempt.inputBytes());
final var setUnlimitedAutoAssociations = (boolean) call.get(0);
return new SetUnlimitedAutoAssociationsCall(attempt, bodyFor(attempt, setUnlimitedAutoAssociations));
}

@NonNull
private TransactionBody bodyFor(@NonNull final HasCallAttempt attempt, final boolean setUnlimitedAutoAssociations) {
final var cryptoUpdate = CryptoUpdateTransactionBody.newBuilder()
.accountIDToUpdate(attempt.redirectAccountId())
.maxAutomaticTokenAssociations(
setUnlimitedAutoAssociations ? UNLIMITED_AUTO_ASSOCIATIONS : NO_AUTO_ASSOCIATIONS)
.build();
return TransactionBody.newBuilder().cryptoUpdateAccount(cryptoUpdate).build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ public class ProxyEvmAccount extends AbstractProxyEvmAccount {
// hbarAllowance(address spender)
0xbbee989e,
// hbarApprove(address spender, int256 amount)
0x86aff07c);
0x86aff07c,
// setUnlimitedAutomaticAssociations(bool enableAutoAssociations
0xf5677e99);

// Only pass in a non-null account address if the function selector is eligible for proxy redirection.
// A null address will return the 0x bytecode.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,4 +150,27 @@ public static HasCallAttempt prepareHasAttemptWithSelector(
List.of(translator),
false);
}

public static HasCallAttempt prepareHasAttemptWithSelectorAndCustomConfig(
final Function function,
final CallTranslator<HasCallAttempt> translator,
final HederaWorldUpdater.Enhancement enhancement,
final AddressIdConverter addressIdConverter,
final VerificationStrategies verificationStrategies,
final SystemContractGasCalculator gasCalculator,
final Configuration config) {
final var input = TestHelpers.bytesForRedirectAccount(function.selector(), NON_SYSTEM_LONG_ZERO_ADDRESS);
return new HasCallAttempt(
input,
OWNER_BESU_ADDRESS,
OWNER_BESU_ADDRESS,
false,
enhancement,
config,
addressIdConverter,
verificationStrategies,
gasCalculator,
List.of(translator),
false);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright (C) 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.
* 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.
*/

package com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.has.setunlimitedautoassociations;

import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.REVERTED_SUCCESS;
import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;

import com.hedera.hapi.node.base.ResponseCodeEnum;
import com.hedera.hapi.node.transaction.TransactionBody;
import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator;
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.HasCallAttempt;
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.setunlimitedautoassociations.SetUnlimitedAutoAssociationsCall;
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.setunlimitedautoassociations.SetUnlimitedAutoAssociationsTranslator;
import com.hedera.node.app.service.contract.impl.records.ContractCallStreamBuilder;
import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase;
import org.apache.tuweni.bytes.Bytes;
import org.hyperledger.besu.evm.frame.MessageFrame.State;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
class SetUnlimitedAutoAssociationsCallTest extends CallTestBase {

@Mock
private HasCallAttempt attempt;

@Mock
private TransactionBody transactionBody;

@Mock
private ContractCallStreamBuilder recordBuilder;

@Mock
private SystemContractGasCalculator gasCalculator;

private SetUnlimitedAutoAssociationsCall subject;

@Test
void successCall() {
given(attempt.systemContractGasCalculator()).willReturn(gasCalculator);
given(attempt.enhancement()).willReturn(mockEnhancement());
given(systemContractOperations.dispatch(any(), any(), any(), any())).willReturn(recordBuilder);
given(recordBuilder.status()).willReturn(ResponseCodeEnum.SUCCESS);

subject = new SetUnlimitedAutoAssociationsCall(attempt, transactionBody);
final var result = subject.execute(frame).fullResult().result();
assertEquals(State.COMPLETED_SUCCESS, result.getState());
assertEquals(
Bytes.wrap(SetUnlimitedAutoAssociationsTranslator.SET_UNLIMITED_AUTO_ASSOC
.getOutputs()
.encodeElements((long) SUCCESS.getNumber())
.array()),
result.getOutput());
}

@Test
void revertCall() {
given(attempt.systemContractGasCalculator()).willReturn(gasCalculator);
given(attempt.enhancement()).willReturn(mockEnhancement());
given(systemContractOperations.dispatch(any(), any(), any(), any())).willReturn(recordBuilder);
given(recordBuilder.status()).willReturn(ResponseCodeEnum.REVERTED_SUCCESS);

subject = new SetUnlimitedAutoAssociationsCall(attempt, transactionBody);
final var result = subject.execute(frame).fullResult().result();
assertEquals(State.COMPLETED_SUCCESS, result.getState());
assertEquals(
Bytes.wrap(SetUnlimitedAutoAssociationsTranslator.SET_UNLIMITED_AUTO_ASSOC
.getOutputs()
.encodeElements((long) REVERTED_SUCCESS.getNumber())
.array()),
result.getOutput());
}
}
Loading
Loading