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
1 change: 1 addition & 0 deletions abi/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ description 'TRON Application Binary Interface (ABI) for working with smart cont

dependencies {
implementation project(':utils')
testImplementation "com.fasterxml.jackson.core:jackson-databind:2.18.6"
}
43 changes: 43 additions & 0 deletions abi/src/main/java/org/tron/trident/abi/CustomErrorEncoder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.tron.trident.abi;

import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.stream.Collectors;
import org.tron.trident.abi.datatypes.CustomError;
import org.tron.trident.abi.datatypes.Type;
import org.tron.trident.crypto.Hash;
import org.tron.trident.utils.Numeric;

/**
* Ethereum custom error encoding. Further limited details are available <a
* href="https://docs.soliditylang.org/en/develop/abi-spec.html#errors">here</a>.
*/
public class CustomErrorEncoder {

private CustomErrorEncoder() {
}

public static String encode(CustomError error) {
return calculateSignatureHash(
buildErrorSignature(error.getName(), error.getParameters()));
}

static <T extends Type> String buildErrorSignature(
String errorName, List<TypeReference<T>> parameters) {

StringBuilder result = new StringBuilder();
result.append(errorName);
result.append("(");
String params =
parameters.stream().map(Utils::getTypeName).collect(Collectors.joining(","));
result.append(params);
result.append(")");
return result.toString();
}

public static String calculateSignatureHash(String errorSignature) {
byte[] input = errorSignature.getBytes(StandardCharsets.UTF_8);
byte[] hash = Hash.sha3(input);
return Numeric.toHexString(hash).substring(2);
}
}
47 changes: 45 additions & 2 deletions abi/src/main/java/org/tron/trident/abi/DefaultFunctionEncoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import java.util.List;
import org.tron.trident.abi.datatypes.Function;
import org.tron.trident.abi.datatypes.StaticArray;
import org.tron.trident.abi.datatypes.StaticStruct;
import org.tron.trident.abi.datatypes.Type;
import org.tron.trident.abi.datatypes.Uint;

Expand Down Expand Up @@ -64,11 +65,53 @@ private static String encodeParameters(
return result.toString();
}

/**
* Encodes a function call including its method signature (selector) and its parameters.
*
* @param methodId the 4-byte selector (8 hex chars)
* @param parameters the list of parameters to encode
* @return the complete ABI-encoded hex string representing the function call
*/
public String encodeWithSelector(String methodId, List<Type> parameters) {
final StringBuilder result = new StringBuilder(methodId);

return encodeParameters(parameters, result);
}

/**
* Encodes parameters using tight packing (abi.encodePacked).
* This is a non-standard ABI encoding used primarily for computing hashes,
* where padding is omitted and dynamic types are concatenated without length prefixes.
*
* @param parameters the list of parameters to pack
* @return the packed ABI-encoded hex string
*/
@Override
protected String encodePackedParameters(List<Type> parameters) {
final StringBuilder result = new StringBuilder();
for (Type parameter : parameters) {
result.append(TypeEncoder.encodePacked(parameter));
}
return result.toString();
}

