diff --git a/hedera-node/hedera-app/src/xtest/java/contract/SetApprovalForAllXTest.java b/hedera-node/hedera-app/src/xtest/java/contract/SetApprovalForAllXTest.java
new file mode 100644
index 000000000000..a6769fb496ed
--- /dev/null
+++ b/hedera-node/hedera-app/src/xtest/java/contract/SetApprovalForAllXTest.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2023 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 contract;
+
+import static com.hedera.hapi.node.base.ResponseCodeEnum.SPENDER_DOES_NOT_HAVE_ALLOWANCE;
+import static contract.HtsErc721TransferXTestConstants.APPROVED_ID;
+import static contract.HtsErc721TransferXTestConstants.UNAUTHORIZED_SPENDER_ID;
+import static contract.MiscClassicTransfersXTestConstants.INITIAL_RECEIVER_AUTO_ASSOCIATIONS;
+import static contract.MiscClassicTransfersXTestConstants.NEXT_ENTITY_NUM;
+import static contract.XTestConstants.AN_ED25519_KEY;
+import static contract.XTestConstants.ERC721_TOKEN_ADDRESS;
+import static contract.XTestConstants.ERC721_TOKEN_ID;
+import static contract.XTestConstants.OWNER_ADDRESS;
+import static contract.XTestConstants.OWNER_BESU_ADDRESS;
+import static contract.XTestConstants.OWNER_HEADLONG_ADDRESS;
+import static contract.XTestConstants.OWNER_ID;
+import static contract.XTestConstants.RECEIVER_HEADLONG_ADDRESS;
+import static contract.XTestConstants.RECEIVER_ID;
+import static contract.XTestConstants.SENDER_ADDRESS;
+import static contract.XTestConstants.SENDER_BESU_ADDRESS;
+import static contract.XTestConstants.SENDER_HEADLONG_ADDRESS;
+import static contract.XTestConstants.SENDER_ID;
+import static contract.XTestConstants.SN_1234;
+import static contract.XTestConstants.SN_1234_METADATA;
+import static contract.XTestConstants.SN_2345;
+import static contract.XTestConstants.SN_2345_METADATA;
+import static contract.XTestConstants.addErc721Relation;
+import static contract.XTestConstants.assertSuccess;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import com.hedera.hapi.node.base.AccountID;
+import com.hedera.hapi.node.base.NftID;
+import com.hedera.hapi.node.base.TokenID;
+import com.hedera.hapi.node.base.TokenType;
+import com.hedera.hapi.node.state.common.EntityIDPair;
+import com.hedera.hapi.node.state.primitives.ProtoBytes;
+import com.hedera.hapi.node.state.token.Account;
+import com.hedera.hapi.node.state.token.Nft;
+import com.hedera.hapi.node.state.token.Token;
+import com.hedera.hapi.node.state.token.TokenRelation;
+import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes;
+import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.setapproval.SetApprovalForAllTranslator;
+import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.transfer.ClassicTransfersTranslator;
+import com.hedera.node.app.spi.state.ReadableKVState;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.tuweni.bytes.Bytes;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Exercises setApprovalForAll via the following steps relative to an {@code OWNER} account:
+ *
+ * - Transfer {@code ERC721_TOKEN} serialN 1234 from Owner's account. Should fail with SPENDER_DOES_NOT_HAVE_ALLOWANCE
+ * - SetApprovalForAll to true and verify successful operation
+ * - Transfer {@code ERC721_TOKEN} serialN 1234 from Owner's account and verify successful operation
+ * - SetApprovalForAll to false and verify successful operation
+ * - Transfer {@code ERC721_TOKEN} serialN 2345 from Owner's account. Should fail with SPENDER_DOES_NOT_HAVE_ALLOWANCE
+ * - SetApprovalForAll with ERC call to true and verify successful operation
+ * - Transfer {@code ERC721_TOKEN} serialN 2345 from Owner's account and verify successful operation
+ * - SetApprovalForAll with ERC call to false and verify successful operation
+ * - Via {@code assertExpectedAccounts} verify that 2 NFTs have been transferred from the OWNER to the RECEIVER
+ *
+ */
+public class SetApprovalForAllXTest extends AbstractContractXTest {
+
+ private static final long NUMBER_OWNED_NFT = 3L;
+
+ @Override
+ protected void doScenarioOperations() {
+ // Transfer series 1234 of ERC721_TOKEN to RECEIVER
+ runHtsCallAndExpectOnSuccess(
+ SENDER_BESU_ADDRESS,
+ Bytes.wrap(ClassicTransfersTranslator.TRANSFER_NFT
+ .encodeCallWithArgs(
+ ERC721_TOKEN_ADDRESS,
+ OWNER_HEADLONG_ADDRESS,
+ RECEIVER_HEADLONG_ADDRESS,
+ SN_1234.serialNumber())
+ .array()),
+ output -> assertEquals(
+ Bytes.wrap(ReturnTypes.encodedRc(SPENDER_DOES_NOT_HAVE_ALLOWANCE)
+ .array()),
+ output));
+
+ // Set approval for all to true
+ runHtsCallAndExpectOnSuccess(
+ OWNER_BESU_ADDRESS,
+ Bytes.wrap(SetApprovalForAllTranslator.SET_APPROVAL_FOR_ALL
+ .encodeCallWithArgs(ERC721_TOKEN_ADDRESS, SENDER_HEADLONG_ADDRESS, true)
+ .array()),
+ assertSuccess());
+
+ // Transfer series 1234 of ERC721_TOKEN to RECEIVER
+ runHtsCallAndExpectOnSuccess(
+ SENDER_BESU_ADDRESS,
+ Bytes.wrap(ClassicTransfersTranslator.TRANSFER_NFT
+ .encodeCallWithArgs(
+ ERC721_TOKEN_ADDRESS,
+ OWNER_HEADLONG_ADDRESS,
+ RECEIVER_HEADLONG_ADDRESS,
+ SN_1234.serialNumber())
+ .array()),
+ assertSuccess());
+
+ // Set approval for all to false
+ runHtsCallAndExpectOnSuccess(
+ OWNER_BESU_ADDRESS,
+ Bytes.wrap(SetApprovalForAllTranslator.SET_APPROVAL_FOR_ALL
+ .encodeCallWithArgs(ERC721_TOKEN_ADDRESS, SENDER_HEADLONG_ADDRESS, false)
+ .array()),
+ assertSuccess());
+
+ // Attempt to transfer series 2345 of ERC721_TOKEN to RECEIVER
+ runHtsCallAndExpectOnSuccess(
+ SENDER_BESU_ADDRESS,
+ Bytes.wrap(ClassicTransfersTranslator.TRANSFER_NFT
+ .encodeCallWithArgs(
+ ERC721_TOKEN_ADDRESS,
+ OWNER_HEADLONG_ADDRESS,
+ RECEIVER_HEADLONG_ADDRESS,
+ SN_2345.serialNumber())
+ .array()),
+ output -> assertEquals(
+ Bytes.wrap(ReturnTypes.encodedRc(SPENDER_DOES_NOT_HAVE_ALLOWANCE)
+ .array()),
+ output));
+
+ // Set approval for all to true via ERC call
+ runHtsCallAndExpectOnSuccess(
+ OWNER_BESU_ADDRESS,
+ bytesForRedirect(
+ SetApprovalForAllTranslator.ERC721_SET_APPROVAL_FOR_ALL.encodeCallWithArgs(
+ SENDER_HEADLONG_ADDRESS, true),
+ ERC721_TOKEN_ID),
+ assertSuccess());
+
+ // Transfer series 2345 of ERC721_TOKEN to RECEIVER
+ runHtsCallAndExpectOnSuccess(
+ SENDER_BESU_ADDRESS,
+ Bytes.wrap(ClassicTransfersTranslator.TRANSFER_NFT
+ .encodeCallWithArgs(
+ ERC721_TOKEN_ADDRESS,
+ OWNER_HEADLONG_ADDRESS,
+ RECEIVER_HEADLONG_ADDRESS,
+ SN_2345.serialNumber())
+ .array()),
+ assertSuccess());
+
+ // Set approval for all to false via ERC call
+ runHtsCallAndExpectOnSuccess(
+ OWNER_BESU_ADDRESS,
+ bytesForRedirect(
+ SetApprovalForAllTranslator.ERC721_SET_APPROVAL_FOR_ALL.encodeCallWithArgs(
+ SENDER_HEADLONG_ADDRESS, false),
+ ERC721_TOKEN_ID),
+ assertSuccess());
+ }
+
+ @Override
+ protected long initialEntityNum() {
+ return NEXT_ENTITY_NUM - 1;
+ }
+
+ @Override
+ protected Map initialAliases() {
+ final var aliases = new HashMap();
+ aliases.put(ProtoBytes.newBuilder().value(SENDER_ADDRESS).build(), SENDER_ID);
+ aliases.put(ProtoBytes.newBuilder().value(OWNER_ADDRESS).build(), OWNER_ID);
+ return aliases;
+ }
+
+ @Override
+ protected Map initialTokens() {
+ final var tokens = new HashMap();
+ tokens.put(
+ ERC721_TOKEN_ID,
+ Token.newBuilder()
+ .tokenId(ERC721_TOKEN_ID)
+ .treasuryAccountId(UNAUTHORIZED_SPENDER_ID)
+ .tokenType(TokenType.NON_FUNGIBLE_UNIQUE)
+ .build());
+ return tokens;
+ }
+
+ @Override
+ protected Map initialTokenRelationships() {
+ final var tokenRelationships = new HashMap();
+ addErc721Relation(tokenRelationships, OWNER_ID, NUMBER_OWNED_NFT);
+ return tokenRelationships;
+ }
+
+ @Override
+ protected Map initialNfts() {
+ final var nfts = new HashMap();
+ nfts.put(
+ SN_1234,
+ Nft.newBuilder()
+ .nftId(SN_1234)
+ .ownerId(OWNER_ID)
+ .spenderId(APPROVED_ID)
+ .metadata(SN_1234_METADATA)
+ .build());
+ nfts.put(
+ SN_2345,
+ Nft.newBuilder()
+ .nftId(SN_2345)
+ .ownerId(OWNER_ID)
+ .metadata(SN_2345_METADATA)
+ .build());
+ return nfts;
+ }
+
+ @Override
+ protected Map initialAccounts() {
+ final var accounts = new HashMap();
+ accounts.put(
+ SENDER_ID,
+ Account.newBuilder()
+ .accountId(SENDER_ID)
+ .alias(SENDER_ADDRESS)
+ .smartContract(true)
+ .build());
+ accounts.put(
+ OWNER_ID,
+ Account.newBuilder()
+ .accountId(OWNER_ID)
+ .alias(OWNER_ADDRESS)
+ .key(AN_ED25519_KEY)
+ .numberOwnedNfts(NUMBER_OWNED_NFT)
+ .build());
+ accounts.put(
+ RECEIVER_ID,
+ Account.newBuilder()
+ .accountId(RECEIVER_ID)
+ .maxAutoAssociations(INITIAL_RECEIVER_AUTO_ASSOCIATIONS)
+ .build());
+ accounts.put(
+ UNAUTHORIZED_SPENDER_ID,
+ Account.newBuilder().accountId(UNAUTHORIZED_SPENDER_ID).build());
+ return accounts;
+ }
+
+ @Override
+ protected void assertExpectedAccounts(@NotNull final ReadableKVState accounts) {
+ final var ownerAccount = accounts.get(OWNER_ID);
+ final var receiverAccount = accounts.get(RECEIVER_ID);
+ assertNotNull(ownerAccount);
+ assertNotNull(receiverAccount);
+
+ // Number of owned NFTs should be decreased by 2
+ // Number of receiver NFTs should be 2
+ assertEquals(NUMBER_OWNED_NFT - 2, ownerAccount.numberOwnedNfts());
+ assertEquals(2, receiverAccount.numberOwnedNfts());
+ }
+}
diff --git a/hedera-node/hedera-app/src/xtest/java/contract/XTestConstants.java b/hedera-node/hedera-app/src/xtest/java/contract/XTestConstants.java
index 5c91bfdf3508..2b0d318a9e4e 100644
--- a/hedera-node/hedera-app/src/xtest/java/contract/XTestConstants.java
+++ b/hedera-node/hedera-app/src/xtest/java/contract/XTestConstants.java
@@ -80,6 +80,8 @@ class XTestConstants {
.build();
static final Bytes SENDER_ADDRESS =
com.hedera.pbj.runtime.io.buffer.Bytes.fromHex("f91e624b8b8ea7244e8159ba7c0deeea2b6be990");
+ static final com.esaulpaugh.headlong.abi.Address SENDER_HEADLONG_ADDRESS =
+ asHeadlongAddress(SENDER_ADDRESS.toByteArray());
static final Address SENDER_BESU_ADDRESS = pbjToBesuAddress(SENDER_ADDRESS);
static final AccountID RECEIVER_ID =
AccountID.newBuilder().accountNum(987654321L).build();
diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/processors/HtsTranslatorsModule.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/processors/HtsTranslatorsModule.java
index c5d16f2ff47c..ad54452a1e88 100644
--- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/processors/HtsTranslatorsModule.java
+++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/processors/HtsTranslatorsModule.java
@@ -29,6 +29,7 @@
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.name.NameTranslator;
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ownerof.OwnerOfTranslator;
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.pauses.PausesTranslator;
+import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.setapproval.SetApprovalForAllTranslator;
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.symbol.SymbolTranslator;
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.tokenuri.TokenUriTranslator;
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.totalsupply.TotalSupplyTranslator;
@@ -140,6 +141,13 @@ static HtsCallTranslator provideOwnerOfTranslator(@NonNull final OwnerOfTranslat
return translator;
}
+ @Provides
+ @Singleton
+ @IntoSet
+ static HtsCallTranslator provideSetApprovalForAllTranslator(@NonNull final SetApprovalForAllTranslator translator) {
+ return translator;
+ }
+
@Provides
@Singleton
@IntoSet
diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/setapproval/SetApprovalForAllDecoder.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/setapproval/SetApprovalForAllDecoder.java
new file mode 100644
index 000000000000..821e847f457e
--- /dev/null
+++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/setapproval/SetApprovalForAllDecoder.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 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.hts.setapproval;
+
+import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.asTokenId;
+
+import com.esaulpaugh.headlong.abi.Address;
+import com.hedera.hapi.node.base.AccountID;
+import com.hedera.hapi.node.base.TokenID;
+import com.hedera.hapi.node.token.CryptoApproveAllowanceTransactionBody;
+import com.hedera.hapi.node.token.NftAllowance;
+import com.hedera.hapi.node.transaction.TransactionBody;
+import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AddressIdConverter;
+import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt;
+import edu.umd.cs.findbugs.annotations.NonNull;
+import java.util.Objects;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+@Singleton
+public class SetApprovalForAllDecoder {
+
+ @Inject
+ public SetApprovalForAllDecoder() {
+ // Dagger 2 constructor
+ }
+
+ /**
+ * Decodes the given {@code attempt} into a {@link TransactionBody} for a setApprovalForAll function call.
+ *
+ * @param attempt the attempt to decode
+ * @return a {@link TransactionBody}
+ */
+ public TransactionBody decodeSetApprovalForAll(@NonNull final HtsCallAttempt attempt) {
+ final var call = SetApprovalForAllTranslator.SET_APPROVAL_FOR_ALL.decodeCall(attempt.inputBytes());
+ return bodyOf(approveAllAllowanceNFTBody(
+ attempt.addressIdConverter(), attempt.senderId(), asTokenId(call.get(0)), call.get(1), call.get(2)));
+ }
+
+ /**
+ * Decodes the given {@code attempt} into a {@link TransactionBody} for a setApprovalForAll ERC function call.
+ *
+ * @param attempt the attempt to decode
+ * @return a {@link TransactionBody}
+ */
+ public TransactionBody decodeSetApprovalForAllERC(@NonNull final HtsCallAttempt attempt) {
+ final var call = SetApprovalForAllTranslator.ERC721_SET_APPROVAL_FOR_ALL.decodeCall(attempt.inputBytes());
+ final var tokenId = attempt.redirectTokenId();
+ Objects.requireNonNull(tokenId, "Redirect Token ID is null.");
+
+ return bodyOf(approveAllAllowanceNFTBody(
+ attempt.addressIdConverter(), attempt.senderId(), tokenId, call.get(0), call.get(1)));
+ }
+
+ private CryptoApproveAllowanceTransactionBody approveAllAllowanceNFTBody(
+ @NonNull final AddressIdConverter addressIdConverter,
+ @NonNull final AccountID senderId,
+ @NonNull final TokenID tokenID,
+ @NonNull final Address operatorAddress,
+ final boolean approved) {
+ return CryptoApproveAllowanceTransactionBody.newBuilder()
+ .nftAllowances(NftAllowance.newBuilder()
+ .tokenId(tokenID)
+ .owner(senderId)
+ .spender(addressIdConverter.convert(operatorAddress))
+ .approvedForAll(approved)
+ .build())
+ .build();
+ }
+
+ private TransactionBody bodyOf(
+ @NonNull final CryptoApproveAllowanceTransactionBody approveAllowanceTransactionBody) {
+ return TransactionBody.newBuilder()
+ .cryptoApproveAllowance(approveAllowanceTransactionBody)
+ .build();
+ }
+}
diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/setapproval/SetApprovalForAllTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/setapproval/SetApprovalForAllTranslator.java
new file mode 100644
index 000000000000..a48b2219f972
--- /dev/null
+++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/setapproval/SetApprovalForAllTranslator.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2023 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.hts.setapproval;
+
+import com.esaulpaugh.headlong.abi.Function;
+import com.hedera.hapi.node.transaction.TransactionBody;
+import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCallTranslator;
+import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.DispatchForResponseCodeHtsCall;
+import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall;
+import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt;
+import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes;
+import com.hedera.node.app.spi.workflows.record.SingleTransactionRecordBuilder;
+import edu.umd.cs.findbugs.annotations.NonNull;
+import java.util.Arrays;
+import javax.inject.Inject;
+
+/**
+ * Translates setApprovalForAll (including ERC) call to the HTS system contract. There are no special cases for these
+ * calls, so the returned {@link HtsCall} is simply an instance of {@link DispatchForResponseCodeHtsCall}.
+ */
+public class SetApprovalForAllTranslator extends AbstractHtsCallTranslator {
+
+ public static final Function SET_APPROVAL_FOR_ALL =
+ new Function("setApprovalForAll(address,address,bool)", ReturnTypes.INT);
+ public static final Function ERC721_SET_APPROVAL_FOR_ALL =
+ new Function("setApprovalForAll(address,bool)", ReturnTypes.INT);
+
+ private final SetApprovalForAllDecoder decoder;
+
+ @Inject
+ public SetApprovalForAllTranslator(final SetApprovalForAllDecoder decoder) {
+ this.decoder = decoder;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean matches(@NonNull final HtsCallAttempt attempt) {
+ return selectorMatches(attempt, SET_APPROVAL_FOR_ALL)
+ || (attempt.isTokenRedirect() && selectorMatches(attempt, ERC721_SET_APPROVAL_FOR_ALL));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public HtsCall callFrom(@NonNull final HtsCallAttempt attempt) {
+ return new DispatchForResponseCodeHtsCall<>(
+ attempt, bodyForClassic(attempt), SingleTransactionRecordBuilder.class);
+ }
+
+ private TransactionBody bodyForClassic(@NonNull final HtsCallAttempt attempt) {
+ if (selectorMatches(attempt, SET_APPROVAL_FOR_ALL)) {
+ return decoder.decodeSetApprovalForAll(attempt);
+ }
+ return decoder.decodeSetApprovalForAllERC(attempt);
+ }
+
+ private boolean selectorMatches(final HtsCallAttempt attempt, final Function function) {
+ return Arrays.equals(attempt.selector(), function.selector());
+ }
+}
diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/setapproval/SetApprovalForAllDecoderTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/setapproval/SetApprovalForAllDecoderTest.java
new file mode 100644
index 000000000000..991daca361a8
--- /dev/null
+++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/setapproval/SetApprovalForAllDecoderTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2023 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.hts.setapproval;
+
+import static com.hedera.node.app.service.contract.impl.test.TestHelpers.APPROVED_HEADLONG_ADDRESS;
+import static com.hedera.node.app.service.contract.impl.test.TestHelpers.APPROVED_ID;
+import static com.hedera.node.app.service.contract.impl.test.TestHelpers.NON_FUNGIBLE_TOKEN_HEADLONG_ADDRESS;
+import static com.hedera.node.app.service.contract.impl.test.TestHelpers.NON_FUNGIBLE_TOKEN_ID;
+import static com.hedera.node.app.service.contract.impl.test.TestHelpers.SENDER_ID;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.BDDMockito.given;
+
+import com.hedera.hapi.node.transaction.TransactionBody;
+import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AddressIdConverter;
+import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt;
+import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.setapproval.SetApprovalForAllDecoder;
+import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.setapproval.SetApprovalForAllTranslator;
+import org.junit.jupiter.api.BeforeEach;
+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)
+public class SetApprovalForAllDecoderTest {
+
+ @Mock
+ private HtsCallAttempt attempt;
+
+ @Mock
+ private AddressIdConverter addressIdConverter;
+
+ private final SetApprovalForAllDecoder subject = new SetApprovalForAllDecoder();
+
+ @BeforeEach
+ void setUp() {
+ given(attempt.addressIdConverter()).willReturn(addressIdConverter);
+ given(attempt.senderId()).willReturn(SENDER_ID);
+ given(addressIdConverter.convert(APPROVED_HEADLONG_ADDRESS)).willReturn(APPROVED_ID);
+ }
+
+ @Test
+ void setApprovalForAll_true_Works() {
+ // given
+ final var encodedInput = SetApprovalForAllTranslator.SET_APPROVAL_FOR_ALL
+ .encodeCallWithArgs(NON_FUNGIBLE_TOKEN_HEADLONG_ADDRESS, APPROVED_HEADLONG_ADDRESS, true)
+ .array();
+ given(attempt.inputBytes()).willReturn(encodedInput);
+
+ // when
+ final var transactionBody = subject.decodeSetApprovalForAll(attempt);
+
+ // then
+ verifyDecoding(true, transactionBody);
+ }
+
+ @Test
+ void setApprovalForAllToken_false_Works() {
+ // given
+ final var encodedInput = SetApprovalForAllTranslator.SET_APPROVAL_FOR_ALL
+ .encodeCallWithArgs(NON_FUNGIBLE_TOKEN_HEADLONG_ADDRESS, APPROVED_HEADLONG_ADDRESS, false)
+ .array();
+ given(attempt.inputBytes()).willReturn(encodedInput);
+
+ // when
+ final var transactionBody = subject.decodeSetApprovalForAll(attempt);
+
+ // then
+ verifyDecoding(false, transactionBody);
+ }
+
+ @Test
+ void setApprovalForAllTokenERC_true_Works() {
+ // given
+ final var encodedInput = SetApprovalForAllTranslator.ERC721_SET_APPROVAL_FOR_ALL
+ .encodeCallWithArgs(APPROVED_HEADLONG_ADDRESS, true)
+ .array();
+ given(attempt.inputBytes()).willReturn(encodedInput);
+ given(attempt.redirectTokenId()).willReturn(NON_FUNGIBLE_TOKEN_ID);
+
+ // when
+ final var transactionBody = subject.decodeSetApprovalForAllERC(attempt);
+
+ // then
+ verifyDecoding(true, transactionBody);
+ }
+
+ @Test
+ void setApprovalForAllTokenERC_false_Works() {
+ // given
+ final var encodedInput = SetApprovalForAllTranslator.ERC721_SET_APPROVAL_FOR_ALL
+ .encodeCallWithArgs(APPROVED_HEADLONG_ADDRESS, false)
+ .array();
+ given(attempt.inputBytes()).willReturn(encodedInput);
+ given(attempt.redirectTokenId()).willReturn(NON_FUNGIBLE_TOKEN_ID);
+
+ // when
+ final var transactionBody = subject.decodeSetApprovalForAllERC(attempt);
+
+ // then
+ verifyDecoding(false, transactionBody);
+ }
+
+ private void verifyDecoding(final boolean expectedApproval, final TransactionBody body) {
+ final var cryptoApproveAllowance = body.cryptoApproveAllowance();
+ assertThat(cryptoApproveAllowance).isNotNull();
+
+ final var nftAllowancesList = cryptoApproveAllowance.nftAllowances();
+ assertThat(nftAllowancesList).isNotEmpty();
+
+ assertThat(nftAllowancesList.get(0).tokenId()).isEqualTo(NON_FUNGIBLE_TOKEN_ID);
+ assertThat(nftAllowancesList.get(0).spender()).isEqualTo(APPROVED_ID);
+ assertThat(nftAllowancesList.get(0).owner()).isEqualTo(SENDER_ID);
+ assertThat(nftAllowancesList.get(0).approvedForAll()).isEqualTo(expectedApproval);
+ }
+}
diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/setapproval/SetApprovalForAllTranslatorTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/setapproval/SetApprovalForAllTranslatorTest.java
new file mode 100644
index 000000000000..39389986d03d
--- /dev/null
+++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/setapproval/SetApprovalForAllTranslatorTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 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.hts.setapproval;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.mockito.BDDMockito.given;
+
+import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt;
+import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.setapproval.SetApprovalForAllDecoder;
+import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.setapproval.SetApprovalForAllTranslator;
+import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.transfer.ClassicTransfersTranslator;
+import org.junit.jupiter.api.BeforeEach;
+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)
+public class SetApprovalForAllTranslatorTest {
+
+ @Mock
+ private HtsCallAttempt attempt;
+
+ private final SetApprovalForAllDecoder decoder = new SetApprovalForAllDecoder();
+
+ private SetApprovalForAllTranslator subject;
+
+ @BeforeEach
+ void setUp() {
+ subject = new SetApprovalForAllTranslator(decoder);
+ }
+
+ @Test
+ void matchesClassicalSelectorTest() {
+ given(attempt.selector()).willReturn(SetApprovalForAllTranslator.SET_APPROVAL_FOR_ALL.selector());
+ final var matches = subject.matches(attempt);
+ assertThat(matches).isTrue();
+ }
+
+ @Test
+ void matchesERCSelectorTest() {
+ given(attempt.selector()).willReturn(SetApprovalForAllTranslator.ERC721_SET_APPROVAL_FOR_ALL.selector());
+ given(attempt.isTokenRedirect()).willReturn(true);
+ final var matches = subject.matches(attempt);
+ assertThat(matches).isTrue();
+ }
+
+ @Test
+ void falseOnInvalidSelector() {
+ given(attempt.selector()).willReturn(ClassicTransfersTranslator.CRYPTO_TRANSFER.selector());
+ final var matches = subject.matches(attempt);
+ assertFalse(matches);
+ }
+}
diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoApproveAllowanceHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoApproveAllowanceHandler.java
index fc541e82fec5..fca3464c053f 100644
--- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoApproveAllowanceHandler.java
+++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoApproveAllowanceHandler.java
@@ -115,8 +115,7 @@ public void preHandle(@NonNull final PreHandleContext context) throws PreCheckEx
@Override
public void handle(@NonNull final HandleContext context) throws HandleException {
- final var txn = context.body();
- final var payer = txn.transactionIDOrThrow().accountIDOrThrow();
+ final var payer = context.payer();
final var accountStore = context.writableStore(WritableAccountStore.class);
diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoApproveAllowanceHandlerTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoApproveAllowanceHandlerTest.java
index f0bafefe4c9c..6042a223bbdd 100644
--- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoApproveAllowanceHandlerTest.java
+++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoApproveAllowanceHandlerTest.java
@@ -16,7 +16,11 @@
package com.hedera.node.app.service.token.impl.test.handlers;
-import static com.hedera.hapi.node.base.ResponseCodeEnum.*;
+import static com.hedera.hapi.node.base.ResponseCodeEnum.EMPTY_ALLOWANCES;
+import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ALLOWANCE_OWNER_ID;
+import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_DELEGATING_SPENDER;
+import static com.hedera.hapi.node.base.ResponseCodeEnum.MAX_ALLOWANCES_EXCEEDED;
+import static com.hedera.hapi.node.base.ResponseCodeEnum.SENDER_DOES_NOT_OWN_NFT_SERIAL_NO;
import static com.hedera.node.app.spi.fixtures.Assertions.assertThrowsPreCheck;
import static com.hedera.node.app.spi.fixtures.workflows.ExceptionConditions.responseCode;
import static org.assertj.core.api.Assertions.assertThat;
@@ -60,12 +64,14 @@ class CryptoApproveAllowanceHandlerTest extends CryptoTokenHandlerTestBase {
private CryptoApproveAllowanceHandler subject;
+ @Override
@BeforeEach
public void setUp() {
super.setUp();
refreshWritableStores();
final var validator = new ApproveAllowanceValidator();
givenStoresAndConfig(handleContext);
+ given(handleContext.payer()).willReturn(payerId);
subject = new CryptoApproveAllowanceHandler(validator);
}