-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
feat: dynamic array encode #2184
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,7 +15,6 @@ | |
import java.io.ByteArrayOutputStream; | ||
import java.io.IOException; | ||
import java.lang.reflect.Constructor; | ||
import java.lang.reflect.InvocationTargetException; | ||
import java.math.BigInteger; | ||
import java.util.ArrayList; | ||
import java.util.Collections; | ||
|
@@ -102,7 +101,7 @@ public Set<String> getDependencies(String primaryType) { | |
String baseDeclarationTypeName = | ||
arrayTypePattern.matcher(declarationFieldTypeName).find() | ||
? declarationFieldTypeName.substring( | ||
0, declarationFieldTypeName.indexOf('[')) | ||
0, declarationFieldTypeName.indexOf('[')) | ||
: declarationFieldTypeName; | ||
if (!types.containsKey(baseDeclarationTypeName)) { | ||
// Don't expand on non-user defined types | ||
|
@@ -307,16 +306,21 @@ public byte[] encodeData(String primaryType, HashMap<String, Object> data) | |
|
||
List<String> encTypes = new ArrayList<>(); | ||
List<Object> encValues = new ArrayList<>(); | ||
List<byte[]> dynamicData = new ArrayList<>(); // Store dynamic data | ||
|
||
// Add typehash | ||
encTypes.add("bytes32"); | ||
encValues.add(typeHash(primaryType)); | ||
|
||
|
||
|
||
Comment on lines
+315
to
+316
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why the extra newlines? |
||
// Add field contents | ||
for (StructuredData.Entry field : types.get(primaryType)) { | ||
Object value = data.get(field.getName()); | ||
|
||
if (value == null) continue; | ||
if (value == null) { | ||
continue; | ||
} | ||
Comment on lines
-319
to
+323
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unnecessary change? |
||
|
||
if (field.getType().equals("string")) { | ||
encTypes.add("bytes32"); | ||
|
@@ -335,88 +339,123 @@ public byte[] encodeData(String primaryType, HashMap<String, Object> data) | |
encTypes.add(field.getType()); | ||
encValues.add(Numeric.hexStringToByteArray((String) value)); | ||
} else if (arrayTypePattern.matcher(field.getType()).find()) { | ||
// Calculate header size (static part) | ||
int headSize = 32; | ||
Comment on lines
+342
to
+343
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are not calculating the header size, but hardcoding it - so the comment is misleading. |
||
// Track total size of dynamic data | ||
int dynamicDataSize = 0; | ||
Comment on lines
+344
to
+345
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redundant comment |
||
String baseTypeName = field.getType().substring(0, field.getType().indexOf('[')); | ||
List<Object> arrayItems = getArrayItems(field, value); | ||
ByteArrayOutputStream concatenatedArrayEncodingBuffer = new ByteArrayOutputStream(); | ||
|
||
for (Object arrayItem : arrayItems) { | ||
byte[] arrayItemEncoding; | ||
if (types.containsKey(baseTypeName)) { | ||
arrayItemEncoding = | ||
sha3( | ||
encodeData( | ||
baseTypeName, | ||
(HashMap<String, Object>) | ||
arrayItem)); // need to hash each user type | ||
// before adding | ||
} else { | ||
arrayItemEncoding = | ||
convertToEncodedItem( | ||
baseTypeName, | ||
arrayItem); // add raw item, packed to 32 bytes | ||
|
||
if (baseTypeName.startsWith("uint") | ||
|| baseTypeName.startsWith("int") | ||
|| baseTypeName.equals("address") | ||
|| baseTypeName.equals("bool")) { | ||
// Handle dynamic array | ||
encTypes.add(baseTypeName); // Use base type instead of array type | ||
Comment on lines
+353
to
+354
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems to be AI-generated. |
||
// Add offset position, considering actual size of all previous dynamic data | ||
encValues.add(BigInteger.valueOf(headSize + dynamicDataSize)); | ||
|
||
// Prepare dynamic data | ||
ByteArrayOutputStream dynamicBuffer = new ByteArrayOutputStream(); | ||
Comment on lines
+358
to
+359
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redundant comment. |
||
// Write array length | ||
byte[] lengthBytes = | ||
Numeric.toBytesPadded(BigInteger.valueOf(arrayItems.size()), 32); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Extract magic number to a constant |
||
dynamicBuffer.write(lengthBytes, 0, lengthBytes.length); | ||
|
||
// Write array elements | ||
for (Object arrayItem : arrayItems) { | ||
BigInteger itemValue = convertToBigInt(arrayItem); | ||
byte[] itemBytes = Numeric.toBytesPadded(itemValue, 32); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here ^ |
||
dynamicBuffer.write(itemBytes, 0, itemBytes.length); | ||
} | ||
|
||
concatenatedArrayEncodingBuffer.write( | ||
arrayItemEncoding, 0, arrayItemEncoding.length); | ||
byte[] dynamicBytes = dynamicBuffer.toByteArray(); | ||
dynamicData.add(dynamicBytes); | ||
// Update total size of dynamic data | ||
dynamicDataSize += dynamicBytes.length; | ||
Comment on lines
+374
to
+375
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redundant comment - possibly because of AI |
||
} else { | ||
// Handle other types of arrays | ||
ByteArrayOutputStream concatenatedArrayEncodingBuffer = | ||
new ByteArrayOutputStream(); | ||
for (Object arrayItem : arrayItems) { | ||
byte[] arrayItemEncoding; | ||
if (types.containsKey(baseTypeName)) { | ||
arrayItemEncoding = | ||
sha3( | ||
encodeData( | ||
baseTypeName, | ||
(HashMap<String, Object>) arrayItem)); | ||
} else { | ||
arrayItemEncoding = convertToEncodedItem(baseTypeName, arrayItem); | ||
} | ||
concatenatedArrayEncodingBuffer.write( | ||
arrayItemEncoding, 0, arrayItemEncoding.length); | ||
} | ||
byte[] concatenatedArrayEncodings = | ||
concatenatedArrayEncodingBuffer.toByteArray(); | ||
byte[] hashedValue = sha3(concatenatedArrayEncodings); | ||
encTypes.add("bytes32"); | ||
encValues.add(hashedValue); | ||
} | ||
|
||
byte[] concatenatedArrayEncodings = concatenatedArrayEncodingBuffer.toByteArray(); | ||
byte[] hashedValue = sha3(concatenatedArrayEncodings); | ||
encTypes.add("bytes32"); | ||
encValues.add(hashedValue); | ||
} else if (field.getType().startsWith("uint") || field.getType().startsWith("int")) { | ||
encTypes.add(field.getType()); | ||
// convert to BigInteger for ABI constructor compatibility | ||
try { | ||
encValues.add(convertToBigInt(value)); | ||
} catch (NumberFormatException | NullPointerException e) { | ||
encValues.add( | ||
value); // value null or failed to convert, fallback to add string as | ||
// before | ||
encValues.add(value); | ||
} | ||
} else { | ||
encTypes.add(field.getType()); | ||
encValues.add(value); | ||
} | ||
} | ||
|
||
// Write all data | ||
ByteArrayOutputStream baos = new ByteArrayOutputStream(); | ||
|
||
// Write header (static data and offsets) | ||
for (int i = 0; i < encTypes.size(); i++) { | ||
Class<Type> typeClazz = (Class<Type>) AbiTypes.getType(encTypes.get(i)); | ||
String type = encTypes.get(i); | ||
Object value = encValues.get(i); | ||
|
||
boolean atleastOneConstructorExistsForGivenParametersType = false; | ||
// Using the Reflection API to get the types of the parameters | ||
Constructor[] constructors = typeClazz.getConstructors(); | ||
for (Constructor constructor : constructors) { | ||
// Check which constructor matches | ||
try { | ||
Class[] parameterTypes = constructor.getParameterTypes(); | ||
byte[] temp = | ||
Numeric.hexStringToByteArray( | ||
TypeEncoder.encode( | ||
typeClazz | ||
.getDeclaredConstructor(parameterTypes) | ||
.newInstance(encValues.get(i)))); | ||
baos.write(temp, 0, temp.length); | ||
atleastOneConstructorExistsForGivenParametersType = true; | ||
break; | ||
} catch (IllegalArgumentException | ||
| NoSuchMethodException | ||
| InstantiationException | ||
| IllegalAccessException | ||
| InvocationTargetException ignored) { | ||
if (type.equals("bytes32")) { | ||
if (value instanceof byte[]) { | ||
baos.write((byte[]) value, 0, ((byte[]) value).length); | ||
} else { | ||
throw new RuntimeException("Expected byte[] for bytes32 type"); | ||
} | ||
} else { | ||
Class<Type> typeClazz = (Class<Type>) AbiTypes.getType(type); | ||
Constructor[] constructors = typeClazz.getConstructors(); | ||
boolean encoded = false; | ||
|
||
for (Constructor constructor : constructors) { | ||
try { | ||
Class[] parameterTypes = constructor.getParameterTypes(); | ||
byte[] temp = | ||
Numeric.hexStringToByteArray( | ||
TypeEncoder.encode( | ||
typeClazz | ||
.getDeclaredConstructor(parameterTypes) | ||
.newInstance(value))); | ||
baos.write(temp, 0, temp.length); | ||
encoded = true; | ||
break; | ||
} catch (Exception ignored) { | ||
} | ||
} | ||
} | ||
|
||
if (!atleastOneConstructorExistsForGivenParametersType) { | ||
throw new RuntimeException( | ||
String.format( | ||
"Received an invalid argument for which no constructor" | ||
+ " exists for the ABI Class %s", | ||
typeClazz.getSimpleName())); | ||
if (!encoded) { | ||
throw new RuntimeException("Failed to encode parameter"); | ||
} | ||
} | ||
} | ||
|
||
// Write dynamic data | ||
for (byte[] dynamicBytes : dynamicData) { | ||
baos.write(dynamicBytes, 0, dynamicBytes.length); | ||
} | ||
|
||
return baos.toByteArray(); | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Redundant comment