/**
* Calculates the length of the tuple head (in 32-byte slots) required for the given parameters.
* Crucially, all dynamic types (including StaticArrays containing dynamic elements) always occupy exactly
* 1 slot in the head (for the offset pointer). Pure static arrays and structs are flattened to calculate
* their total inline slot requirement.
*
* @param parameters the list of types to be encoded.
* @return the total number of 32-byte slots required for the tuple head.
*/
@SuppressWarnings("unchecked")
private static int getLength(final List<Type> parameters) {
int count = 0;
for (final Type type : parameters) {
if (type instanceof StaticArray) {
count += ((StaticArray) type).getValue().size();
if (TypeEncoder.isDynamic(type)) {
count++;
} else if (type instanceof StaticArray || type instanceof StaticStruct) {
count += type.bytes32PaddedLength() / Type.MAX_BYTE_LENGTH;
} else {
count++;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@

package org.tron.trident.abi;

import static org.tron.trident.abi.TypeDecoder.MAX_BYTE_LENGTH_FOR_HEX_STRING;
import static org.tron.trident.abi.TypeDecoder.isDynamic;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
Expand All @@ -31,7 +34,7 @@
import org.tron.trident.utils.Strings;

/**
* Ethereum Contract Application Binary Interface (ABI) encoding for functions. Further details are
* Ethereum Contract Application Binary Interface (ABI) decoding for functions. Further details are
* available <a href="https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI">here</a>.
*/
public class DefaultFunctionReturnDecoder extends FunctionReturnDecoder {
Expand Down Expand Up @@ -73,60 +76,65 @@ public <T extends Type> Type decodeEventParameter(
}

private static List<Type> build(String input, List<TypeReference<Type>> outputParameters) {
// Reject cyclic or excessively nested TypeReference graphs up front; any
// downstream recursion through subTypeReference / innerTypes is then bounded
// by what passed validation here.
for (TypeReference<?> typeReference : outputParameters) {
Utils.validateTypeReferenceDepth(typeReference);
}

List<Type> results = new ArrayList<>(outputParameters.size());

int offset = 0;
for (TypeReference<?> typeReference : outputParameters) {
try {
int hexStringDataOffset = getDataOffset(input, offset, typeReference);

@SuppressWarnings("unchecked")
Class<Type> classType = (Class<Type>) typeReference.getClassType();

int hexStringDataOffset = getDataOffset(input, offset, classType);

Type result;
if (DynamicStruct.class.isAssignableFrom(classType)) {
if (outputParameters.size() != 1) {
throw new UnsupportedOperationException(
"Multiple return objects containing a struct is not supported");
}
result =
TypeDecoder.decodeDynamicStruct(
input, hexStringDataOffset, typeReference);
offset += TypeDecoder.MAX_BYTE_LENGTH_FOR_HEX_STRING;
TypeDecoder.decodeDynamicStruct(
input, hexStringDataOffset, typeReference);
offset += MAX_BYTE_LENGTH_FOR_HEX_STRING;

} else if (DynamicArray.class.isAssignableFrom(classType)) {
result =
TypeDecoder.decodeDynamicArray(
input, hexStringDataOffset, typeReference);
offset += TypeDecoder.MAX_BYTE_LENGTH_FOR_HEX_STRING;

} else if (typeReference instanceof TypeReference.StaticArrayTypeReference) {
int length = ((TypeReference.StaticArrayTypeReference) typeReference).getSize();
result =
TypeDecoder.decodeStaticArray(
input, hexStringDataOffset, typeReference, length);
offset += length * TypeDecoder.MAX_BYTE_LENGTH_FOR_HEX_STRING;
TypeDecoder.decodeDynamicArray(
input, hexStringDataOffset, typeReference);
offset += MAX_BYTE_LENGTH_FOR_HEX_STRING;

} else if (StaticStruct.class.isAssignableFrom(classType)) {
result =
TypeDecoder.decodeStaticStruct(
input, hexStringDataOffset, typeReference);
offset +=
classType.getDeclaredFields().length * TypeDecoder.MAX_BYTE_LENGTH_FOR_HEX_STRING;
TypeDecoder.decodeStaticStruct(
input, hexStringDataOffset, typeReference);
offset += (result.bytes32PaddedLength() / Type.MAX_BYTE_LENGTH)
* MAX_BYTE_LENGTH_FOR_HEX_STRING;

} else if (StaticArray.class.isAssignableFrom(classType)) {
int length =
Integer.parseInt(
classType
.getSimpleName()
.substring(StaticArray.class.getSimpleName().length()));
int length;
if (typeReference instanceof TypeReference.StaticArrayTypeReference) {
length = ((TypeReference.StaticArrayTypeReference) typeReference).getSize();
} else {
length = Integer.parseInt(
classType.getSimpleName()
.substring(StaticArray.class.getSimpleName().length()));
}
result =
TypeDecoder.decodeStaticArray(
input, hexStringDataOffset, typeReference, length);
offset += length * TypeDecoder.MAX_BYTE_LENGTH_FOR_HEX_STRING;

TypeDecoder.decodeStaticArray(
input, hexStringDataOffset, typeReference, length);
if (isDynamic(typeReference)) {
offset += MAX_BYTE_LENGTH_FOR_HEX_STRING;
} else {
offset +=
(result.bytes32PaddedLength() / Type.MAX_BYTE_LENGTH)
* MAX_BYTE_LENGTH_FOR_HEX_STRING;
}
} else {
result = TypeDecoder.decode(input, hexStringDataOffset, classType);
offset += TypeDecoder.MAX_BYTE_LENGTH_FOR_HEX_STRING;
offset += MAX_BYTE_LENGTH_FOR_HEX_STRING;
}
results.add(result);

Expand All @@ -137,13 +145,31 @@ private static List<Type> build(String input, List<TypeReference<Type>> outputPa
return results;
}

private static <T extends Type> int getDataOffset(String input, int offset, Class<T> type) {
public static <T extends Type> int getDataOffset(
String input, int offset, TypeReference<?> typeReference)
throws ClassNotFoundException {
@SuppressWarnings("unchecked")
Class<Type> type = (Class<Type>) typeReference.getClassType();
if (DynamicBytes.class.isAssignableFrom(type)
|| Utf8String.class.isAssignableFrom(type)
|| DynamicArray.class.isAssignableFrom(type)) {
|| Utf8String.class.isAssignableFrom(type)
|| DynamicArray.class.isAssignableFrom(type)
|| DynamicStruct.class.isAssignableFrom(type)
|| hasDynamicOffsetInStaticArray(typeReference)) {
return TypeDecoder.decodeUintAsInt(input, offset) << 1;
} else {
return offset;
}
}

private static boolean hasDynamicOffsetInStaticArray(TypeReference<?> typeReference)
throws ClassNotFoundException {
@SuppressWarnings("unchecked")
Class<Type> type = (Class<Type>) typeReference.getClassType();
try {
return StaticArray.class.isAssignableFrom(type)
&& isDynamic(typeReference);
} catch (ClassCastException e) {
return false;
}
}
}
20 changes: 20 additions & 0 deletions abi/src/main/java/org/tron/trident/abi/EventEncoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,16 @@ public static String encode(Event event) {
return buildEventSignature(methodSignature);
}

/**
* Encodes the given {@code Event} and removes '0x' of the encoded string.
*
* @param event the {@code Event} instance to be encoded
* @return a hexadecimal string representing the encoded event, excluding the prefix '0x'
*/
public static String encodeWithOutPrefix(Event event) {
return encode(event).substring(2);
}

static <T extends Type> String buildMethodSignature(
String methodName, List<TypeReference<T>> parameters) {

Expand All @@ -54,4 +64,14 @@ public static String buildEventSignature(String methodSignature) {
byte[] hash = Hash.sha3(input);
return Numeric.toHexString(hash);
}

/**
* Generates the event signature hash without the '0x' prefix
*
* @param methodSignature the string representation of the method signature for the event
* @return a hexadecimal string representing the event signature hash, excluding the prefix '0x'
*/
public static String buildEventSignatureWithOutPrefix(String methodSignature) {
return buildEventSignature(methodSignature).substring(2);
}
}
Loading