diff --git a/framework/src/main/java/org/tron/core/services/jsonrpc/JsonRpcApiUtil.java b/framework/src/main/java/org/tron/core/services/jsonrpc/JsonRpcApiUtil.java index 08c1068e3a..70673d2148 100644 --- a/framework/src/main/java/org/tron/core/services/jsonrpc/JsonRpcApiUtil.java +++ b/framework/src/main/java/org/tron/core/services/jsonrpc/JsonRpcApiUtil.java @@ -442,6 +442,50 @@ public static boolean paramQuantityIsNull(String quantity) { return StringUtils.isEmpty(quantity) || quantity.equals("0x0"); } + /** + * Validation mode for {@link #requireValidHex}. + */ + public enum HexMode { + /** + * Execution-apis BYTES schema: requires {@code 0x} prefix and + * even total length; {@code ""} is accepted as empty bytes per + * geth's {@code hexutil.Bytes.UnmarshalText}. + */ + STRICT, + /** + * {@link ByteArray#fromHexString}'s lenient parsing: accepts bare + * hex and odd-length input. Kept for backward compatibility. + */ + LENIENT + } + + /** + * Throws if {@code value} is not parseable hex under the given + * {@code mode}. {@code null} is treated as absent and returns + * silently. {@code fieldName} is used only in error messages. + */ + public static void requireValidHex(String fieldName, String value, HexMode mode) + throws JsonRpcInvalidParamsException { + if (value == null) { + return; + } + if (mode == HexMode.STRICT) { + if (value.isEmpty()) { + return; + } + if (!value.startsWith("0x") || value.length() % 2 != 0) { + throw new JsonRpcInvalidParamsException( + "invalid hex string for \"" + fieldName + "\""); + } + } + try { + ByteArray.fromHexString(value); + } catch (Exception e) { + throw new JsonRpcInvalidParamsException( + "invalid hex string for \"" + fieldName + "\""); + } + } + public static long parseQuantityValue(String value) throws JsonRpcInvalidParamsException { long callValue = 0L; diff --git a/framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpcImpl.java b/framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpcImpl.java index 72fc579aa5..ea8f15cd08 100644 --- a/framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpcImpl.java +++ b/framework/src/main/java/org/tron/core/services/jsonrpc/TronJsonRpcImpl.java @@ -645,7 +645,7 @@ public String estimateGas(CallArguments args) throws JsonRpcInvalidRequestExcept estimateEnergy(ownerAddress, contractAddress, args.parseValue(), - ByteArray.fromHexString(args.getData()), + ByteArray.fromHexString(args.resolveData()), trxExtBuilder, retBuilder, estimateBuilder); @@ -653,7 +653,7 @@ public String estimateGas(CallArguments args) throws JsonRpcInvalidRequestExcept callTriggerConstantContract(ownerAddress, contractAddress, args.parseValue(), - ByteArray.fromHexString(args.getData()), + ByteArray.fromHexString(args.resolveData()), trxExtBuilder, retBuilder); } @@ -1007,7 +1007,7 @@ public String getCall(CallArguments transactionCall, Object blockParamObj) byte[] contractAddressData = addressCompatibleToByteArray(transactionCall.getTo()); return call(addressData, contractAddressData, transactionCall.parseValue(), - ByteArray.fromHexString(transactionCall.getData())); + ByteArray.fromHexString(transactionCall.resolveData())); } @Override @@ -1114,7 +1114,8 @@ private TransactionJson buildCreateSmartContractTransaction(byte[] ownerAddress, smartBuilder.setOriginAddress(ByteString.copyFrom(ownerAddress)); // bytecode + parameter - smartBuilder.setBytecode(ByteString.copyFrom(ByteArray.fromHexString(args.getData()))); + smartBuilder.setBytecode( + ByteString.copyFrom(ByteArray.fromHexString(args.resolveData()))); if (StringUtils.isNotEmpty(args.getName())) { smartBuilder.setName(args.getName()); @@ -1159,8 +1160,9 @@ private TransactionJson buildTriggerSmartContractTransaction(byte[] ownerAddress build.setOwnerAddress(ByteString.copyFrom(ownerAddress)) .setContractAddress(ByteString.copyFrom(contractAddress)); - if (StringUtils.isNotEmpty(args.getData())) { - build.setData(ByteString.copyFrom(ByteArray.fromHexString(args.getData()))); + String callData = args.resolveData(); + if (StringUtils.isNotEmpty(callData)) { + build.setData(ByteString.copyFrom(ByteArray.fromHexString(callData))); } else { build.setData(ByteString.copyFrom(new byte[0])); } diff --git a/framework/src/main/java/org/tron/core/services/jsonrpc/types/BuildArguments.java b/framework/src/main/java/org/tron/core/services/jsonrpc/types/BuildArguments.java index 490219a13d..ef4e958ae4 100644 --- a/framework/src/main/java/org/tron/core/services/jsonrpc/types/BuildArguments.java +++ b/framework/src/main/java/org/tron/core/services/jsonrpc/types/BuildArguments.java @@ -4,8 +4,10 @@ import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.paramQuantityIsNull; import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.paramStringIsNull; import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.parseQuantityValue; +import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.requireValidHex; import com.google.protobuf.ByteString; +import java.util.Arrays; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @@ -13,9 +15,11 @@ import lombok.ToString; import org.apache.commons.lang3.StringUtils; import org.tron.api.GrpcAPI.BytesMessage; +import org.tron.common.utils.ByteArray; import org.tron.core.Wallet; import org.tron.core.exception.jsonrpc.JsonRpcInvalidParamsException; import org.tron.core.exception.jsonrpc.JsonRpcInvalidRequestException; +import org.tron.core.services.jsonrpc.JsonRpcApiUtil.HexMode; import org.tron.protos.Protocol.Transaction.Contract.ContractType; import org.tron.protos.contract.SmartContractOuterClass.SmartContract; @@ -24,6 +28,16 @@ @ToString public class BuildArguments { + /** + * Conflict error message wording. Mirrors go-ethereum's + * {@code setDefaults} verbatim — external EVM tooling may + * pattern-match this string. Do not change the wording without + * coordinating with downstream consumers. + */ + private static final String CONFLICT_ERR_MSG = + "both \"data\" and \"input\" are set and not equal. " + + "Please use \"input\" to pass transaction call data"; + @Getter @Setter private String from; @@ -44,6 +58,9 @@ public class BuildArguments { private String data; @Getter @Setter + private String input; + @Getter + @Setter private String nonce = ""; //not used @Getter @@ -83,16 +100,50 @@ public BuildArguments(CallArguments args) { gasPrice = args.getGasPrice(); value = args.getValue(); data = args.getData(); + input = args.getInput(); + } + + /** + * Returns {@code input} if non-null, else {@code data}. Pure + * precedence resolution, mirroring go-ethereum's + * {@code TransactionArgs.data()}. + * + *

Both fields are first validated by + * {@link org.tron.core.services.jsonrpc.JsonRpcApiUtil#requireValidHex} + * — strict for {@code input}, lenient for {@code data} (see that + * method for the rules). + * + *

Conflict between {@code input} and {@code data} is not checked + * here. Build-path callers must route through + * {@link #getContractType(Wallet)} for the geth-equivalent + * {@code setDefaults} enforcement. + * + *

Java callers using positional constructors should pass + * {@code null} (not {@code ""}) for unset {@code input}. + * + *

Verb-prefix name (not {@code getXxx}) keeps Jackson and + * FastJSON's JavaBean introspection from invoking it during + * serialisation; two regression tests per DTO pin this invariant. + */ + public String resolveData() throws JsonRpcInvalidParamsException { + requireValidHex("input", input, HexMode.STRICT); + requireValidHex("data", data, HexMode.LENIENT); + return input != null ? input : data; } public ContractType getContractType(Wallet wallet) throws JsonRpcInvalidRequestException, JsonRpcInvalidParamsException { + // Fail fast on bad hex / conflict before the state lookup; + // calldataEquals relies on resolveData() having validated hex first. + String resolvedData = resolveData(); + validateCallDataConflict(); + ContractType contractType; // to is null if (paramStringIsNull(to)) { // data is null - if (paramStringIsNull(data)) { + if (paramStringIsNull(resolvedData)) { throw new JsonRpcInvalidRequestException("invalid json request"); } @@ -136,4 +187,22 @@ private boolean availableTransferAsset() { return tokenId > 0 && tokenValue > 0 && paramQuantityIsNull(value); } + /** + * Throws when both fields decode to non-equal bytes. Wording matches + * geth's setDefaults so existing tooling can detect the error string. + */ + private void validateCallDataConflict() throws JsonRpcInvalidParamsException { + if (input != null && data != null && !calldataEquals(input, data)) { + throw new JsonRpcInvalidParamsException(CONFLICT_ERR_MSG); + } + } + + /** + * Byte-level equality, so {@code "0xDEAD"} equals {@code "0xdead"}. Both + * args must have passed {@code requireValidHex} first. + */ + private static boolean calldataEquals(String a, String b) { + return Arrays.equals(ByteArray.fromHexString(a), ByteArray.fromHexString(b)); + } + } \ No newline at end of file diff --git a/framework/src/main/java/org/tron/core/services/jsonrpc/types/CallArguments.java b/framework/src/main/java/org/tron/core/services/jsonrpc/types/CallArguments.java index 70edd1ad94..1715636a2a 100644 --- a/framework/src/main/java/org/tron/core/services/jsonrpc/types/CallArguments.java +++ b/framework/src/main/java/org/tron/core/services/jsonrpc/types/CallArguments.java @@ -3,6 +3,7 @@ import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.addressCompatibleToByteArray; import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.paramStringIsNull; import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.parseQuantityValue; +import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.requireValidHex; import com.google.protobuf.ByteString; import lombok.AllArgsConstructor; @@ -15,6 +16,7 @@ import org.tron.core.Wallet; import org.tron.core.exception.jsonrpc.JsonRpcInvalidParamsException; import org.tron.core.exception.jsonrpc.JsonRpcInvalidRequestException; +import org.tron.core.services.jsonrpc.JsonRpcApiUtil.HexMode; import org.tron.protos.Protocol.Transaction.Contract.ContractType; import org.tron.protos.contract.SmartContractOuterClass.SmartContract; @@ -43,21 +45,41 @@ public class CallArguments { private String data; @Getter @Setter + private String input; + @Getter + @Setter private String nonce; // not used + /** + * Returns {@code input} if non-null, else {@code data}. Pure + * precedence resolution, mirroring go-ethereum's + * {@code TransactionArgs.data()}; no conflict check on the query + * path (matches geth's {@code ToMessage}). See + * {@link BuildArguments#resolveData()} for the rationale on + * naming, validation split, and serialiser interaction. + */ + public String resolveData() throws JsonRpcInvalidParamsException { + requireValidHex("input", input, HexMode.STRICT); + requireValidHex("data", data, HexMode.LENIENT); + return input != null ? input : data; + } + /** * just support TransferContract, CreateSmartContract and TriggerSmartContract * */ public ContractType getContractType(Wallet wallet) throws JsonRpcInvalidRequestException, JsonRpcInvalidParamsException { - ContractType contractType; - // from or to is null if (paramStringIsNull(from)) { throw new JsonRpcInvalidRequestException("invalid json request"); - } else if (paramStringIsNull(to)) { + } + // Fail fast on bad hex before the state lookup. + String resolvedData = resolveData(); + + ContractType contractType; + if (paramStringIsNull(to)) { // data is null - if (paramStringIsNull(data)) { + if (paramStringIsNull(resolvedData)) { throw new JsonRpcInvalidRequestException("invalid json request"); } diff --git a/framework/src/test/java/org/tron/core/services/jsonrpc/BuildArgumentsTest.java b/framework/src/test/java/org/tron/core/services/jsonrpc/BuildArgumentsTest.java index 26699bc63f..5ad2c85d18 100644 --- a/framework/src/test/java/org/tron/core/services/jsonrpc/BuildArgumentsTest.java +++ b/framework/src/test/java/org/tron/core/services/jsonrpc/BuildArgumentsTest.java @@ -1,5 +1,6 @@ package org.tron.core.services.jsonrpc; +import com.fasterxml.jackson.databind.ObjectMapper; import javax.annotation.Resource; import org.junit.Assert; import org.junit.Before; @@ -29,9 +30,9 @@ public class BuildArgumentsTest extends BaseTest { public void initBuildArgs() { buildArguments = new BuildArguments( "0x0000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000001","0x10","0.01","0x100", - "","0",9L,10000L,"",10L, - 2000L,"args",1,"",true); + "0x0000000000000000000000000000000000000001", "0x10", "0.01", "0x100", + "", "", "0", 9L, 10000L, "", 10L, + 2000L, "args", 1, "", true); } @@ -39,15 +40,13 @@ public void initBuildArgs() { public void testBuildArgument() { CallArguments callArguments = new CallArguments( "0x0000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000001","0x10","0.01","0x100", - "","0"); - BuildArguments buildArguments = new BuildArguments(callArguments); - Assert.assertEquals(buildArguments.getFrom(), - "0x0000000000000000000000000000000000000000"); - Assert.assertEquals(buildArguments.getTo(), - "0x0000000000000000000000000000000000000001"); - Assert.assertEquals(buildArguments.getGas(), "0x10"); - Assert.assertEquals(buildArguments.getGasPrice(), "0.01"); + "0x0000000000000000000000000000000000000001", "0x10", "0.01", "0x100", + "", "", "0"); + BuildArguments args = new BuildArguments(callArguments); + Assert.assertEquals("0x0000000000000000000000000000000000000000", args.getFrom()); + Assert.assertEquals("0x0000000000000000000000000000000000000001", args.getTo()); + Assert.assertEquals("0x10", args.getGas()); + Assert.assertEquals("0.01", args.getGasPrice()); } @Test @@ -55,19 +54,275 @@ public void testGetContractType() throws JsonRpcInvalidRequestException, JsonRpcInvalidParamsException { Protocol.Transaction.Contract.ContractType contractType = buildArguments.getContractType(wallet); - Assert.assertEquals(contractType, Protocol.Transaction.Contract.ContractType.TransferContract); + Assert.assertEquals(Protocol.Transaction.Contract.ContractType.TransferContract, contractType); } @Test public void testParseValue() throws JsonRpcInvalidParamsException { long value = buildArguments.parseValue(); - Assert.assertEquals(value, 256L); + Assert.assertEquals(256L, value); } @Test public void testParseGas() throws JsonRpcInvalidParamsException { long gas = buildArguments.parseGas(); - Assert.assertEquals(gas, 16L); + Assert.assertEquals(16L, gas); + } + + @Test + public void resolveData_inputOnly_returnsInput() throws JsonRpcInvalidParamsException { + BuildArguments args = new BuildArguments(); + args.setInput("0xdeadbeef"); + Assert.assertEquals("0xdeadbeef", args.resolveData()); + } + + @Test + public void resolveData_dataOnly_returnsData() throws JsonRpcInvalidParamsException { + BuildArguments args = new BuildArguments(); + args.setData("0xcafebabe"); + Assert.assertEquals("0xcafebabe", args.resolveData()); + } + + @Test + public void resolveData_bothPresentSame_returnsValue() throws JsonRpcInvalidParamsException { + BuildArguments args = new BuildArguments(); + args.setData("0xfeedface"); + args.setInput("0xfeedface"); + Assert.assertEquals("0xfeedface", args.resolveData()); + } + + /** Pins that "0x" on both sides decodes to []==[] and is not a conflict. */ + @Test + public void resolveData_bothZeroX_returnsZeroX() throws JsonRpcInvalidParamsException { + BuildArguments args = new BuildArguments(); + args.setInput("0x"); + args.setData("0x"); + Assert.assertEquals("0x", args.resolveData()); + } + + @Test + public void resolveData_inputZeroXOnly_returnsZeroX() throws JsonRpcInvalidParamsException { + BuildArguments args = new BuildArguments(); + args.setInput("0x"); + Assert.assertEquals("0x", args.resolveData()); + } + + @Test + public void resolveData_dataZeroXOnly_returnsZeroX() throws JsonRpcInvalidParamsException { + BuildArguments args = new BuildArguments(); + args.setData("0x"); + Assert.assertEquals("0x", args.resolveData()); + } + + @Test + public void resolveData_caseDifference_returnsInput() + throws JsonRpcInvalidParamsException { + BuildArguments args = new BuildArguments(); + args.setInput("0xDEADbeef"); + args.setData("0xdeadbeef"); + Assert.assertEquals("0xDEADbeef", args.resolveData()); + } + + /** + * Pins geth-equivalent semantics: empty string is presence with + * empty bytes, so paired with non-empty data the byte values differ + * and the build path raises the geth setDefaults conflict at the + * {@code getContractType()} entry point. + */ + @Test + public void getContractType_inputEmptyDataNonEmpty_throwsConflict() { + BuildArguments args = new BuildArguments(); + args.setFrom("0x0000000000000000000000000000000000000001"); + args.setInput(""); + args.setData("0xdeadbeef"); + Assert.assertThrows(JsonRpcInvalidParamsException.class, + () -> args.getContractType(wallet)); + } + + /** + * Wording matches go-ethereum's setDefaults so existing tooling can + * detect the error string. + */ + @Test + public void getContractType_inputAndDataConflict_throwsInvalidParams() { + BuildArguments args = new BuildArguments(); + args.setFrom("0x0000000000000000000000000000000000000001"); + args.setInput("0xdeadbeef"); + args.setData("0xcafebabe"); + + JsonRpcInvalidParamsException ex = Assert.assertThrows( + JsonRpcInvalidParamsException.class, + () -> args.getContractType(wallet)); + Assert.assertTrue( + "error message should match go-ethereum's wording: " + ex.getMessage(), + ex.getMessage().contains("both \"data\" and \"input\" are set and not equal")); + } + + @Test + public void getContractType_inputZeroXDataNonEmpty_throwsConflict() { + BuildArguments args = new BuildArguments(); + args.setFrom("0x0000000000000000000000000000000000000001"); + args.setInput("0x"); + args.setData("0xdeadbeef"); + + Assert.assertThrows( + JsonRpcInvalidParamsException.class, + () -> args.getContractType(wallet)); + } + + @Test + public void getContractType_inputAndDataEqual_succeeds() + throws JsonRpcInvalidRequestException, JsonRpcInvalidParamsException { + BuildArguments args = new BuildArguments(); + args.setFrom("0x0000000000000000000000000000000000000001"); + args.setInput("0xdeadbeef"); + args.setData("0xdeadbeef"); + Assert.assertEquals( + Protocol.Transaction.Contract.ContractType.CreateSmartContract, + args.getContractType(wallet)); + } + + /** Reproduces issue #6517 contract-creation symptom on the build path. */ + @Test + public void getContractType_createSmartContractViaInput_succeeds() + throws JsonRpcInvalidRequestException, JsonRpcInvalidParamsException { + BuildArguments args = new BuildArguments(); + args.setFrom("0x0000000000000000000000000000000000000001"); + args.setInput("0xdeadbeef"); + Assert.assertEquals( + Protocol.Transaction.Contract.ContractType.CreateSmartContract, + args.getContractType(wallet)); + } + + @Test + public void copyConstructor_preservesBothInputAndData() { + CallArguments src = new CallArguments(); + src.setFrom("0x0000000000000000000000000000000000000001"); + src.setData("0xcafebabe"); + src.setInput("0xdeadbeef"); + + BuildArguments copy = new BuildArguments(src); + Assert.assertEquals("0xcafebabe", copy.getData()); + Assert.assertEquals("0xdeadbeef", copy.getInput()); + } + + @Test + public void copyConstructor_propagatesConflictToBuildPath() { + CallArguments src = new CallArguments(); + src.setData("0xcafebabe"); + src.setInput("0xdeadbeef"); + + BuildArguments copy = new BuildArguments(src); + Assert.assertThrows(JsonRpcInvalidParamsException.class, + () -> copy.getContractType(wallet)); + } + + @Test + public void deserializeWithInputField_succeedsAndResolvesToInput() throws Exception { + String json = "{\"from\":\"0x0000000000000000000000000000000000000001\"," + + "\"to\":\"0x0000000000000000000000000000000000000002\"," + + "\"input\":\"0xdeadbeef\"}"; + BuildArguments args = new ObjectMapper().readValue(json, BuildArguments.class); + Assert.assertEquals("0xdeadbeef", args.resolveData()); + Assert.assertEquals("0xdeadbeef", args.getInput()); + Assert.assertNull(args.getData()); + } + + /** + * Regression guard: a future {@code getXxx} rename would expose + * {@code resolveData} as a wire property and risk throwing during + * serialisation. + */ + @Test + public void jacksonSerialize_doesNotExposeResolveDataOrThrowOnConflict() + throws Exception { + BuildArguments args = new BuildArguments(); + args.setInput("0xdeadbeef"); + args.setData("0xcafebabe"); // conflicting bytes, would throw if resolveData() were invoked + String json = new ObjectMapper().writeValueAsString(args); + Assert.assertFalse("should not leak resolveData: " + json, + json.contains("resolveData")); + } + + /** Same guarantee for FastJSON, which also discovers bean getters. */ + @Test + public void fastjsonSerialize_doesNotExposeResolveDataOrThrowOnConflict() { + BuildArguments args = new BuildArguments(); + args.setInput("0xdeadbeef"); + args.setData("0xcafebabe"); + String json = com.alibaba.fastjson.JSON.toJSONString(args); + Assert.assertFalse("should not leak resolveData: " + json, + json.contains("resolveData")); + } + + /** Validates the loser field too, not only the precedence winner. */ + @Test + public void resolveData_inputValidDataMalformed_throwsInvalidParams() { + BuildArguments args = new BuildArguments(); + args.setInput("0xdeadbeef"); + args.setData("0xzz"); + Assert.assertThrows(JsonRpcInvalidParamsException.class, args::resolveData); + } + + @Test + public void resolveData_inputMalformedDataValid_throwsInvalidParams() { + BuildArguments args = new BuildArguments(); + args.setInput("0xzz"); + args.setData("0xdeadbeef"); + Assert.assertThrows(JsonRpcInvalidParamsException.class, args::resolveData); + } + + @Test + public void resolveData_inputMalformedDataAbsent_throwsInvalidParams() { + BuildArguments args = new BuildArguments(); + args.setInput("0xzz"); + Assert.assertThrows(JsonRpcInvalidParamsException.class, args::resolveData); + } + + @Test + public void resolveData_dataMalformedInputAbsent_throwsInvalidParams() { + BuildArguments args = new BuildArguments(); + args.setData("0xzz"); + Assert.assertThrows(JsonRpcInvalidParamsException.class, args::resolveData); + } + + /** + * {@code input} is the new spec-aligned field: missing {@code 0x} prefix + * is rejected per the execution-apis BYTES schema. + */ + @Test + public void resolveData_inputNoPrefix_throwsInvalidParams() { + BuildArguments args = new BuildArguments(); + args.setInput("deadbeef"); + Assert.assertThrows(JsonRpcInvalidParamsException.class, args::resolveData); + } + + @Test + public void resolveData_inputOddLength_throwsInvalidParams() { + BuildArguments args = new BuildArguments(); + args.setInput("0x123"); + Assert.assertThrows(JsonRpcInvalidParamsException.class, args::resolveData); + } + + /** + * {@code data} is the legacy field: bare hex (no {@code 0x} prefix) + * stays accepted for backward compatibility with existing callers + * (e.g. BuildTransactionTest.testCreateSmartContract). + */ + @Test + public void resolveData_dataNoPrefix_acceptedForBackwardCompat() + throws JsonRpcInvalidParamsException { + BuildArguments args = new BuildArguments(); + args.setData("deadbeef"); + Assert.assertEquals("deadbeef", args.resolveData()); + } + + @Test + public void resolveData_dataOddLength_acceptedForBackwardCompat() + throws JsonRpcInvalidParamsException { + BuildArguments args = new BuildArguments(); + args.setData("0x123"); + Assert.assertEquals("0x123", args.resolveData()); } } diff --git a/framework/src/test/java/org/tron/core/services/jsonrpc/CallArgumentsTest.java b/framework/src/test/java/org/tron/core/services/jsonrpc/CallArgumentsTest.java index 2148e1a2fe..66fb8e0a0c 100644 --- a/framework/src/test/java/org/tron/core/services/jsonrpc/CallArgumentsTest.java +++ b/framework/src/test/java/org/tron/core/services/jsonrpc/CallArgumentsTest.java @@ -1,5 +1,6 @@ package org.tron.core.services.jsonrpc; +import com.fasterxml.jackson.databind.ObjectMapper; import javax.annotation.Resource; import org.junit.Assert; import org.junit.Before; @@ -26,9 +27,11 @@ public class CallArgumentsTest extends BaseTest { @Before public void init() { - callArguments = new CallArguments("0x0000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000001","0x10","0.01","0x100", - "","0"); + callArguments = new CallArguments( + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000001", + "0x10", "0.01", "0x100", + "", "", "0"); } @Test @@ -44,4 +47,200 @@ public void testParseValue() throws JsonRpcInvalidParamsException { Assert.assertEquals(256L, value); } + @Test + public void resolveData_inputOnly_returnsInput() throws JsonRpcInvalidParamsException { + CallArguments args = new CallArguments(); + args.setInput("0xdeadbeef"); + Assert.assertEquals("0xdeadbeef", args.resolveData()); + } + + @Test + public void resolveData_dataOnly_returnsData() throws JsonRpcInvalidParamsException { + CallArguments args = new CallArguments(); + args.setData("0xcafebabe"); + Assert.assertEquals("0xcafebabe", args.resolveData()); + } + + @Test + public void resolveData_bothPresentSame_returnsValue() throws JsonRpcInvalidParamsException { + CallArguments args = new CallArguments(); + args.setData("0xfeedface"); + args.setInput("0xfeedface"); + Assert.assertEquals("0xfeedface", args.resolveData()); + } + + @Test + public void resolveData_bothPresentDifferent_inputWinsNoError() + throws JsonRpcInvalidParamsException { + CallArguments args = new CallArguments(); + args.setData("0xcafebabe"); + args.setInput("0xdeadbeef"); + Assert.assertEquals("0xdeadbeef", args.resolveData()); + } + + @Test + public void resolveData_inputIsZeroX_dataNonEmpty_returnsInput() + throws JsonRpcInvalidParamsException { + CallArguments args = new CallArguments(); + args.setInput("0x"); + args.setData("0xdeadbeef"); + Assert.assertEquals("0x", args.resolveData()); + } + + @Test + public void resolveData_dataIsZeroX_inputNonEmpty_returnsInput() + throws JsonRpcInvalidParamsException { + CallArguments args = new CallArguments(); + args.setInput("0xdeadbeef"); + args.setData("0x"); + Assert.assertEquals("0xdeadbeef", args.resolveData()); + } + + @Test + public void resolveData_caseDifference_returnsInput() + throws JsonRpcInvalidParamsException { + CallArguments args = new CallArguments(); + args.setInput("0xDEADbeef"); + args.setData("0xdeadbeef"); + Assert.assertEquals("0xDEADbeef", args.resolveData()); + } + + /** Pins geth-equivalent semantics: "" is presence, wins over data by precedence. */ + @Test + public void resolveData_inputEmpty_dataNonEmpty_inputWinsAsEmpty() + throws JsonRpcInvalidParamsException { + CallArguments args = new CallArguments(); + args.setInput(""); + args.setData("0xdeadbeef"); + Assert.assertEquals("", args.resolveData()); + } + + @Test + public void resolveData_neitherPresent_returnsNull() + throws JsonRpcInvalidParamsException { + CallArguments args = new CallArguments(); + Assert.assertNull(args.resolveData()); + } + + /** Validates the loser field too, not only the precedence winner. */ + @Test + public void resolveData_inputValidDataMalformed_throwsInvalidParams() { + CallArguments args = new CallArguments(); + args.setInput("0xdeadbeef"); + args.setData("0xzz"); + Assert.assertThrows(JsonRpcInvalidParamsException.class, args::resolveData); + } + + @Test + public void resolveData_inputMalformedDataValid_throwsInvalidParams() { + CallArguments args = new CallArguments(); + args.setInput("0xzz"); + args.setData("0xdeadbeef"); + Assert.assertThrows(JsonRpcInvalidParamsException.class, args::resolveData); + } + + @Test + public void resolveData_inputMalformedDataAbsent_throwsInvalidParams() { + CallArguments args = new CallArguments(); + args.setInput("0xzz"); + Assert.assertThrows(JsonRpcInvalidParamsException.class, args::resolveData); + } + + @Test + public void resolveData_dataMalformedInputAbsent_throwsInvalidParams() { + CallArguments args = new CallArguments(); + args.setData("0xzz"); + Assert.assertThrows(JsonRpcInvalidParamsException.class, args::resolveData); + } + + /** + * {@code input} is the new spec-aligned field: missing {@code 0x} prefix + * is rejected per the execution-apis BYTES schema. + */ + @Test + public void resolveData_inputNoPrefix_throwsInvalidParams() { + CallArguments args = new CallArguments(); + args.setInput("deadbeef"); + Assert.assertThrows(JsonRpcInvalidParamsException.class, args::resolveData); + } + + @Test + public void resolveData_inputOddLength_throwsInvalidParams() { + CallArguments args = new CallArguments(); + args.setInput("0x123"); + Assert.assertThrows(JsonRpcInvalidParamsException.class, args::resolveData); + } + + /** + * {@code data} is the legacy field: bare hex (no {@code 0x} prefix) + * stays accepted for backward compatibility with existing callers + * (e.g. BuildTransactionTest.testCreateSmartContract). + */ + @Test + public void resolveData_dataNoPrefix_acceptedForBackwardCompat() + throws JsonRpcInvalidParamsException { + CallArguments args = new CallArguments(); + args.setData("deadbeef"); + Assert.assertEquals("deadbeef", args.resolveData()); + } + + @Test + public void resolveData_dataOddLength_acceptedForBackwardCompat() + throws JsonRpcInvalidParamsException { + CallArguments args = new CallArguments(); + args.setData("0x123"); + Assert.assertEquals("0x123", args.resolveData()); + } + + /** Reproduces issue #6517 contract-creation symptom. */ + @Test + public void getContractType_createSmartContractViaInput_succeeds() + throws JsonRpcInvalidRequestException, JsonRpcInvalidParamsException { + CallArguments args = new CallArguments(); + args.setFrom("0x0000000000000000000000000000000000000001"); + args.setInput("0xdeadbeef"); + Assert.assertEquals( + Protocol.Transaction.Contract.ContractType.CreateSmartContract, + args.getContractType(wallet)); + } + + /** Reproduces issue #6517 Jackson parse-error symptom. */ + @Test + public void deserializeWithInputField_succeedsAndResolvesToInput() throws Exception { + String json = "{\"from\":\"0x0000000000000000000000000000000000000001\"," + + "\"to\":\"0x0000000000000000000000000000000000000002\"," + + "\"input\":\"0xdeadbeef\"}"; + CallArguments args = new ObjectMapper().readValue(json, CallArguments.class); + Assert.assertEquals("0xdeadbeef", args.resolveData()); + Assert.assertEquals("0xdeadbeef", args.getInput()); + Assert.assertNull(args.getData()); + } + + /** + * Regression guard: a future {@code getXxx} rename would expose + * {@code resolveData} as a wire property and risk throwing during + * serialisation. + */ + @Test + public void jacksonSerialize_doesNotExposeResolveDataOrThrowOnConflict() + throws Exception { + CallArguments args = new CallArguments(); + args.setInput("0xdeadbeef"); + args.setData("0xcafebabe"); // would throw conflict in build path + String json = new ObjectMapper().writeValueAsString(args); + Assert.assertFalse("should not leak resolveData: " + json, + json.contains("resolveData")); + } + + /** Same guarantee for FastJSON, which also discovers bean getters. */ + @Test + public void fastjsonSerialize_doesNotExposeResolveDataOrThrowOnConflict() { + CallArguments args = new CallArguments(); + args.setInput("0xdeadbeef"); + args.setData("0xcafebabe"); + String json = com.alibaba.fastjson.JSON.toJSONString(args); + Assert.assertFalse("should not leak resolveData: " + json, + json.contains("resolveData")); + } + }