Skip to content

Commit 101599d

Browse files
committed
fix(vm): record precompile value transfers
record internal transactions for successful TRX and TRC10 value transfers to precompiled contracts. cover failed calls and transaction-scoped nonce sequencing.
1 parent 4b5d37d commit 101599d

2 files changed

Lines changed: 221 additions & 0 deletions

File tree

actuator/src/main/java/org/tron/core/vm/program/Program.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1740,6 +1740,16 @@ public void callToPrecompiledAddress(MessageCall msg,
17401740
this.refundEnergy(msg.getEnergy().longValue() - requiredEnergy, CALL_PRE_COMPILED);
17411741
this.stackPushOne();
17421742
returnDataBuffer = out.getRight();
1743+
if (endowment > 0) {
1744+
increaseNonce();
1745+
HashMap<String, Long> tokenInfo = null;
1746+
if (isTokenTransfer) {
1747+
tokenInfo = new HashMap<>();
1748+
tokenInfo.put(new String(stripLeadingZeroes(tokenId)), endowment);
1749+
}
1750+
addInternalTx(null, senderAddress, contextAddress, !isTokenTransfer ? endowment : 0,
1751+
data, "call", nonce, tokenInfo);
1752+
}
17431753
deposit.commit();
17441754
} else {
17451755
// spend all energy on failure, push zero and revert state changes

framework/src/test/java/org/tron/common/runtime/vm/OperationsTest.java

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@
44
import static org.mockito.ArgumentMatchers.any;
55
import static org.tron.core.config.Parameter.ChainConstant.FROZEN_PERIOD;
66

7+
import com.google.protobuf.ByteString;
8+
import java.util.Arrays;
79
import java.util.List;
810
import java.util.Locale;
911
import java.util.Random;
1012

1113
import lombok.SneakyThrows;
1214
import lombok.extern.slf4j.Slf4j;
15+
import org.apache.commons.lang3.tuple.Pair;
1316
import org.bouncycastle.util.encoders.Hex;
1417
import org.junit.AfterClass;
1518
import org.junit.Assert;
@@ -27,6 +30,7 @@
2730
import org.tron.core.Constant;
2831
import org.tron.core.Wallet;
2932
import org.tron.core.capsule.AccountCapsule;
33+
import org.tron.core.capsule.AssetIssueCapsule;
3034
import org.tron.core.config.args.Args;
3135
import org.tron.core.exception.ContractValidateException;
3236
import org.tron.core.store.StoreFactory;
@@ -45,6 +49,7 @@
4549
import org.tron.core.vm.program.invoke.ProgramInvokeMockImpl;
4650
import org.tron.core.vm.repository.Repository;
4751
import org.tron.protos.Protocol;
52+
import org.tron.protos.contract.AssetIssueContractOuterClass.AssetIssueContract;
4853

4954
@Slf4j
5055
public class OperationsTest extends BaseTest {
@@ -783,11 +788,217 @@ Op.CALL, new DataWord(10000),
783788
DataWord.ZERO(), DataWord.ZERO(),
784789
DataWord.ZERO(), false);
785790
program.callToPrecompiledAddress(messageCall, new PrecompiledContracts.ECRecover());
791+
Assert.assertTrue(program.getResult().getInternalTransactions().isEmpty());
786792

787793
DecodeUtil.addressPreFixByte = prePrefixByte;
788794
VMConfig.initAllowTvmSelfdestructRestriction(0);
789795
}
790796

