Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package org.tron.common.logsfilter;

import static org.tron.common.math.Maths.min;

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.util.regex.Pattern;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
Expand Down Expand Up @@ -38,9 +37,14 @@ public static String parseDataBytes(byte[] data, String typeStr, int index) {
byte[] lengthBytes = subBytes(data, start, DATAWORD_UNIT_SIZE);
// this length is byte count. no need X 32
int length = intValueExact(lengthBytes);
if (length < 0) {
throw new OutputLengthException("data length:" + length);
}
byte[] realBytes =
length > 0 ? subBytes(data, start + DATAWORD_UNIT_SIZE, length) : new byte[0];
return type == Type.STRING ? new String(realBytes) : Hex.toHexString(realBytes);
return type == Type.STRING
? new String(realBytes, StandardCharsets.UTF_8)
: Hex.toHexString(realBytes);
}
} catch (OutputLengthException | ArithmeticException e) {
logger.debug("parseDataBytes ", e);
Expand Down Expand Up @@ -74,11 +78,15 @@ protected static Integer intValueExact(byte[] data) {
}

protected static byte[] subBytes(byte[] src, int start, int length) {
if (ArrayUtils.isEmpty(src) || start >= src.length || length < 0) {
throw new OutputLengthException("data start:" + start + ", length:" + length);
if (ArrayUtils.isEmpty(src)) {
throw new OutputLengthException("source data is empty");
}
if (start < 0 || start >= src.length || length < 0 || length > src.length - start) {
throw new OutputLengthException(
"data start:" + start + ", length:" + length + ", src.length:" + src.length);
}
byte[] dst = new byte[length];
System.arraycopy(src, start, dst, 0, min(length, src.length - start, true));
System.arraycopy(src, start, dst, 0, length);
return dst;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ public enum RequestSource {
private static final String NO_BLOCK_HEADER_BY_HASH = "header for hash not found";

private static final String ERROR_SELECTOR = "08c379a0"; // Function selector for Error(string)
private static final int REVERT_REASON_SELECTOR_LENGTH = 4;
private static final int MAX_REVERT_REASON_PAYLOAD_BYTES = 4096;
/**
* thread pool of query section bloom store
*/
Expand Down Expand Up @@ -469,6 +471,36 @@ private void estimateEnergy(byte[] ownerAddressByte, byte[] contractAddressByte,
estimateBuilder.setResult(retBuilder);
}

/**
* Decodes an Error(string) revert reason when possible.
* Returns ": reason" on success, otherwise "".
*/
static String tryDecodeRevertReason(byte[] resData) {
if (resData == null || resData.length <= REVERT_REASON_SELECTOR_LENGTH) {
return "";
}
if (!Hex.toHexString(resData, 0, REVERT_REASON_SELECTOR_LENGTH).equals(ERROR_SELECTOR)) {
return "";
}

int revertPayloadLength = resData.length - REVERT_REASON_SELECTOR_LENGTH;
if (revertPayloadLength > MAX_REVERT_REASON_PAYLOAD_BYTES) {
logger.debug("skip parsing oversized revert reason payload: {} bytes", revertPayloadLength);
return "";
}

try {
String reason = ContractEventParser.parseDataBytes(
org.bouncycastle.util.Arrays.copyOfRange(resData, REVERT_REASON_SELECTOR_LENGTH,
resData.length),
"string", 0);
return ": " + reason;
} catch (Exception e) {
logger.debug("parse revert reason failed", e);
return "";
}
}

/**
* @param data Hash of the method signature and encoded parameters. for example:
* getMethodSign(methodName(uint256,uint256)) || data1 || data2
Expand Down Expand Up @@ -512,14 +544,8 @@ private String call(byte[] ownerAddressByte, byte[] contractAddressByte, long va
}
result = ByteArray.toJsonHex(listBytes);
} else {
String errMsg = retBuilder.getMessage().toStringUtf8();
byte[] resData = trxExtBuilder.getConstantResult(0).toByteArray();
if (resData.length > 4 && Hex.toHexString(resData).startsWith(ERROR_SELECTOR)) {
String msg = ContractEventParser
.parseDataBytes(org.bouncycastle.util.Arrays.copyOfRange(resData, 4, resData.length),
"string", 0);
errMsg += ": " + msg;
}
String errMsg = retBuilder.getMessage().toStringUtf8() + tryDecodeRevertReason(resData);

if (resData.length > 0) {
throw new JsonRpcInternalException(errMsg, ByteArray.toJsonHex(resData));
Expand Down Expand Up @@ -677,15 +703,8 @@ public String estimateGas(CallArguments args) throws JsonRpcInvalidRequestExcept
}

if (trxExtBuilder.getTransaction().getRet(0).getRet().equals(code.FAILED)) {
String errMsg = retBuilder.getMessage().toStringUtf8();

byte[] data = trxExtBuilder.getConstantResult(0).toByteArray();
if (data.length > 4 && Hex.toHexString(data).startsWith(ERROR_SELECTOR)) {
String msg = ContractEventParser
.parseDataBytes(org.bouncycastle.util.Arrays.copyOfRange(data, 4, data.length),
"string", 0);
errMsg += ": " + msg;
}
String errMsg = retBuilder.getMessage().toStringUtf8() + tryDecodeRevertReason(data);

if (data.length > 0) {
throw new JsonRpcInternalException(errMsg, ByteArray.toJsonHex(data));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.bouncycastle.crypto.OutputLengthException;
import org.bouncycastle.util.Arrays;
import org.junit.Assert;
import org.junit.Test;
Expand Down Expand Up @@ -101,6 +102,81 @@ public synchronized void testEventParser() {

}

@Test
public void testParseDataBytesIntegerTypes() {
// uint256 = 255
byte[] uintData = ByteArray.fromHexString(
"00000000000000000000000000000000000000000000000000000000000000ff");
Assert.assertEquals("255", ContractEventParser.parseDataBytes(uintData, "uint256", 0));

// int256 = -1 (two's complement 0xFF..FF is signed negative one)
byte[] negIntData = ByteArray.fromHexString(
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
Assert.assertEquals("-1", ContractEventParser.parseDataBytes(negIntData, "int256", 0));

// trcToken is classified as INT_NUMBER
byte[] tokenData = ByteArray.fromHexString(
"0000000000000000000000000000000000000000000000000000000000000064");
Assert.assertEquals("100", ContractEventParser.parseDataBytes(tokenData, "trcToken", 0));
}

@Test
public void testParseDataBytesBool() {
byte[] trueData = ByteArray.fromHexString(
"0000000000000000000000000000000000000000000000000000000000000001");
Assert.assertEquals("true", ContractEventParser.parseDataBytes(trueData, "bool", 0));

byte[] falseData = ByteArray.fromHexString(
"0000000000000000000000000000000000000000000000000000000000000000");
Assert.assertEquals("false", ContractEventParser.parseDataBytes(falseData, "bool", 0));
}

@Test
public void testParseDataBytesFixedBytes() {
String hex = "1234567890abcdef0000000000000000000000000000000000000000000000ff";
byte[] data = ByteArray.fromHexString(hex);
Assert.assertEquals(hex, ContractEventParser.parseDataBytes(data, "bytes32", 0));
}

@Test
public void testParseDataBytesAddress() {
Wallet.setAddressPreFixByte(ADD_PRE_FIX_BYTE_MAINNET);
// last 20 bytes = ca35...733c => Base58Check = TUQPrDEJkV4ttkrL7cVv1p3mikWYfM7LWt
byte[] data = ByteArray.fromHexString(
"000000000000000000000000ca35b7d915458ef540ade6068dfe2f44e8fa733c");
Assert.assertEquals("TUQPrDEJkV4ttkrL7cVv1p3mikWYfM7LWt",
ContractEventParser.parseDataBytes(data, "address", 0));
}

@Test
public void testParseDataBytesDynamicBytes() {
// offset 0x20 | length 3 | 0x010203 padded to 32 bytes
byte[] data = ByteArray.fromHexString(
"0000000000000000000000000000000000000000000000000000000000000020"
+ "0000000000000000000000000000000000000000000000000000000000000003"
+ "0102030000000000000000000000000000000000000000000000000000000000");
Assert.assertEquals("010203", ContractEventParser.parseDataBytes(data, "bytes", 0));
}

@Test
public void testParseDataBytesEmptyString() {
// offset 0x20 | length 0
byte[] data = ByteArray.fromHexString(
"0000000000000000000000000000000000000000000000000000000000000020"
+ "0000000000000000000000000000000000000000000000000000000000000000");
Assert.assertEquals("", ContractEventParser.parseDataBytes(data, "string", 0));
}

@Test
public void testParseDataBytesNonEmptyString() {
// "hello world" is 11 ASCII bytes (68656c6c6f20776f726c64), padded to 32 bytes.
byte[] data = ByteArray.fromHexString(
"0000000000000000000000000000000000000000000000000000000000000020"
+ "000000000000000000000000000000000000000000000000000000000000000b"
+ "68656c6c6f20776f726c64000000000000000000000000000000000000000000");
Assert.assertEquals("hello world", ContractEventParser.parseDataBytes(data, "string", 0));
}

@Test
public void testParseRevert() {
String dataHex = "08c379a0"
Expand All @@ -114,4 +190,87 @@ public void testParseRevert() {
Assert.assertEquals(msg, "not enough input value");

}

@Test
public void testSubBytesRejectsOversizedLength() {
// Length must fit in the available source bytes. Reject instead of
// truncating so oversized ABI lengths are not silently coerced.
byte[] src = new byte[]{1, 2, 3};
try {
ContractEventParser.subBytes(src, 0, Integer.MAX_VALUE);
Assert.fail("Expected OutputLengthException");
} catch (OutputLengthException e) {
Assert.assertTrue(e.getMessage().contains("data start:0"));
Assert.assertTrue(e.getMessage().contains("length:2147483647"));
Assert.assertTrue(e.getMessage().contains("src.length:3"));
}
Comment thread
0xbigapple marked this conversation as resolved.
}

@Test
public void testSubBytesAcceptsExactLength() {
byte[] src = new byte[]{1, 2, 3, 4};
byte[] result = ContractEventParser.subBytes(src, 1, 3);
Assert.assertArrayEquals(new byte[]{2, 3, 4}, result);
}

@Test
public void testSubBytesRejectsNegativeOffset() {
// ABI offsets are unsigned, but BigInteger(byte[]) interprets 0xFF..FF as
// -1. The guard should reject that value before System.arraycopy runs.
byte[] src = new byte[]{1, 2, 3, 4};
try {
ContractEventParser.subBytes(src, -1, 3);
Assert.fail("Expected OutputLengthException");
} catch (OutputLengthException e) {
Assert.assertTrue(e.getMessage().contains("data start:-1"));
Assert.assertTrue(e.getMessage().contains("length:3"));
Assert.assertTrue(e.getMessage().contains("src.length:4"));
}
}

@Test
public void testSubBytesRejectsEmptySource() {
try {
ContractEventParser.subBytes(new byte[0], 0, 0);
Assert.fail("Expected OutputLengthException");
} catch (OutputLengthException e) {
Assert.assertTrue(e.getMessage().contains("source data is empty"));
}
}

@Test(expected = UnsupportedOperationException.class)
public void testParseDataBytesRejectsNegativeOffset() {
// End-to-end check: an offset field of 0xFF..FF decodes to -1 and should
// be rejected through the existing UnsupportedOperationException path.
String dataHex = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
+ "0000000000000000000000000000000000000000000000000000000000000003"
+ "414243";
byte[] data = ByteArray.fromHexString(dataHex);

ContractEventParser.parseDataBytes(data, "string", 0);
}

@Test(expected = UnsupportedOperationException.class)
public void testParseDataBytesRejectsMalformedLength() {
// ABI-encoded "string" whose declared length exceeds the available payload
// should be rejected via the existing UnsupportedOperationException path.
String dataHex = "0000000000000000000000000000000000000000000000000000000000000020"
+ "000000000000000000000000000000000000000000000000000000007fffffff"
+ "414243";
byte[] data = ByteArray.fromHexString(dataHex);

ContractEventParser.parseDataBytes(data, "string", 0);
}

@Test(expected = UnsupportedOperationException.class)
public void testParseDataBytesRejectsNegativeLength() {
// ABI length is an unsigned word. If 0xFF..FF is decoded as -1, reject it
// instead of treating it as an empty string/bytes payload.
String dataHex = "0000000000000000000000000000000000000000000000000000000000000020"
+ "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
+ "414243";
byte[] data = ByteArray.fromHexString(dataHex);

ContractEventParser.parseDataBytes(data, "string", 0);
}
}
Loading
Loading