Skip to content

Commit

Permalink
Merge branch 'refs/heads/develop' into 15591-token-reject-system-cont…
Browse files Browse the repository at this point in the history
…ract

# Conflicts:
#	hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/gas/DispatchType.java
#	hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/processors/HtsTranslatorsModule.java
#	hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/dsl/contracts/TokenRedirectContract.java
  • Loading branch information
stoyanov-st committed Oct 25, 2024
2 parents ced338d + 2c35e13 commit d80872a
Show file tree
Hide file tree
Showing 27 changed files with 2,211 additions and 15 deletions.
36 changes: 25 additions & 11 deletions .github/workflows/zxcron-extended-test-suite.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,18 +64,33 @@ jobs:
- name: Check for tags
id: check-tags-exist
run: |
XTS_COMMIT=$(git rev-list -n 1 ${XTS_CANDIDATE_TAG})
# Check if the tag exists and if so grab its commit id
set +e
git branch --contains ${XTS_COMMIT} | grep --quiet develop >/dev/null 2>&1
XTS_COMMIT=$(git rev-list -n 1 "${XTS_CANDIDATE_TAG}") >/dev/null 2>&1
XTS_COMMIT_FOUND="${?}"
set -e
# Cancel out if the tag does not exist
if [[ "${XTS_COMMIT_FOUND}" -ne 0 ]]; then
gh run cancel ${{ github.run_id }}
fi
# Check if the tag exists on the develop branch
set +e
git branch --contains "${XTS_COMMIT}" | grep --quiet develop >/dev/null 2>&1
BRANCH_ON_DEVELOP="${?}"
set -e
if [[ -n "${XTS_COMMIT}" && "${BRANCH_ON_DEVELOP}" -eq 0 ]]; then
# If the tag exists on the Develop Branch set the output variables as appropriate
# Otherwise cancel out
if [[ "${BRANCH_ON_DEVELOP}" -eq 0 ]]; then
echo "xts-tag-exists=true" >> $GITHUB_OUTPUT
echo "xts-tag-commit=${XTS_COMMIT}" >> $GITHUB_OUTPUT
echo "### Commit has been tagged as an XTS-Candidate" >> $GITHUB_STEP_SUMMARY
echo "xts-tag-commit=${XTS_COMMIT}" >> $GITHUB_STEP_SUMMARY
git push --delete origin ${XTS_CANDIDATE_TAG}
git tag -d ${XTS_CANDIDATE_TAG}
git push --delete origin "${XTS_CANDIDATE_TAG}"
git tag -d "${XTS_CANDIDATE_TAG}"
else
gh run cancel ${{ github.run_id }}
fi
Expand Down Expand Up @@ -152,14 +167,13 @@ jobs:
name: Tag as XTS-Passing
runs-on: network-node-linux-medium
needs:
# - abbreviated-panel
- abbreviated-panel
- extended-test-suite
- fetch-xts-candidate
# - hedera-node-jrs-panel
# if: ${{ needs.abbreviated-panel.result == 'success' ||
# needs.extended-test-suite.result == 'success' ||
# needs.hedera-node-jrs-panel.result == 'success' }}
if: ${{ needs.extended-test-suite.result == 'success' }}
- hedera-node-jrs-panel
if: ${{ needs.abbreviated-panel.result == 'success' ||
needs.extended-test-suite.result == 'success' ||
needs.hedera-node-jrs-panel.result == 'success' }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/zxcron-promote-build-candidate.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ jobs:

- name: Checkout Tagged Code
id: checkout-tagged-code
if: ${{ needs.determine-build-candidate.build-candidate-exists == 'true' }}
if: ${{ needs.determine-build-candidate.outputs.build-candidate-exists == 'true' }}
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
with:
fetch-depth: '0'
Expand Down
15 changes: 13 additions & 2 deletions .github/workflows/zxf-prepare-extended-test-suite.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,19 @@ jobs:
# move the tag if successful
- name: Tag Code and push
run: |
git push --delete origin "${XTS_CANDIDATE_TAG}"
git tag --delete "${XTS_CANDIDATE_TAG}"
# Check if the tag exists
set +e
git rev-list -n 1 "${XTS_CANDIDATE_TAG}" >/dev/null 2>&1
XTS_COMMIT_FOUND="${?}"
set -e
# Delete the tag if it does exist
if [[ "${XTS_COMMIT_FOUND}" -eq 0 ]]; then
git push --delete origin "${XTS_CANDIDATE_TAG}"
git tag -d "${XTS_CANDIDATE_TAG}"
fi
# Create the new tag
git tag --annotate "${XTS_CANDIDATE_TAG}" --message "chore: tagging commit for XTS promotion"
git push --set-upstream origin --tags
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public enum DispatchType {
TOKEN_INFO(HederaFunctionality.TOKEN_GET_INFO, DEFAULT),
UPDATE_TOKEN_CUSTOM_FEES(HederaFunctionality.TOKEN_FEE_SCHEDULE_UPDATE, DEFAULT),
TOKEN_AIRDROP(HederaFunctionality.TOKEN_AIRDROP, DEFAULT),
TOKEN_CLAIM_AIRDROP(HederaFunctionality.TOKEN_CLAIM_AIRDROP, DEFAULT),
TOKEN_REJECT(HederaFunctionality.TOKEN_REJECT, TOKEN_FUNGIBLE_COMMON);

private final HederaFunctionality functionality;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.associations.AssociationsTranslator;
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.balanceof.BalanceOfTranslator;
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.burn.BurnTranslator;
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.claimairdrops.TokenClaimAirdropTranslator;
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.create.CreateTranslator;
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.customfees.TokenCustomFeesTranslator;
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.decimals.DecimalsTranslator;
Expand Down Expand Up @@ -450,6 +451,15 @@ static CallTranslator<HtsCallAttempt> provideTokenAirdropTranslator(
return translator;
}

@Provides
@Singleton
@IntoSet
@Named("HtsTranslators")
static CallTranslator<HtsCallAttempt> provideTokenClaimAirdropDecoder(
@NonNull final TokenClaimAirdropTranslator translator) {
return translator;
}

@Provides
@Singleton
@IntoSet
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/*
* 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.hts.claimairdrops;

import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TOKEN_ID;
import static com.hedera.hapi.node.base.ResponseCodeEnum.PENDING_AIRDROP_ID_LIST_TOO_LONG;
import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.asTokenId;
import static com.hedera.node.app.spi.workflows.HandleException.validateFalse;
import static com.hedera.node.app.spi.workflows.HandleException.validateTrue;

import com.esaulpaugh.headlong.abi.Address;
import com.esaulpaugh.headlong.abi.Tuple;
import com.hedera.hapi.node.base.AccountID;
import com.hedera.hapi.node.base.NftID;
import com.hedera.hapi.node.base.PendingAirdropId;
import com.hedera.hapi.node.base.TokenID;
import com.hedera.hapi.node.base.TokenType;
import com.hedera.hapi.node.token.TokenClaimAirdropTransactionBody;
import com.hedera.hapi.node.transaction.TransactionBody;
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt;
import com.hedera.node.config.data.TokensConfig;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.util.ArrayList;
import java.util.Arrays;
import javax.inject.Inject;
import javax.inject.Singleton;

@Singleton
public class TokenClaimAirdropDecoder {

// Tuple indexes
private static final int TRANSFER_LIST = 0;
private static final int SENDER = 0;
private static final int RECEIVER = 1;
private static final int TOKEN = 2;
private static final int SERIAL = 3;
private static final int HRC_SENDER = 0;
private static final int HRC_SERIAL = 1;

@Inject
public TokenClaimAirdropDecoder() {
// Dagger2
}

public TransactionBody decodeTokenClaimAirdrop(@NonNull final HtsCallAttempt attempt) {
final var call = TokenClaimAirdropTranslator.CLAIM_AIRDROP.decodeCall(attempt.inputBytes());
final var maxPendingAirdropsToClaim =
attempt.configuration().getConfigData(TokensConfig.class).maxAllowedPendingAirdropsToClaim();
validateFalse(((Tuple[]) call.get(0)).length > maxPendingAirdropsToClaim, PENDING_AIRDROP_ID_LIST_TOO_LONG);

final var transferList = (Tuple[]) call.get(TRANSFER_LIST);
final var pendingAirdrops = new ArrayList<PendingAirdropId>();
Arrays.stream(transferList).forEach(transfer -> {
final var senderAddress = (Address) transfer.get(SENDER);
final var receiverAddress = (Address) transfer.get(RECEIVER);
final var tokenAddress = (Address) transfer.get(TOKEN);
final var serial = (long) transfer.get(SERIAL);

final var senderId = attempt.addressIdConverter().convert(senderAddress);
final var receiverId = attempt.addressIdConverter().convert(receiverAddress);
final var tokenId = asTokenId(tokenAddress);

final var token = attempt.enhancement().nativeOperations().getToken(tokenId.tokenNum());
validateTrue(token != null, INVALID_TOKEN_ID);
if (token.tokenType().equals(TokenType.FUNGIBLE_COMMON)) {
pendingAirdrops.add(pendingFTAirdrop(senderId, receiverId, tokenId));
} else {
pendingAirdrops.add(pendingNFTAirdrop(senderId, receiverId, tokenId, serial));
}
});

return TransactionBody.newBuilder()
.tokenClaimAirdrop(TokenClaimAirdropTransactionBody.newBuilder().pendingAirdrops(pendingAirdrops))
.build();
}

public TransactionBody decodeHrcClaimAirdropFt(@NonNull final HtsCallAttempt attempt) {
final var call = TokenClaimAirdropTranslator.HRC_CLAIM_AIRDROP_FT.decodeCall(attempt.inputBytes());

// As the Token Claim is an operation for the receiver of an Airdrop,
// hence the `transaction sender` in the HRC scenario is in reality the `Airdrop receiver`.
final var receiverId = attempt.senderId();
final var senderAddress = (Address) call.get(HRC_SENDER);
final var token = attempt.redirectTokenId();
validateTrue(token != null, INVALID_TOKEN_ID);
final var senderId = attempt.addressIdConverter().convert(senderAddress);

return TransactionBody.newBuilder()
.tokenClaimAirdrop(TokenClaimAirdropTransactionBody.newBuilder()
.pendingAirdrops(pendingFTAirdrop(senderId, receiverId, token)))
.build();
}

public TransactionBody decodeHrcClaimAirdropNft(@NonNull final HtsCallAttempt attempt) {
final var call = TokenClaimAirdropTranslator.HRC_CLAIM_AIRDROP_NFT.decodeCall(attempt.inputBytes());

// As the Token Claim is an operation for the receiver of an Airdrop,
// hence the `transaction sender` in the HRC scenario is in reality the `Airdrop receiver`.
final var receiverId = attempt.senderId();
final var senderAddress = (Address) call.get(HRC_SENDER);
final var serial = (long) call.get(HRC_SERIAL);
final var token = attempt.redirectTokenId();
validateTrue(token != null, INVALID_TOKEN_ID);
final var senderId = attempt.addressIdConverter().convert(senderAddress);

return TransactionBody.newBuilder()
.tokenClaimAirdrop(TokenClaimAirdropTransactionBody.newBuilder()
.pendingAirdrops(pendingNFTAirdrop(senderId, receiverId, token, serial)))
.build();
}

private PendingAirdropId pendingFTAirdrop(
@NonNull final AccountID senderId, @NonNull final AccountID receiverId, @NonNull final TokenID tokenId) {
return PendingAirdropId.newBuilder()
.senderId(senderId)
.receiverId(receiverId)
.fungibleTokenType(tokenId)
.build();
}

private PendingAirdropId pendingNFTAirdrop(
@NonNull final AccountID senderId,
@NonNull final AccountID receiverId,
@NonNull final TokenID tokenId,
final long serial) {
return PendingAirdropId.newBuilder()
.senderId(senderId)
.receiverId(receiverId)
.nonFungibleToken(NftID.newBuilder().tokenId(tokenId).serialNumber(serial))
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* 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.hts.claimairdrops;

import com.esaulpaugh.headlong.abi.Function;
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.gas.SystemContractGasCalculator;
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.hts.DispatchForResponseCodeHtsCall;
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.service.contract.impl.hevm.HederaWorldUpdater;
import com.hedera.node.config.data.ContractsConfig;
import edu.umd.cs.findbugs.annotations.NonNull;
import javax.inject.Inject;

public class TokenClaimAirdropTranslator extends AbstractCallTranslator<HtsCallAttempt> {
public static final Function CLAIM_AIRDROP =
new Function("claimAirdrops((address,address,address,int64)[])", ReturnTypes.INT_64);
public static final Function HRC_CLAIM_AIRDROP_FT = new Function("claimAirdropFT(address)", ReturnTypes.INT_64);
public static final Function HRC_CLAIM_AIRDROP_NFT =
new Function("claimAirdropNFT(address,int64)", ReturnTypes.INT_64);

private final TokenClaimAirdropDecoder decoder;

@Inject
public TokenClaimAirdropTranslator(@NonNull final TokenClaimAirdropDecoder decoder) {
this.decoder = decoder;
}

@Override
public boolean matches(@NonNull final HtsCallAttempt attempt) {
final var claimAirdropEnabled =
attempt.configuration().getConfigData(ContractsConfig.class).systemContractClaimAirdropsEnabled();
return attempt.isTokenRedirect()
? attempt.isSelectorIfConfigEnabled(HRC_CLAIM_AIRDROP_FT, claimAirdropEnabled)
|| attempt.isSelectorIfConfigEnabled(HRC_CLAIM_AIRDROP_NFT, claimAirdropEnabled)
: attempt.isSelectorIfConfigEnabled(CLAIM_AIRDROP, claimAirdropEnabled);
}

@Override
public Call callFrom(@NonNull final HtsCallAttempt attempt) {
return new DispatchForResponseCodeHtsCall(
attempt,
attempt.isSelector(CLAIM_AIRDROP) ? bodyForClassic(attempt) : bodyForHRC(attempt),
TokenClaimAirdropTranslator::gasRequirement);
}

public static long gasRequirement(
@NonNull final TransactionBody body,
@NonNull final SystemContractGasCalculator systemContractGasCalculator,
@NonNull final HederaWorldUpdater.Enhancement enhancement,
@NonNull final AccountID payerId) {
return systemContractGasCalculator.gasRequirement(body, DispatchType.TOKEN_CLAIM_AIRDROP, payerId);
}

private TransactionBody bodyForClassic(@NonNull final HtsCallAttempt attempt) {
return decoder.decodeTokenClaimAirdrop(attempt);
}

private TransactionBody bodyForHRC(@NonNull final HtsCallAttempt attempt) {
if (attempt.isSelector(HRC_CLAIM_AIRDROP_FT)) {
return decoder.decodeHrcClaimAirdropFt(attempt);
} else {
return decoder.decodeHrcClaimAirdropNft(attempt);
}
}
}
Loading

0 comments on commit d80872a

Please sign in to comment.