diff --git a/src/main/java/com/esaulpaugh/headlong/abi/ABIType.java b/src/main/java/com/esaulpaugh/headlong/abi/ABIType.java index 4589ad18b..a8eed5a32 100644 --- a/src/main/java/com/esaulpaugh/headlong/abi/ABIType.java +++ b/src/main/java/com/esaulpaugh/headlong/abi/ABIType.java @@ -18,9 +18,7 @@ import com.esaulpaugh.headlong.util.Integers; import com.esaulpaugh.headlong.util.Strings; -import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; -import java.util.function.IntFunction; import static com.esaulpaugh.headlong.abi.UnitType.UNIT_LENGTH_BYTES; @@ -139,43 +137,6 @@ public final J decode(ByteBuffer buffer) { */ abstract J decode(ByteBuffer buffer, byte[] unitBuffer); - private static int[] decodeHeads(int len, ByteBuffer bb, byte[] unitBuffer, Object[] elements, IntFunction> getType) { - final int[] offsets = new int[len]; - for(int i = 0; i < len; i++) { - ABIType t = getType.apply(i); - if(!t.dynamic) { - elements[i] = t.decode(bb, unitBuffer); - } else { - offsets[i] = Encoding.UINT31.decode(bb, unitBuffer); - } - } - return offsets; - } - - static void decodeObjects(int len, ByteBuffer bb, byte[] unitBuffer, Object[] elements, IntFunction> getType) { - final int start = bb.position(); // save this value before offsets are decoded - final int[] offsets = decodeHeads(len, bb, unitBuffer, elements, getType); - for (int i = 0; i < len; i++) { - final int offset = offsets[i]; - if(offset > 0) { - final int jump = start + offset; - final int pos = bb.position(); - if(jump != pos) { - /* LENIENT MODE; see https://github.com/ethereum/solidity/commit/3d1ca07e9b4b42355aa9be5db5c00048607986d1 */ - if(jump < pos) { - throw new IllegalArgumentException("illegal backwards jump: (" + start + "+" + offset + "=" + jump + ")<" + pos); - } - bb.position(jump); // leniently jump to specified offset - } - try { - elements[i] = getType.apply(i).decode(bb, unitBuffer); - } catch (BufferUnderflowException bue) { - throw new IllegalArgumentException(bue); - } - } - } - } - /** * Parses and validates a string representation of J. * diff --git a/src/main/java/com/esaulpaugh/headlong/abi/ArrayType.java b/src/main/java/com/esaulpaugh/headlong/abi/ArrayType.java index 0e6587081..e5b285a5c 100644 --- a/src/main/java/com/esaulpaugh/headlong/abi/ArrayType.java +++ b/src/main/java/com/esaulpaugh/headlong/abi/ArrayType.java @@ -236,73 +236,52 @@ private int checkLength(final int valueLen, Object value) { @Override void encodeTail(Object value, ByteBuffer dest) { - encodeArrayTail(value, dest); - } - - private void insert(int len, ByteBuffer dest, Runnable insert) { - if(length == DYNAMIC_LENGTH) { - Encoding.insertInt(len, dest); - } - insert.run(); - } - - private void encodeArrayTail(Object v, ByteBuffer dest) { switch (elementType.typeCode()) { - case TYPE_CODE_BOOLEAN: encodeBooleans((boolean[]) v, dest); return; - case TYPE_CODE_BYTE: encodeBytes(decodeIfString(v), dest); return; - case TYPE_CODE_INT: encodeInts((int[]) v, dest); return; - case TYPE_CODE_LONG: encodeLongs((long[]) v, dest); return; + case TYPE_CODE_BOOLEAN: encodeBooleans((boolean[]) value, dest); return; + case TYPE_CODE_BYTE: encodeBytes(decodeIfString(value), dest); return; + case TYPE_CODE_INT: encodeInts((int[]) value, dest); return; + case TYPE_CODE_LONG: encodeLongs((long[]) value, dest); return; case TYPE_CODE_BIG_INTEGER: case TYPE_CODE_BIG_DECIMAL: case TYPE_CODE_ARRAY: - case TYPE_CODE_TUPLE: encodeObjects((Object[]) v, dest); return; + case TYPE_CODE_TUPLE: + Object[] arr = (Object[]) value; + encodeArrayLen(arr.length, dest); + TupleType.encodeObjects(dynamic, arr, dest, (i) -> elementType); + return; default: throw new Error(); } } + private void encodeArrayLen(int len, ByteBuffer dest) { + if(length == DYNAMIC_LENGTH) { + Encoding.insertInt(len, dest); + } + } + private void encodeBooleans(boolean[] arr, ByteBuffer dest) { - insert(arr.length, dest, () -> { - for (boolean e : arr) { - BooleanType.encodeBoolean(e, dest); - } - }); + encodeArrayLen(arr.length, dest); + for (boolean e : arr) { + BooleanType.encodeBoolean(e, dest); + } } private void encodeBytes(byte[] arr, ByteBuffer dest) { - insert(arr.length, dest, () -> Encoding.insertBytesPadded(arr, dest)); + encodeArrayLen(arr.length, dest); + Encoding.insertBytesPadded(arr, dest); } private void encodeInts(int[] arr, ByteBuffer dest) { - insert(arr.length, dest, () -> { - for (int e : arr) { - Encoding.insertInt(e, dest); - } - }); - } - - private void encodeLongs(long[] arr, ByteBuffer dest) { - insert(arr.length, dest, () -> { - for (long e : arr) { - Encoding.insertInt(e, dest); - } - }); - } - - private void encodeObjects(Object[] arr, ByteBuffer dest) { - if(dynamic) { - insert(arr.length, dest, () -> insertOffsets(arr, dest)); - } - for (Object object : arr) { - elementType.encodeTail(object, dest); + encodeArrayLen(arr.length, dest); + for (int e : arr) { + Encoding.insertInt(e, dest); } } - private void insertOffsets(Object[] objects, ByteBuffer dest) { - if (elementType.dynamic) { - int nextOffset = objects.length * OFFSET_LENGTH_BYTES; - for (Object object : objects) { - nextOffset = Encoding.insertOffset(nextOffset, dest, elementType.byteLength(object)); - } + private void encodeLongs(long[] arr, ByteBuffer dest) { + encodeArrayLen(arr.length, dest); + for (long e : arr) { + Encoding.insertInt(e, dest); } } @@ -377,7 +356,7 @@ private static Object decodeLongs(int len, ByteBuffer bb, LongType longType, byt private Object decodeObjects(int len, ByteBuffer bb, byte[] unitBuffer) { Object[] elements = (Object[]) Array.newInstance(elementType.clazz, len); // reflection ftw - decodeObjects(len, bb, unitBuffer, elements, (i) -> elementType); + TupleType.decodeObjects(len, bb, unitBuffer, elements, (i) -> elementType); return elements; } diff --git a/src/main/java/com/esaulpaugh/headlong/abi/PackedDecoder.java b/src/main/java/com/esaulpaugh/headlong/abi/PackedDecoder.java index 78cca6503..4809a50fb 100644 --- a/src/main/java/com/esaulpaugh/headlong/abi/PackedDecoder.java +++ b/src/main/java/com/esaulpaugh/headlong/abi/PackedDecoder.java @@ -121,8 +121,8 @@ private static int decode(ABIType type, byte[] buffer, int idx, int end, Obje case TYPE_CODE_BYTE: elements[i] = buffer[idx]; return type.byteLengthPacked(null); case TYPE_CODE_INT: return insertInt((IntType) type, buffer, idx, type.byteLengthPacked(null), elements, i); case TYPE_CODE_LONG: return insertLong((LongType) type, buffer, idx, type.byteLengthPacked(null), elements, i); - case TYPE_CODE_BIG_INTEGER: return insertBigInteger(type.byteLengthPacked(null), buffer, idx, elements, i, (BigIntegerType) type); - case TYPE_CODE_BIG_DECIMAL: return insertBigDecimal(type.byteLengthPacked(null), buffer, idx, elements, i, (BigDecimalType) type); + case TYPE_CODE_BIG_INTEGER: return insertBigInteger((BigIntegerType) type, type.byteLengthPacked(null), buffer, idx, elements, i); + case TYPE_CODE_BIG_DECIMAL: return insertBigDecimal((BigDecimalType) type, type.byteLengthPacked(null), buffer, idx, elements, i); case TYPE_CODE_ARRAY: return insertArray((ArrayType, ?>) type, buffer, idx, end, elements, i); case TYPE_CODE_TUPLE: return type.dynamic @@ -152,7 +152,7 @@ private static int insertLong(UnitType type, byte[] buffer, in return len; } - private static int insertBigInteger(int elementLen, byte[] buffer, int idx, Object[] dest, int destIdx, BigIntegerType type) { + private static int insertBigInteger(BigIntegerType type, int elementLen, byte[] buffer, int idx, Object[] dest, int destIdx) { if(type.unsigned) { byte[] copy = new byte[1 + elementLen]; System.arraycopy(buffer, idx, copy, 1, elementLen); @@ -164,7 +164,7 @@ private static int insertBigInteger(int elementLen, byte[] buffer, int idx, Obje return elementLen; } - private static int insertBigDecimal(int elementLen, byte[] buffer, int idx, Object[] dest, int destIdx, BigDecimalType type) { + private static int insertBigDecimal(BigDecimalType type, int elementLen, byte[] buffer, int idx, Object[] dest, int destIdx) { BigInteger unscaled; if(type.unsigned) { byte[] copy = new byte[1 + elementLen]; @@ -194,11 +194,11 @@ private static int insertArray(ArrayType, ?> arrayType, byt final Object array; switch (elementType.typeCode()) { case TYPE_CODE_BOOLEAN: array = decodeBooleanArray(arrayLen, buffer, idx); break; - case TYPE_CODE_BYTE: array = arrayType.encodeIfString(decodeByteArray(arrayLen, buffer, idx)); break; + case TYPE_CODE_BYTE: array = decodeByteArray(arrayType, arrayLen, buffer, idx); break; case TYPE_CODE_INT: array = decodeIntArray((IntType) elementType, elementByteLen, arrayLen, buffer, idx); break; case TYPE_CODE_LONG: array = decodeLongArray((LongType) elementType, elementByteLen, arrayLen, buffer, idx); break; - case TYPE_CODE_BIG_INTEGER: array = decodeBigIntegerArray(elementByteLen, arrayLen, buffer, idx, (BigIntegerType) elementType); break; - case TYPE_CODE_BIG_DECIMAL: array = decodeBigDecimalArray(elementByteLen, arrayLen, buffer, idx, (BigDecimalType) elementType); break; + case TYPE_CODE_BIG_INTEGER: array = decodeBigIntegerArray((BigIntegerType) elementType, elementByteLen, arrayLen, buffer, idx); break; + case TYPE_CODE_BIG_DECIMAL: array = decodeBigDecimalArray((BigDecimalType) elementType, elementByteLen, arrayLen, buffer, idx); break; case TYPE_CODE_ARRAY: case TYPE_CODE_TUPLE: array = decodeObjectArray(arrayLen, elementType, buffer, idx, end); break; default: throw new Error(); @@ -215,10 +215,10 @@ private static boolean[] decodeBooleanArray(int arrayLen, byte[] buffer, int idx return booleans; } - private static byte[] decodeByteArray(int arrayLen, byte[] buffer, int idx) { + private static Object decodeByteArray(ArrayType arrayType, int arrayLen, byte[] buffer, int idx) { byte[] bytes = new byte[arrayLen]; System.arraycopy(buffer, idx, bytes, 0, arrayLen); - return bytes; + return arrayType.encodeIfString(bytes); } private static int[] decodeIntArray(IntType intType, int elementLen, int arrayLen, byte[] buffer, int idx) { @@ -247,18 +247,18 @@ private static long[] decodeLongArray(UnitType type, int eleme return longs; } - private static BigInteger[] decodeBigIntegerArray(int elementLen, int arrayLen, byte[] buffer, int idx, BigIntegerType elementType) { + private static BigInteger[] decodeBigIntegerArray(BigIntegerType elementType, int elementLen, int arrayLen, byte[] buffer, int idx) { BigInteger[] bigInts = new BigInteger[arrayLen]; for (int i = 0; i < arrayLen; i++) { - idx += insertBigInteger(elementLen, buffer, idx, bigInts, i, elementType); + idx += insertBigInteger(elementType, elementLen, buffer, idx, bigInts, i); } return bigInts; } - private static BigDecimal[] decodeBigDecimalArray(int elementLen, int arrayLen, byte[] buffer, int idx, BigDecimalType elementType) { + private static BigDecimal[] decodeBigDecimalArray(BigDecimalType elementType, int elementLen, int arrayLen, byte[] buffer, int idx) { BigDecimal[] bigDecimals = new BigDecimal[arrayLen]; for (int i = 0; i < arrayLen; i++) { - idx += insertBigDecimal(elementLen, buffer, idx, bigDecimals, i, elementType); + idx += insertBigDecimal(elementType, elementLen, buffer, idx, bigDecimals, i); } return bigDecimals; } diff --git a/src/main/java/com/esaulpaugh/headlong/abi/TupleType.java b/src/main/java/com/esaulpaugh/headlong/abi/TupleType.java index 8a511909b..52bf5684b 100644 --- a/src/main/java/com/esaulpaugh/headlong/abi/TupleType.java +++ b/src/main/java/com/esaulpaugh/headlong/abi/TupleType.java @@ -17,6 +17,7 @@ import com.esaulpaugh.headlong.util.SuperSerial; +import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.Iterator; @@ -125,31 +126,26 @@ public int validate(final Object value) { @Override void encodeTail(Object value, ByteBuffer dest) { - final Object[] values = ((Tuple) value).elements; - if(!dynamic) { - encodeHeads(elementTypes, values, dest, -1); - return; + encodeObjects(dynamic, ((Tuple) value).elements, dest, (i) -> elementTypes[i]); + } + + static void encodeObjects(boolean dynamic, Object[] values, ByteBuffer dest, IntFunction> getType) { + int nextOffset = !dynamic ? -1 : headLengthSum(values, getType); + for (int i = 0; i < values.length; i++) { + nextOffset = getType.apply(i).encodeHead(values[i], dest, nextOffset); } - final ABIType[] types = elementTypes; - encodeHeads(types, values, dest, headLengthSum(types, values)); - for (int i = 0; i < types.length; i++) { - ABIType t = types[i]; + for (int i = 0; i < values.length; i++) { + ABIType t = getType.apply(i); if(t.dynamic) { t.encodeTail(values[i], dest); } } } - private static void encodeHeads(ABIType[] types, Object[] values, ByteBuffer dest, int nextOffset) { - for (int i = 0; i < types.length; i++) { - nextOffset = types[i].encodeHead(values[i], dest, nextOffset); - } - } - - private static int headLengthSum(ABIType[] types, Object[] elements) { + private static int headLengthSum(Object[] elements, IntFunction> getType) { int sum = 0; - for (int i = 0; i < types.length; i++) { - ABIType type = types[i]; + for (int i = 0; i < elements.length; i++) { + ABIType type = getType.apply(i); sum += !type.dynamic ? type.byteLength(elements[i]) : OFFSET_LENGTH_BYTES; } return sum; @@ -163,6 +159,43 @@ Tuple decode(ByteBuffer bb, byte[] unitBuffer) { return new Tuple(elements); } + private static int[] decodeHeads(int len, ByteBuffer bb, byte[] unitBuffer, Object[] elements, IntFunction> getType) { + final int[] offsets = new int[len]; + for(int i = 0; i < len; i++) { + ABIType t = getType.apply(i); + if(!t.dynamic) { + elements[i] = t.decode(bb, unitBuffer); + } else { + offsets[i] = Encoding.UINT31.decode(bb, unitBuffer); + } + } + return offsets; + } + + static void decodeObjects(int len, ByteBuffer bb, byte[] unitBuffer, Object[] elements, IntFunction> getType) { + final int start = bb.position(); // save this value before offsets are decoded + final int[] offsets = decodeHeads(len, bb, unitBuffer, elements, getType); + for (int i = 0; i < len; i++) { + final int offset = offsets[i]; + if(offset > 0) { + final int jump = start + offset; + final int pos = bb.position(); + if(jump != pos) { + /* LENIENT MODE; see https://github.com/ethereum/solidity/commit/3d1ca07e9b4b42355aa9be5db5c00048607986d1 */ + if(jump < pos) { + throw new IllegalArgumentException("illegal backwards jump: (" + start + "+" + offset + "=" + jump + ")<" + pos); + } + bb.position(jump); // leniently jump to specified offset + } + try { + elements[i] = getType.apply(i).decode(bb, unitBuffer); + } catch (BufferUnderflowException bue) { + throw new IllegalArgumentException(bue); + } + } + } + } + /** * Parses RLP Object {@link com.esaulpaugh.headlong.rlp.util.Notation} as a {@link Tuple}. * diff --git a/src/main/java/com/esaulpaugh/headlong/abi/UnitType.java b/src/main/java/com/esaulpaugh/headlong/abi/UnitType.java index aa2d6e6d8..40d4f7cb0 100644 --- a/src/main/java/com/esaulpaugh/headlong/abi/UnitType.java +++ b/src/main/java/com/esaulpaugh/headlong/abi/UnitType.java @@ -70,15 +70,9 @@ public int validate(Object value) { return UNIT_LENGTH_BYTES; } - @Override - int encodeHead(Object value, ByteBuffer dest, int nextOffset) { - Encoding.insertInt(((Number) value).longValue(), dest); - return nextOffset; - } - @Override void encodeTail(Object value, ByteBuffer dest) { - encodeHead(value, dest, 0); + Encoding.insertInt(((Number) value).longValue(), dest); } final void validatePrimitive(long longVal) { diff --git a/src/test/java/com/esaulpaugh/headlong/abi/ABIJSONTest.java b/src/test/java/com/esaulpaugh/headlong/abi/ABIJSONTest.java index a7ee0a8fd..ec2a4eece 100644 --- a/src/test/java/com/esaulpaugh/headlong/abi/ABIJSONTest.java +++ b/src/test/java/com/esaulpaugh/headlong/abi/ABIJSONTest.java @@ -30,6 +30,7 @@ import static com.esaulpaugh.headlong.abi.ABIType.TYPE_CODE_TUPLE; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNull; @@ -279,33 +280,36 @@ public void testToJson() { } @Test - public void testParseFunction() { + public void testParseFunctionA() { + final Function f = Function.fromJson(FUNCTION_A_JSON); + final TupleType in = f.getParamTypes(); + final TupleType out = f.getOutputTypes(); + final ABIType out0 = out.get(0); - Function f; + System.out.println(f.getName() + " : " + f.getCanonicalSignature() + " : " + out0); + assertEquals(1, in.elementTypes.length); + assertEquals(1, out.elementTypes.length); - f = Function.fromJson(FUNCTION_A_JSON); - System.out.println(f.getName() + " : " + f.getCanonicalSignature() + " : " + f.getOutputTypes().get(0)); - assertEquals(1, f.getParamTypes().elementTypes.length); assertEquals("foo((decimal,decimal)[][])", f.getCanonicalSignature()); - assertEquals(1, f.getOutputTypes().elementTypes.length); - assertEquals("uint64", f.getOutputTypes().get(0).canonicalType); + assertEquals("uint64", out0.getCanonicalType()); + + assertFalse(out0.isDynamic()); assertNull(f.getStateMutability()); f.encodeCallWithArgs((Object) new Tuple[][] { new Tuple[] { new Tuple(new BigDecimal(BigInteger.ONE, 10), new BigDecimal(BigInteger.TEN, 10)) } }); - printTupleType(f.getParamTypes()); - - printTupleType(f.getOutputTypes()); + printTupleType(in); + printTupleType(out); + } - f = Function.fromJson(FUNCTION_B_JSON, Function.newDefaultDigest()); + @Test + public void testParseFunctionB() { + final Function f = Function.fromJson(FUNCTION_B_JSON, Function.newDefaultDigest()); System.out.println(f.getName() + " : " + f.getCanonicalSignature()); assertEquals(TupleType.EMPTY, f.getOutputTypes()); assertEquals("func((decimal,fixed128x18),fixed128x18[],(uint256,int256[],(int8,uint40)[]))", f.getCanonicalSignature()); assertEquals("view", f.getStateMutability()); printTupleType(f.getParamTypes()); - - Function f2 = Function.fromJson("{\"name\": \"foo\", \"type\": \"function\", \"inputs\": [ {\"name\": \"complex_nums\", \"type\": \"tuple[]\", \"components\": [ {\"name\": \"real\", \"type\": \"decimal\"}, {\"name\": \"imaginary\", \"type\": \"decimal\"} ]} ]}"); - System.out.println(f2.toJson(false)); } @Test