797+
@Test
798+
public void testCallPrecompiledAddressWithValueAddsInternalTransaction()
799+
throws ContractValidateException {
800+
invoke = new ProgramInvokeMockImpl();
801+
Protocol.Transaction trx = Protocol.Transaction.getDefaultInstance();
802+
InternalTransaction interTrx =
803+
new InternalTransaction(trx, InternalTransaction.TrxType.TRX_UNKNOWN_TYPE);
804+
805+
byte prePrefixByte = DecodeUtil.addressPreFixByte;
806+
DecodeUtil.addressPreFixByte = Constant.ADD_PRE_FIX_BYTE_MAINNET;
807+
try {
808+
DataWord precompiledAddressWord = new DataWord(4);
809+
byte[] precompiledAddress = precompiledAddressWord.toTronAddress();
810+
byte[] senderAddress = invoke.getContractAddress().toTronAddress();
811+
long value = 123L;
812+
813+
invoke.getDeposit().createAccount(precompiledAddress, Protocol.AccountType.Normal);
814+
invoke.getDeposit().addBalance(senderAddress, 1000L);
815+
816+
program = new Program(new byte[0], new byte[0], invoke, interTrx);
817+
MessageCall messageCall = new MessageCall(
818+
Op.CALL, new DataWord(10000),
819+
precompiledAddressWord, new DataWord(value),
820+
DataWord.ZERO(), DataWord.ZERO(),
821+
DataWord.ZERO(), DataWord.ZERO(),
822+
DataWord.ZERO(), false);
823+
program.callToPrecompiledAddress(messageCall, new PrecompiledContracts.Identity());
824+
825+
Assert.assertEquals(1000L - value, invoke.getDeposit().getBalance(senderAddress));
826+
Assert.assertEquals(value, invoke.getDeposit().getBalance(precompiledAddress));
827+
Assert.assertEquals(1, program.getResult().getInternalTransactions().size());
828+
829+
InternalTransaction internalTx = program.getResult().getInternalTransactions().get(0);
830+
Assert.assertFalse(internalTx.isRejected());
831+
Assert.assertEquals("call", internalTx.getNote());
832+
Assert.assertEquals(value, internalTx.getValue());
833+
Assert.assertEquals(1L, internalTx.getNonce());
834+
Assert.assertArrayEquals(senderAddress, internalTx.getSender());
835+
Assert.assertArrayEquals(precompiledAddress, internalTx.getTransferToAddress());
836+
} finally {
837+
DecodeUtil.addressPreFixByte = prePrefixByte;
838+
}
839+
}
840+
841+
@Test
842+
public void testCallPrecompiledAddressWithTokenAddsInternalTransaction()
843+
throws ContractValidateException {
844+
byte prePrefixByte = DecodeUtil.addressPreFixByte;
845+
DecodeUtil.addressPreFixByte = Constant.ADD_PRE_FIX_BYTE_MAINNET;
846+
long tokenId = chainBaseManager.getDynamicPropertiesStore().getTokenIdNum() + 1;
847+
byte[] tokenIdBytes = Long.toString(tokenId).getBytes();
848+
try {
849+
chainBaseManager.getDynamicPropertiesStore().saveAllowSameTokenName(1);
850+
chainBaseManager.getDynamicPropertiesStore().saveTokenIdNum(tokenId);
851+
AssetIssueContract assetIssueContract = AssetIssueContract.newBuilder()
852+
.setOwnerAddress(ByteString.copyFrom(invokeAddress()))
853+
.setName(ByteString.copyFromUtf8("precompile-token"))
854+
.setId(Long.toString(tokenId))
855+
.build();
856+
AssetIssueCapsule assetIssueCapsule = new AssetIssueCapsule(assetIssueContract);
857+
chainBaseManager.getAssetIssueV2Store()
858+
.put(assetIssueCapsule.createDbV2Key(), assetIssueCapsule);
859+
860+
StoreFactory.init();
861+
StoreFactory storeFactory = StoreFactory.getInstance();
862+
storeFactory.setChainBaseManager(chainBaseManager);
863+
invoke = new ProgramInvokeMockImpl(storeFactory, new byte[0], invokeAddress());
864+
InternalTransaction interTrx = new InternalTransaction(
865+
Protocol.Transaction.getDefaultInstance(),
866+
InternalTransaction.TrxType.TRX_UNKNOWN_TYPE);
867+
program = new Program(new byte[0], new byte[0], invoke, interTrx);
868+
869+
DataWord precompiledAddressWord = new DataWord(4);
870+
byte[] precompiledAddress = precompiledAddressWord.toTronAddress();
871+
byte[] senderAddress = invoke.getContractAddress().toTronAddress();
872+
long value = 123L;
873+
invoke.getDeposit().createAccount(precompiledAddress, Protocol.AccountType.Normal);
874+
invoke.getDeposit().addTokenBalance(senderAddress, tokenIdBytes, 1000L);
875+
876+
MessageCall messageCall = new MessageCall(
877+
Op.CALLTOKEN, new DataWord(10000),
878+
precompiledAddressWord, new DataWord(value),
879+
DataWord.ZERO(), DataWord.ZERO(),
880+
DataWord.ZERO(), DataWord.ZERO(),
881+
new DataWord(tokenId), true);
882+
program.callToPrecompiledAddress(messageCall, new PrecompiledContracts.Identity());
883+
884+
Assert.assertEquals(1000L - value,
885+
invoke.getDeposit().getTokenBalance(senderAddress, tokenIdBytes));
886+
Assert.assertEquals(value,
887+
invoke.getDeposit().getTokenBalance(precompiledAddress, tokenIdBytes));
888+
Assert.assertEquals(1, program.getResult().getInternalTransactions().size());
889+
890+
InternalTransaction internalTx = program.getResult().getInternalTransactions().get(0);
891+
Assert.assertFalse(internalTx.isRejected());
892+
Assert.assertEquals("call", internalTx.getNote());
893+
Assert.assertEquals(0L, internalTx.getValue());
894+
Assert.assertEquals(1L, internalTx.getNonce());
895+
Assert.assertEquals(value,
896+
internalTx.getTokenInfo().get(Long.toString(tokenId)).longValue());
897+
Assert.assertArrayEquals(senderAddress, internalTx.getSender());
898+
Assert.assertArrayEquals(precompiledAddress, internalTx.getTransferToAddress());
899+
} finally {
900+
DecodeUtil.addressPreFixByte = prePrefixByte;
901+
}
902+
}
903+
904+
@Test
905+
public void testFailedPrecompiledAddressCallDoesNotAddInternalTransaction()
906+
throws ContractValidateException {
907+
invoke = new ProgramInvokeMockImpl();
908+
InternalTransaction interTrx = new InternalTransaction(
909+
Protocol.Transaction.getDefaultInstance(),
910+
InternalTransaction.TrxType.TRX_UNKNOWN_TYPE);
911+
program = new Program(new byte[0], new byte[0], invoke, interTrx);
912+
913+
byte prePrefixByte = DecodeUtil.addressPreFixByte;
914+
DecodeUtil.addressPreFixByte = Constant.ADD_PRE_FIX_BYTE_MAINNET;
915+
try {
916+
DataWord precompiledAddressWord = new DataWord(4);
917+
byte[] precompiledAddress = precompiledAddressWord.toTronAddress();
918+
byte[] senderAddress = invoke.getContractAddress().toTronAddress();
919+
long value = 123L;
920+
invoke.getDeposit().createAccount(precompiledAddress, Protocol.AccountType.Normal);
921+
invoke.getDeposit().addBalance(senderAddress, 1000L);
922+
923+
PrecompiledContracts.PrecompiledContract failedContract =
924+
new PrecompiledContracts.PrecompiledContract() {
925+
@Override
926+
public long getEnergyForData(byte[] data) {
927+
return 0;
928+
}
929+
930+
@Override
931+
public Pair<Boolean, byte[]> execute(byte[] data) {
932+
return Pair.of(false, new byte[0]);
933+
}
934+
};
935+
MessageCall messageCall = new MessageCall(
936+
Op.CALL, new DataWord(10000),
937+
precompiledAddressWord, new DataWord(value),
938+
DataWord.ZERO(), DataWord.ZERO(),
939+
DataWord.ZERO(), DataWord.ZERO(),
940+
DataWord.ZERO(), false);
941+
program.callToPrecompiledAddress(messageCall, failedContract);
942+
943+
Assert.assertEquals(1000L, invoke.getDeposit().getBalance(senderAddress));
944+
Assert.assertEquals(0L, invoke.getDeposit().getBalance(precompiledAddress));
945+
Assert.assertTrue(program.getResult().getInternalTransactions().isEmpty());
946+
} finally {
947+
DecodeUtil.addressPreFixByte = prePrefixByte;
948+
}
949+
}
950+
951+
@Test
952+
public void testPrecompiledAddressTransferChangesFollowingInternalTransactionHash()
953+
throws ContractValidateException {
954+
invoke = new ProgramInvokeMockImpl();
955+
InternalTransaction interTrx = new InternalTransaction(
956+
Protocol.Transaction.getDefaultInstance(),
957+
InternalTransaction.TrxType.TRX_UNKNOWN_TYPE);
958+
program = new Program(new byte[0], new byte[0], invoke, interTrx);
959+
960+
byte prePrefixByte = DecodeUtil.addressPreFixByte;
961+
DecodeUtil.addressPreFixByte = Constant.ADD_PRE_FIX_BYTE_MAINNET;
962+
try {
963+
DataWord precompiledAddressWord = new DataWord(4);
964+
byte[] precompiledAddress = precompiledAddressWord.toTronAddress();
965+
byte[] senderAddress = invoke.getContractAddress().toTronAddress();
966+
invoke.getDeposit().createAccount(precompiledAddress, Protocol.AccountType.Normal);
967+
invoke.getDeposit().addBalance(senderAddress, 1000L);
968+
969+
MessageCall messageCall = new MessageCall(
970+
Op.CALL, new DataWord(10000),
971+
precompiledAddressWord, DataWord.ONE(),
972+
DataWord.ZERO(), DataWord.ZERO(),
973+
DataWord.ZERO(), DataWord.ZERO(),
974+
DataWord.ZERO(), false);
975+
program.callToPrecompiledAddress(messageCall, new PrecompiledContracts.Identity());
976+
977+
byte[] followingAddress = new DataWord(5).toTronAddress();
978+
byte[] followingData = {1, 2, 3};
979+
long followingValue = 7L;
980+
InternalTransaction legacyFollowingTx = new InternalTransaction(
981+
interTrx.getHash(), program.getCallDeep(), 0, senderAddress, followingAddress,
982+
followingValue, followingData, "call", 1L, null);
983+
984+
program.increaseNonce();
985+
InternalTransaction upgradedFollowingTx = program.getResult().addInternalTransaction(
986+
interTrx.getHash(), program.getCallDeep(), senderAddress, followingAddress,
987+
followingValue, followingData, "call", program.getNonce(), null);
988+
989+
Assert.assertEquals(1L, legacyFollowingTx.getNonce());
990+
Assert.assertEquals(2L, upgradedFollowingTx.getNonce());
991+
Assert.assertFalse(Arrays.equals(
992+
legacyFollowingTx.getHash(), upgradedFollowingTx.getHash()));
993+
} finally {
994+
DecodeUtil.addressPreFixByte = prePrefixByte;
995+
}
996+
}
997+
998+
private byte[] invokeAddress() {
999+
return Hex.decode("41471fd3ad3e9eeadeec4608b92d16ce6b500704cc");
1000+
}
1001+
7911002
// TIP-854 outer-frame containment: a CALL to validateMultiSign or
7921003
// batchValidateSign with malformed calldata must (a) push 0 onto the outer
7931004
// stack, (b) leave the outer frame free of any propagated exception, and

0 commit comments

Comments
 (0)