From 8a8ab6ce9f8fc6af68c1cf48d17cf8a6ef19d16d Mon Sep 17 00:00:00 2001 From: ramonliu Date: Wed, 2 Jul 2025 16:31:42 +0800 Subject: [PATCH 1/4] feat(tvm): tip-6780: selfdestruct in same transaction --- .../org/tron/core/utils/ProposalUtil.java | 18 ++- .../java/org/tron/core/vm/EnergyCost.java | 9 ++ .../org/tron/core/vm/OperationActions.java | 15 +++ .../org/tron/core/vm/OperationRegistry.java | 11 ++ .../org/tron/core/vm/config/ConfigLoader.java | 1 + .../tron/core/vm/program/ContractState.java | 10 ++ .../org/tron/core/vm/program/Program.java | 108 ++++++++++++++++++ .../tron/core/vm/repository/Repository.java | 4 + .../core/vm/repository/RepositoryImpl.java | 33 ++++++ .../core/store/DynamicPropertiesStore.java | 18 +++ .../common/parameter/CommonParameter.java | 4 + .../src/main/java/org/tron/core/Constant.java | 3 + .../java/org/tron/core/config/Parameter.java | 5 +- .../org/tron/core/vm/config/VMConfig.java | 10 ++ .../src/main/java/org/tron/core/Wallet.java | 5 + .../java/org/tron/core/config/args/Args.java | 5 + .../tron/core/consensus/ProposalService.java | 4 + .../java/org/tron/common/ParameterTest.java | 2 + .../common/runtime/vm/OperationsTest.java | 75 ++++++++++++ .../core/actuator/utils/ProposalUtilTest.java | 54 +++++++++ 20 files changed, 391 insertions(+), 3 deletions(-) diff --git a/actuator/src/main/java/org/tron/core/utils/ProposalUtil.java b/actuator/src/main/java/org/tron/core/utils/ProposalUtil.java index 0bad940da7e..941d565b6ea 100644 --- a/actuator/src/main/java/org/tron/core/utils/ProposalUtil.java +++ b/actuator/src/main/java/org/tron/core/utils/ProposalUtil.java @@ -839,6 +839,21 @@ public static void validator(DynamicPropertiesStore dynamicPropertiesStore, } break; } + case ALLOW_TVM_SELFDESTRUCT_RESTRICTION: { + if (!forkController.pass(ForkBlockVersionEnum.VERSION_4_8_1)) { + throw new ContractValidateException( + "Bad chain parameter id [ALLOW_TVM_SELFDESTRUCT_RESTRICTION]"); + } + if (dynamicPropertiesStore.allowTvmSelfdestructRestriction()) { + throw new ContractValidateException( + "[ALLOW_TVM_SELFDESTRUCT_RESTRICTION] has been valid, no need to propose again"); + } + if (value != 1) { + throw new ContractValidateException( + "This value[ALLOW_TVM_SELFDESTRUCT_RESTRICTION] is only allowed to be 1"); + } + break; + } default: break; } @@ -921,7 +936,8 @@ public enum ProposalType { // current value, value range ALLOW_TVM_CANCUN(83), // 0, 1 ALLOW_STRICT_MATH(87), // 0, 1 CONSENSUS_LOGIC_OPTIMIZATION(88), // 0, 1 - ALLOW_TVM_BLOB(89); // 0, 1 + ALLOW_TVM_BLOB(89), // 0, 1 + ALLOW_TVM_SELFDESTRUCT_RESTRICTION(94); // 0, 1 private long code; diff --git a/actuator/src/main/java/org/tron/core/vm/EnergyCost.java b/actuator/src/main/java/org/tron/core/vm/EnergyCost.java index b758438c940..d47f716943f 100644 --- a/actuator/src/main/java/org/tron/core/vm/EnergyCost.java +++ b/actuator/src/main/java/org/tron/core/vm/EnergyCost.java @@ -55,6 +55,7 @@ public class EnergyCost { private static final long EXT_CODE_SIZE = 20; private static final long EXT_CODE_HASH = 400; private static final long SUICIDE = 0; + private static final long SUICIDE_V2 = 5000; private static final long STOP = 0; private static final long CREATE_DATA = 200; private static final long TLOAD = 100; @@ -289,6 +290,14 @@ public static long getSuicideCost2(Program program) { return getSuicideCost(program); } + public static long getSuicideCost3(Program program) { + DataWord inheritorAddress = program.getStack().peek(); + if (isDeadAccount(program, inheritorAddress)) { + return SUICIDE_V2 + NEW_ACCT_CALL; + } + return SUICIDE_V2; + } + public static long getBalanceCost(Program ignored) { return BALANCE; } diff --git a/actuator/src/main/java/org/tron/core/vm/OperationActions.java b/actuator/src/main/java/org/tron/core/vm/OperationActions.java index f10fb37dd7e..0d978743a5e 100644 --- a/actuator/src/main/java/org/tron/core/vm/OperationActions.java +++ b/actuator/src/main/java/org/tron/core/vm/OperationActions.java @@ -1072,4 +1072,19 @@ public static void suicideAction(Program program) { program.stop(); } + public static void suicideAction2(Program program) { + if (program.isStaticCall()) { + throw new Program.StaticCallModificationException(); + } + + if (!program.canSuicide2()) { + program.getResult().setRevert(); + } else { + DataWord address = program.stackPop(); + program.suicide2(address); + } + + program.stop(); + } + } diff --git a/actuator/src/main/java/org/tron/core/vm/OperationRegistry.java b/actuator/src/main/java/org/tron/core/vm/OperationRegistry.java index be29238a775..f6140107efb 100644 --- a/actuator/src/main/java/org/tron/core/vm/OperationRegistry.java +++ b/actuator/src/main/java/org/tron/core/vm/OperationRegistry.java @@ -79,6 +79,10 @@ public static JumpTable getTable() { adjustForFairEnergy(table); } + if (VMConfig.allowTvmSelfdestructRestriction()) { + adjustSelfdestruct(table); + } + return table; } @@ -695,4 +699,11 @@ public static void appendCancunOperations(JumpTable table) { OperationActions::blobBaseFeeAction, tvmBlobProposal)); } + + public static void adjustSelfdestruct(JumpTable table) { + table.set(new Operation( + Op.SUICIDE, 1, 0, + EnergyCost::getSuicideCost3, + OperationActions::suicideAction2)); + } } diff --git a/actuator/src/main/java/org/tron/core/vm/config/ConfigLoader.java b/actuator/src/main/java/org/tron/core/vm/config/ConfigLoader.java index 1ec8a75d669..e0ebca329cd 100644 --- a/actuator/src/main/java/org/tron/core/vm/config/ConfigLoader.java +++ b/actuator/src/main/java/org/tron/core/vm/config/ConfigLoader.java @@ -44,6 +44,7 @@ public static void load(StoreFactory storeFactory) { VMConfig.initAllowTvmCancun(ds.getAllowTvmCancun()); VMConfig.initDisableJavaLangMath(ds.getConsensusLogicOptimization()); VMConfig.initAllowTvmBlob(ds.getAllowTvmBlob()); + VMConfig.initAllowTvmSelfdestructRestriction(ds.getAllowTvmSelfdestructRestriction()); } } } diff --git a/actuator/src/main/java/org/tron/core/vm/program/ContractState.java b/actuator/src/main/java/org/tron/core/vm/program/ContractState.java index e095201e393..c6347b9a072 100644 --- a/actuator/src/main/java/org/tron/core/vm/program/ContractState.java +++ b/actuator/src/main/java/org/tron/core/vm/program/ContractState.java @@ -121,6 +121,16 @@ public void updateContractState(byte[] address, ContractStateCapsule contractSta repository.updateContractState(address, contractStateCapsule); } + @Override + public void putNewContract(byte[] address) { + repository.putNewContract(address); + } + + @Override + public boolean isNewContract(byte[] address) { + return repository.isNewContract(address); + } + @Override public void updateAccount(byte[] address, AccountCapsule accountCapsule) { repository.updateAccount(address, accountCapsule); diff --git a/actuator/src/main/java/org/tron/core/vm/program/Program.java b/actuator/src/main/java/org/tron/core/vm/program/Program.java index 5da0b02ecb7..59ac8314683 100644 --- a/actuator/src/main/java/org/tron/core/vm/program/Program.java +++ b/actuator/src/main/java/org/tron/core/vm/program/Program.java @@ -515,6 +515,72 @@ public void suicide(DataWord obtainerAddress) { getResult().addDeleteAccount(this.getContractAddress()); } + public void suicide2(DataWord obtainerAddress) { + + byte[] owner = getContextAddress(); + boolean isNewContract = getContractState().isNewContract(owner); + if (isNewContract) { + suicide(obtainerAddress); + return; + } + + byte[] obtainer = obtainerAddress.toTronAddress(); + + long balance = getContractState().getBalance(owner); + + if (logger.isDebugEnabled()) { + logger.debug("Transfer to: [{}] heritage: [{}]", + Hex.toHexString(obtainer), + balance); + } + + increaseNonce(); + + InternalTransaction internalTx = addInternalTx(null, owner, obtainer, balance, null, + "suicide", nonce, getContractState().getAccount(owner).getAssetMapV2()); + + if (FastByteComparisons.isEqual(owner, obtainer)) { + return; + } + + if (VMConfig.allowTvmVote()) { + withdrawRewardAndCancelVote(owner, getContractState()); + balance = getContractState().getBalance(owner); + if (internalTx != null && balance != internalTx.getValue()) { + internalTx.setValue(balance); + } + } + + // transfer balance and trc10 + createAccountIfNotExist(getContractState(), obtainer); + try { + MUtil.transfer(getContractState(), owner, obtainer, balance); + if (VMConfig.allowTvmTransferTrc10()) { + MUtil.transferAllToken(getContractState(), owner, obtainer); + } + } catch (ContractValidateException e) { + if (VMConfig.allowTvmConstantinople()) { + throw new TransferException( + "transfer all token or transfer all trx failed in suicide: %s", e.getMessage()); + } + throw new BytecodeExecutionException("transfer failure"); + } + + // transfer freeze + if (VMConfig.allowTvmFreeze()) { + transferDelegatedResourceToInheritor(owner, obtainer, getContractState()); + } + + // transfer freezeV2 + if (VMConfig.allowTvmFreezeV2()) { + long expireUnfrozenBalance = + transferFrozenV2BalanceToInheritor(owner, obtainer, getContractState()); + if (expireUnfrozenBalance > 0 && internalTx != null) { + internalTx.setValue(internalTx.getValue() + expireUnfrozenBalance); + } + } + } + public Repository getContractState() { return this.contractState; } @@ -544,6 +610,11 @@ private void transferDelegatedResourceToInheritor(byte[] ownerAddr, byte[] inher // transfer all kinds of frozen balance to BlackHole repo.addBalance(inheritorAddr, frozenBalanceForBandwidthOfOwner + frozenBalanceForEnergyOfOwner); + + if (VMConfig.allowTvmSelfdestructRestriction()) { + clearOwnerFreeze(ownerCapsule); + repo.updateAccount(ownerAddr, ownerCapsule); + } } private long transferFrozenV2BalanceToInheritor(byte[] ownerAddr, byte[] inheritorAddr, Repository repo) { @@ -609,6 +680,11 @@ private long transferFrozenV2BalanceToInheritor(byte[] ownerAddr, byte[] inherit return expireUnfrozenBalance; } + private void clearOwnerFreeze(AccountCapsule ownerCapsule) { + ownerCapsule.setFrozenForBandwidth(0, 0); + ownerCapsule.setFrozenForEnergy(0, 0); + } + private void clearOwnerFreezeV2(AccountCapsule ownerCapsule) { ownerCapsule.clearFrozenV2(); ownerCapsule.setNetUsage(0); @@ -666,6 +742,38 @@ public boolean canSuicide() { // return freezeCheck && voteCheck; } + public boolean canSuicide2() { + byte[] owner = getContextAddress(); + AccountCapsule accountCapsule = getContractState().getAccount(owner); + + return freezeV1Check(accountCapsule) && freezeV2Check(accountCapsule); + } + + private boolean freezeV1Check(AccountCapsule accountCapsule) { + if (!VMConfig.allowTvmFreeze()) { + return true; + } + + // check freeze + long now = getContractState().getDynamicPropertiesStore().getLatestBlockHeaderTimestamp(); + // bandwidth + if (accountCapsule.getFrozenCount() > 0 + && accountCapsule.getFrozenList().stream() + .anyMatch(frozen -> frozen.getExpireTime() > now)) { + return false; + } + // energy + Protocol.Account.Frozen frozenEnergy = + accountCapsule.getAccountResource().getFrozenBalanceForEnergy(); + if (frozenEnergy.getFrozenBalance() > 0 && frozenEnergy.getExpireTime() > now) { + return false; + } + + // check delegate + return accountCapsule.getDelegatedFrozenBalanceForBandwidth() == 0 + && accountCapsule.getDelegatedFrozenBalanceForEnergy() == 0; + } + private boolean freezeV2Check(AccountCapsule accountCapsule) { if (!VMConfig.allowTvmFreezeV2()) { return true; diff --git a/actuator/src/main/java/org/tron/core/vm/repository/Repository.java b/actuator/src/main/java/org/tron/core/vm/repository/Repository.java index 664ee26ee92..8f91d59d0b8 100644 --- a/actuator/src/main/java/org/tron/core/vm/repository/Repository.java +++ b/actuator/src/main/java/org/tron/core/vm/repository/Repository.java @@ -55,6 +55,10 @@ public interface Repository { void updateContractState(byte[] address, ContractStateCapsule contractStateCapsule); + void putNewContract(byte[] address); + + boolean isNewContract(byte[] address); + void updateAccount(byte[] address, AccountCapsule accountCapsule); void updateDynamicProperty(byte[] word, BytesCapsule bytesCapsule); diff --git a/actuator/src/main/java/org/tron/core/vm/repository/RepositoryImpl.java b/actuator/src/main/java/org/tron/core/vm/repository/RepositoryImpl.java index a064cbf6c8a..9de7c0691ba 100644 --- a/actuator/src/main/java/org/tron/core/vm/repository/RepositoryImpl.java +++ b/actuator/src/main/java/org/tron/core/vm/repository/RepositoryImpl.java @@ -9,6 +9,7 @@ import com.google.common.collect.HashBasedTable; import com.google.protobuf.ByteString; import java.util.HashMap; +import java.util.HashSet; import java.util.Optional; import lombok.Getter; import lombok.extern.slf4j.Slf4j; @@ -135,6 +136,7 @@ public class RepositoryImpl implements Repository { private final HashMap> delegationCache = new HashMap<>(); private final HashMap> delegatedResourceAccountIndexCache = new HashMap<>(); private final HashBasedTable> transientStorage = HashBasedTable.create(); + private final HashSet newContractCache = new HashSet<>(); public static void removeLruCache(byte[] address) { } @@ -479,6 +481,7 @@ public void deleteContract(byte[] address) { public void createContract(byte[] address, ContractCapsule contractCapsule) { contractCache.put(Key.create(address), Value.create(contractCapsule, Type.CREATE)); + putNewContract(address); } @Override @@ -533,6 +536,29 @@ public void updateContractState(byte[] address, ContractStateCapsule contractSta Value.create(contractStateCapsule, Type.DIRTY)); } + @Override + public void putNewContract(byte[] address) { + newContractCache.add(Key.create(address)); + } + + @Override + public boolean isNewContract(byte[] address) { + Key key = Key.create(address); + if (newContractCache.contains(key)) { + return true; + } + + if (parent != null) { + boolean isNew = parent.isNewContract(address); + if (isNew) { + newContractCache.add(key); + } + return isNew; + } else { + return false; + } + } + @Override public void updateAccount(byte[] address, AccountCapsule accountCapsule) { accountCache.put(Key.create(address), @@ -740,6 +766,7 @@ public void commit() { commitDelegationCache(repository); commitDelegatedResourceAccountIndexCache(repository); commitTransientStorage(repository); + commitNewContractCache(repository); } @Override @@ -1060,6 +1087,12 @@ public void commitTransientStorage(Repository deposit) { } } + public void commitNewContractCache(Repository deposit) { + if (deposit != null) { + newContractCache.forEach(key -> deposit.putNewContract(key.getData())); + } + } + /** * Get the block id from the number. */ diff --git a/chainbase/src/main/java/org/tron/core/store/DynamicPropertiesStore.java b/chainbase/src/main/java/org/tron/core/store/DynamicPropertiesStore.java index 91b0cff68a0..3132ddaa768 100644 --- a/chainbase/src/main/java/org/tron/core/store/DynamicPropertiesStore.java +++ b/chainbase/src/main/java/org/tron/core/store/DynamicPropertiesStore.java @@ -232,6 +232,9 @@ public class DynamicPropertiesStore extends TronStoreWithRevoking private static final byte[] ALLOW_TVM_BLOB = "ALLOW_TVM_BLOB".getBytes(); + private static final byte[] ALLOW_TVM_SELFDESTRUCT_RESTRICTION = + "ALLOW_TVM_SELFDESTRUCT_RESTRICTION".getBytes(); + @Autowired private DynamicPropertiesStore(@Value("properties") String dbName) { super(dbName); @@ -2946,6 +2949,21 @@ public long getAllowTvmBlob() { .orElse(CommonParameter.getInstance().getAllowTvmBlob()); } + public long getAllowTvmSelfdestructRestriction() { + return Optional.ofNullable(getUnchecked(ALLOW_TVM_SELFDESTRUCT_RESTRICTION)) + .map(BytesCapsule::getData) + .map(ByteArray::toLong) + .orElse(CommonParameter.getInstance().getAllowTvmSelfdestructRestriction()); + } + + public void saveAllowTvmSelfdestructRestriction(long value) { + this.put(ALLOW_TVM_SELFDESTRUCT_RESTRICTION, new BytesCapsule(ByteArray.fromLong(value))); + } + + public boolean allowTvmSelfdestructRestriction() { + return getAllowTvmSelfdestructRestriction() == 1L; + } + private static class DynamicResourceProperties { private static final byte[] ONE_DAY_NET_LIMIT = "ONE_DAY_NET_LIMIT".getBytes(); diff --git a/common/src/main/java/org/tron/common/parameter/CommonParameter.java b/common/src/main/java/org/tron/common/parameter/CommonParameter.java index 45893970fb0..4289beb11e5 100644 --- a/common/src/main/java/org/tron/common/parameter/CommonParameter.java +++ b/common/src/main/java/org/tron/common/parameter/CommonParameter.java @@ -724,6 +724,10 @@ public class CommonParameter { @Setter public long allowTvmBlob; + @Getter + @Setter + public long allowTvmSelfdestructRestriction; + private static double calcMaxTimeRatio() { //return max(2.0, min(5.0, 5 * 4.0 / max(Runtime.getRuntime().availableProcessors(), 1))); return 5.0; diff --git a/common/src/main/java/org/tron/core/Constant.java b/common/src/main/java/org/tron/core/Constant.java index c5a8a02fb4e..48c6566c4d3 100644 --- a/common/src/main/java/org/tron/core/Constant.java +++ b/common/src/main/java/org/tron/core/Constant.java @@ -405,4 +405,7 @@ public class Constant { public static final String COMMITTEE_ALLOW_TVM_CANCUN = "committee.allowTvmCancun"; public static final String COMMITTEE_ALLOW_TVM_BLOB = "committee.allowTvmBlob"; + + public static final String COMMITTEE_ALLOW_TVM_SELFDESTRUCT_RESTRICTION = + "committee.allowTvmSelfdestructRestriction"; } diff --git a/common/src/main/java/org/tron/core/config/Parameter.java b/common/src/main/java/org/tron/core/config/Parameter.java index a71dc58e8bd..7d0681a9f91 100644 --- a/common/src/main/java/org/tron/core/config/Parameter.java +++ b/common/src/main/java/org/tron/core/config/Parameter.java @@ -26,7 +26,8 @@ public enum ForkBlockVersionEnum { VERSION_4_7_4(29, 1596780000000L, 80), VERSION_4_7_5(30, 1596780000000L, 80), VERSION_4_7_7(31, 1596780000000L, 80), - VERSION_4_8_0(32, 1596780000000L, 80); + VERSION_4_8_0(32, 1596780000000L, 80), + VERSION_4_8_1(33, 1596780000000L, 80); // if add a version, modify BLOCK_VERSION simultaneously @Getter @@ -75,7 +76,7 @@ public class ChainConstant { public static final int SINGLE_REPEAT = 1; public static final int BLOCK_FILLED_SLOTS_NUMBER = 128; public static final int MAX_FROZEN_NUMBER = 1; - public static final int BLOCK_VERSION = 32; + public static final int BLOCK_VERSION = 33; public static final long FROZEN_PERIOD = 86_400_000L; public static final long DELEGATE_PERIOD = 3 * 86_400_000L; public static final long TRX_PRECISION = 1000_000L; diff --git a/common/src/main/java/org/tron/core/vm/config/VMConfig.java b/common/src/main/java/org/tron/core/vm/config/VMConfig.java index 2bdbeb78b92..578827b2f8c 100644 --- a/common/src/main/java/org/tron/core/vm/config/VMConfig.java +++ b/common/src/main/java/org/tron/core/vm/config/VMConfig.java @@ -59,6 +59,8 @@ public class VMConfig { private static boolean ALLOW_TVM_BLOB = false; + private static boolean ALLOW_TVM_SELFDESTRUCT_RESTRICTION = false; + private VMConfig() { } @@ -166,6 +168,10 @@ public static void initAllowTvmBlob(long allow) { ALLOW_TVM_BLOB = allow == 1; } + public static void initAllowTvmSelfdestructRestriction(long allow) { + ALLOW_TVM_SELFDESTRUCT_RESTRICTION = allow == 1; + } + public static boolean getEnergyLimitHardFork() { return CommonParameter.ENERGY_LIMIT_HARD_FORK; } @@ -261,4 +267,8 @@ public static boolean disableJavaLangMath() { public static boolean allowTvmBlob() { return ALLOW_TVM_BLOB; } + + public static boolean allowTvmSelfdestructRestriction() { + return ALLOW_TVM_SELFDESTRUCT_RESTRICTION; + } } diff --git a/framework/src/main/java/org/tron/core/Wallet.java b/framework/src/main/java/org/tron/core/Wallet.java index 8dfb18331ff..c6050f5cb07 100755 --- a/framework/src/main/java/org/tron/core/Wallet.java +++ b/framework/src/main/java/org/tron/core/Wallet.java @@ -1387,6 +1387,11 @@ public Protocol.ChainParameters getChainParameters() { .setValue(dbManager.getDynamicPropertiesStore().getAllowTvmBlob()) .build()); + builder.addChainParameter(Protocol.ChainParameters.ChainParameter.newBuilder() + .setKey("getAllowTvmSelfdestructRestriction") + .setValue(dbManager.getDynamicPropertiesStore().getAllowTvmSelfdestructRestriction()) + .build()); + return builder.build(); } diff --git a/framework/src/main/java/org/tron/core/config/args/Args.java b/framework/src/main/java/org/tron/core/config/args/Args.java index 277b3e7bc79..3cf6d18aa0b 100644 --- a/framework/src/main/java/org/tron/core/config/args/Args.java +++ b/framework/src/main/java/org/tron/core/config/args/Args.java @@ -247,6 +247,7 @@ public static void clearParam() { PARAMETER.consensusLogicOptimization = 0; PARAMETER.allowTvmCancun = 0; PARAMETER.allowTvmBlob = 0; + PARAMETER.allowTvmSelfdestructRestriction = 0; } /** @@ -1297,6 +1298,10 @@ public static void setParam(final Config config) { config.hasPath(Constant.COMMITTEE_ALLOW_TVM_BLOB) ? config .getInt(Constant.COMMITTEE_ALLOW_TVM_BLOB) : 0; + PARAMETER.allowTvmSelfdestructRestriction = + config.hasPath(Constant.COMMITTEE_ALLOW_TVM_SELFDESTRUCT_RESTRICTION) ? config + .getInt(Constant.COMMITTEE_ALLOW_TVM_SELFDESTRUCT_RESTRICTION) : 0; + logConfig(); } diff --git a/framework/src/main/java/org/tron/core/consensus/ProposalService.java b/framework/src/main/java/org/tron/core/consensus/ProposalService.java index b25f0d6fa8d..0b81dcb8811 100644 --- a/framework/src/main/java/org/tron/core/consensus/ProposalService.java +++ b/framework/src/main/java/org/tron/core/consensus/ProposalService.java @@ -384,6 +384,10 @@ public static boolean process(Manager manager, ProposalCapsule proposalCapsule) manager.getDynamicPropertiesStore().saveAllowTvmBlob(entry.getValue()); break; } + case ALLOW_TVM_SELFDESTRUCT_RESTRICTION: { + manager.getDynamicPropertiesStore().saveAllowTvmSelfdestructRestriction(entry.getValue()); + break; + } default: find = false; break; diff --git a/framework/src/test/java/org/tron/common/ParameterTest.java b/framework/src/test/java/org/tron/common/ParameterTest.java index b16be405f61..03fdf462b26 100644 --- a/framework/src/test/java/org/tron/common/ParameterTest.java +++ b/framework/src/test/java/org/tron/common/ParameterTest.java @@ -316,5 +316,7 @@ public void testCommonParameter() { assertEquals(1, parameter.getAllowEnergyAdjustment()); parameter.setMaxCreateAccountTxSize(1000); assertEquals(1000, parameter.getMaxCreateAccountTxSize()); + parameter.setAllowTvmSelfdestructRestriction(1); + assertEquals(1, parameter.getAllowTvmSelfdestructRestriction()); } } diff --git a/framework/src/test/java/org/tron/common/runtime/vm/OperationsTest.java b/framework/src/test/java/org/tron/common/runtime/vm/OperationsTest.java index 3315005b7d2..922d80361b5 100644 --- a/framework/src/test/java/org/tron/common/runtime/vm/OperationsTest.java +++ b/framework/src/test/java/org/tron/common/runtime/vm/OperationsTest.java @@ -1,6 +1,8 @@ package org.tron.common.runtime.vm; import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.tron.core.config.Parameter.ChainConstant.FROZEN_PERIOD; import java.util.List; import java.util.Random; @@ -13,12 +15,14 @@ import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; +import org.mockito.Mockito; import org.springframework.util.StringUtils; import org.tron.common.BaseTest; import org.tron.common.parameter.CommonParameter; import org.tron.common.runtime.InternalTransaction; import org.tron.common.utils.DecodeUtil; import org.tron.core.Constant; +import org.tron.core.capsule.AccountCapsule; import org.tron.core.config.args.Args; import org.tron.core.exception.ContractValidateException; import org.tron.core.store.StoreFactory; @@ -32,6 +36,7 @@ import org.tron.core.vm.config.VMConfig; import org.tron.core.vm.program.Program; import org.tron.core.vm.program.invoke.ProgramInvokeMockImpl; +import org.tron.core.vm.repository.Repository; import org.tron.protos.Protocol; @Slf4j @@ -911,6 +916,76 @@ public void testSuicideAction() throws ContractValidateException { VMConfig.initAllowEnergyAdjustment(0); } + @Test + public void testCanSuicide2() throws ContractValidateException { + VMConfig.initAllowTvmFreeze(1); + VMConfig.initAllowTvmFreezeV2(1); + + byte[] contractAddr = Hex.decode("41471fd3ad3e9eeadeec4608b92d16ce6b500704cc"); + invoke = new ProgramInvokeMockImpl(StoreFactory.getInstance(), new byte[0], contractAddr); + + program = new Program(null, null, invoke, + new InternalTransaction( + Protocol.Transaction.getDefaultInstance(), + InternalTransaction.TrxType.TRX_UNKNOWN_TYPE)); + program.getContractState().createAccount( + program.getContextAddress(), Protocol.AccountType.Contract); + Assert.assertTrue(program.canSuicide2()); + + long nowInMs = + program.getContractState().getDynamicPropertiesStore().getLatestBlockHeaderTimestamp(); + long expireTime = nowInMs + FROZEN_PERIOD; + AccountCapsule owner = program.getContractState().getAccount(program.getContextAddress()); + owner.setFrozenForEnergy(1000000, expireTime); + program.getContractState().updateAccount(program.getContextAddress(), owner); + Assert.assertFalse(program.canSuicide2()); + + VMConfig.initAllowTvmFreeze(0); + VMConfig.initAllowTvmFreezeV2(0); + } + + @Test + public void testSuicideAction2() throws ContractValidateException { + byte[] contractAddr = Hex.decode("41471fd3ad3e9eeadeec4608b92d16ce6b500704cc"); + invoke = new ProgramInvokeMockImpl(StoreFactory.getInstance(), new byte[0], contractAddr); + Assert.assertTrue(invoke.getDeposit().isNewContract(contractAddr)); + + program = new Program(null, null, invoke, + new InternalTransaction( + Protocol.Transaction.getDefaultInstance(), + InternalTransaction.TrxType.TRX_UNKNOWN_TYPE)); + + VMConfig.initAllowEnergyAdjustment(1); + VMConfig.initAllowTvmSelfdestructRestriction(1); + byte prePrefixByte = DecodeUtil.addressPreFixByte; + DecodeUtil.addressPreFixByte = Constant.ADD_PRE_FIX_BYTE_MAINNET; + + program.suicide2(new DataWord( + dbManager.getAccountStore().getBlackhole().getAddress().toByteArray())); + + Assert.assertEquals(1, program.getResult().getDeleteAccounts().size()); + + + invoke = new ProgramInvokeMockImpl(StoreFactory.getInstance(), new byte[0], contractAddr); + program = new Program(null, null, invoke, + new InternalTransaction( + Protocol.Transaction.getDefaultInstance(), + InternalTransaction.TrxType.TRX_UNKNOWN_TYPE)); + Program spyProgram = Mockito.spy(program); + Repository realContractState = program.getContractState(); + Repository spyContractState = Mockito.spy(realContractState); + Mockito.when(spyContractState.isNewContract(any(byte[].class))).thenReturn(false); + Mockito.when(spyProgram.getContractState()).thenReturn(spyContractState); + spyProgram.suicide2(new DataWord( + dbManager.getAccountStore().getBlackhole().getAddress().toByteArray())); + + Assert.assertEquals(0, spyProgram.getResult().getDeleteAccounts().size()); + + DecodeUtil.addressPreFixByte = prePrefixByte; + VMConfig.initAllowEnergyAdjustment(0); + VMConfig.initAllowTvmSelfdestructRestriction(0); + } + @Test public void testVoteWitnessCost() throws ContractValidateException { // Build stack environment, the stack from top to bottom is 0x00, 0x80, 0x00, 0x80 diff --git a/framework/src/test/java/org/tron/core/actuator/utils/ProposalUtilTest.java b/framework/src/test/java/org/tron/core/actuator/utils/ProposalUtilTest.java index e8a1e862f54..d83f7f14e29 100644 --- a/framework/src/test/java/org/tron/core/actuator/utils/ProposalUtilTest.java +++ b/framework/src/test/java/org/tron/core/actuator/utils/ProposalUtilTest.java @@ -439,6 +439,8 @@ public void validateCheck() { testAllowTvmBlobProposal(); + testAllowTvmSelfdestructRestrictionProposal(); + forkUtils.getManager().getDynamicPropertiesStore() .statsByVersion(ForkBlockVersionEnum.ENERGY_LIMIT.getValue(), stats); forkUtils.reset(); @@ -659,6 +661,58 @@ private void testAllowTvmBlobProposal() { } + private void testAllowTvmSelfdestructRestrictionProposal() { + byte[] stats = new byte[27]; + forkUtils.getManager().getDynamicPropertiesStore() + .statsByVersion(ForkBlockVersionEnum.VERSION_4_8_1.getValue(), stats); + try { + ProposalUtil.validator(dynamicPropertiesStore, forkUtils, + ProposalType.ALLOW_TVM_SELFDESTRUCT_RESTRICTION.getCode(), 1); + Assert.fail(); + } catch (ContractValidateException e) { + Assert.assertEquals( + "Bad chain parameter id [ALLOW_TVM_SELFDESTRUCT_RESTRICTION]", + e.getMessage()); + } + + long maintenanceTimeInterval = forkUtils.getManager().getDynamicPropertiesStore() + .getMaintenanceTimeInterval(); + + long hardForkTime = + ((ForkBlockVersionEnum.VERSION_4_8_1.getHardForkTime() - 1) / maintenanceTimeInterval + 1) + * maintenanceTimeInterval; + forkUtils.getManager().getDynamicPropertiesStore() + .saveLatestBlockHeaderTimestamp(hardForkTime + 1); + + stats = new byte[27]; + Arrays.fill(stats, (byte) 1); + forkUtils.getManager().getDynamicPropertiesStore() + .statsByVersion(ForkBlockVersionEnum.VERSION_4_8_1.getValue(), stats); + + // Should fail because the proposal value is invalid + try { + ProposalUtil.validator(dynamicPropertiesStore, forkUtils, + ProposalType.ALLOW_TVM_SELFDESTRUCT_RESTRICTION.getCode(), 2); + Assert.fail(); + } catch (ContractValidateException e) { + Assert.assertEquals( + "This value[ALLOW_TVM_SELFDESTRUCT_RESTRICTION] is only allowed to be 1", + e.getMessage()); + } + + dynamicPropertiesStore.saveAllowTvmSelfdestructRestriction(1); + try { + ProposalUtil.validator(dynamicPropertiesStore, forkUtils, + ProposalType.ALLOW_TVM_SELFDESTRUCT_RESTRICTION.getCode(), 1); + Assert.fail(); + } catch (ContractValidateException e) { + Assert.assertEquals( + "[ALLOW_TVM_SELFDESTRUCT_RESTRICTION] has been valid, no need to propose again", + e.getMessage()); + } + + } + @Test public void blockVersionCheck() { for (ForkBlockVersionEnum forkVersion : ForkBlockVersionEnum.values()) { From ad3a6abe5de76f8fcf64eac9bb7dacf2127b2df6 Mon Sep 17 00:00:00 2001 From: hanliang Date: Tue, 19 Aug 2025 15:19:31 +0800 Subject: [PATCH 2/4] test(tvm): add test for tip-6780 --- .../common/runtime/vm/OperationsTest.java | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/framework/src/test/java/org/tron/common/runtime/vm/OperationsTest.java b/framework/src/test/java/org/tron/common/runtime/vm/OperationsTest.java index 922d80361b5..ac1ceb87f27 100644 --- a/framework/src/test/java/org/tron/common/runtime/vm/OperationsTest.java +++ b/framework/src/test/java/org/tron/common/runtime/vm/OperationsTest.java @@ -26,12 +26,7 @@ import org.tron.core.config.args.Args; import org.tron.core.exception.ContractValidateException; import org.tron.core.store.StoreFactory; -import org.tron.core.vm.EnergyCost; -import org.tron.core.vm.JumpTable; -import org.tron.core.vm.Op; -import org.tron.core.vm.Operation; -import org.tron.core.vm.OperationRegistry; -import org.tron.core.vm.VM; +import org.tron.core.vm.*; import org.tron.core.vm.config.ConfigLoader; import org.tron.core.vm.config.VMConfig; import org.tron.core.vm.program.Program; @@ -891,6 +886,12 @@ public void testSuicideCost() throws ContractValidateException { Assert.assertEquals(25000, EnergyCost.getSuicideCost2(program)); invoke.getDeposit().createAccount(receiver2, Protocol.AccountType.Normal); Assert.assertEquals(0, EnergyCost.getSuicideCost2(program)); + + byte[] receiver3 = generateRandomAddress(); + program.stackPush(new DataWord(receiver3)); + Assert.assertEquals(30000, EnergyCost.getSuicideCost2(program)); + invoke.getDeposit().createAccount(receiver3, Protocol.AccountType.Normal); + Assert.assertEquals(5000, EnergyCost.getSuicideCost2(program)); } @Test @@ -957,11 +958,14 @@ public void testSuicideAction2() throws ContractValidateException { VMConfig.initAllowEnergyAdjustment(1); VMConfig.initAllowTvmSelfdestructRestriction(1); + VMConfig.initAllowTvmFreeze(1); + VMConfig.initAllowTvmFreezeV2(1); + VMConfig.initAllowTvmCompatibleEvm(1); + VMConfig.initAllowTvmVote(1); byte prePrefixByte = DecodeUtil.addressPreFixByte; DecodeUtil.addressPreFixByte = Constant.ADD_PRE_FIX_BYTE_MAINNET; - program.suicide2(new DataWord( - dbManager.getAccountStore().getBlackhole().getAddress().toByteArray())); + OperationActions.suicideAction2(program); Assert.assertEquals(1, program.getResult().getDeleteAccounts().size()); @@ -984,6 +988,10 @@ public void testSuicideAction2() throws ContractValidateException { DecodeUtil.addressPreFixByte = prePrefixByte; VMConfig.initAllowEnergyAdjustment(0); VMConfig.initAllowTvmSelfdestructRestriction(0); + VMConfig.initAllowTvmFreeze(0); + VMConfig.initAllowTvmFreezeV2(0); + VMConfig.initAllowTvmCompatibleEvm(0); + VMConfig.initAllowTvmVote(0); } @Test From c579744e462c2a657df36d766ed2d9982a0ad24f Mon Sep 17 00:00:00 2001 From: hanliang Date: Tue, 19 Aug 2025 16:11:16 +0800 Subject: [PATCH 3/4] test(tvm): fix import style --- .../java/org/tron/common/runtime/vm/OperationsTest.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/framework/src/test/java/org/tron/common/runtime/vm/OperationsTest.java b/framework/src/test/java/org/tron/common/runtime/vm/OperationsTest.java index ac1ceb87f27..0c477278605 100644 --- a/framework/src/test/java/org/tron/common/runtime/vm/OperationsTest.java +++ b/framework/src/test/java/org/tron/common/runtime/vm/OperationsTest.java @@ -26,7 +26,13 @@ import org.tron.core.config.args.Args; import org.tron.core.exception.ContractValidateException; import org.tron.core.store.StoreFactory; -import org.tron.core.vm.*; +import org.tron.core.vm.EnergyCost; +import org.tron.core.vm.JumpTable; +import org.tron.core.vm.Op; +import org.tron.core.vm.Operation; +import org.tron.core.vm.OperationRegistry; +import org.tron.core.vm.VM; +import org.tron.core.vm.OperationActions; import org.tron.core.vm.config.ConfigLoader; import org.tron.core.vm.config.VMConfig; import org.tron.core.vm.program.Program; From a769b2126f172945ed0725097ab8af2ce2d5d6f5 Mon Sep 17 00:00:00 2001 From: hanliang Date: Tue, 19 Aug 2025 17:47:55 +0800 Subject: [PATCH 4/4] test(tvm): fix suicide energy cost test and suicideAction test. fix checkstyle warning --- .../java/org/tron/common/runtime/vm/OperationsTest.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/framework/src/test/java/org/tron/common/runtime/vm/OperationsTest.java b/framework/src/test/java/org/tron/common/runtime/vm/OperationsTest.java index 0c477278605..b447af87bc2 100644 --- a/framework/src/test/java/org/tron/common/runtime/vm/OperationsTest.java +++ b/framework/src/test/java/org/tron/common/runtime/vm/OperationsTest.java @@ -30,9 +30,9 @@ import org.tron.core.vm.JumpTable; import org.tron.core.vm.Op; import org.tron.core.vm.Operation; +import org.tron.core.vm.OperationActions; import org.tron.core.vm.OperationRegistry; import org.tron.core.vm.VM; -import org.tron.core.vm.OperationActions; import org.tron.core.vm.config.ConfigLoader; import org.tron.core.vm.config.VMConfig; import org.tron.core.vm.program.Program; @@ -895,9 +895,9 @@ public void testSuicideCost() throws ContractValidateException { byte[] receiver3 = generateRandomAddress(); program.stackPush(new DataWord(receiver3)); - Assert.assertEquals(30000, EnergyCost.getSuicideCost2(program)); + Assert.assertEquals(30000, EnergyCost.getSuicideCost3(program)); invoke.getDeposit().createAccount(receiver3, Protocol.AccountType.Normal); - Assert.assertEquals(5000, EnergyCost.getSuicideCost2(program)); + Assert.assertEquals(5000, EnergyCost.getSuicideCost3(program)); } @Test @@ -971,6 +971,8 @@ public void testSuicideAction2() throws ContractValidateException { byte prePrefixByte = DecodeUtil.addressPreFixByte; DecodeUtil.addressPreFixByte = Constant.ADD_PRE_FIX_BYTE_MAINNET; + program.stackPush(new DataWord( + dbManager.getAccountStore().getBlackhole().getAddress().toByteArray())); OperationActions.suicideAction2(program); Assert.assertEquals(1, program.getResult().getDeleteAccounts().size());