项目作者: esaulpaugh

项目描述 :
High-performance Contract ABI and RLP for Ethereum
高级语言: Java
项目地址: git://github.com/esaulpaugh/headlong.git
创建时间: 2018-08-05T20:11:14Z
项目社区:https://github.com/esaulpaugh/headlong

开源协议:Apache License 2.0

下载


Maven Central
Apache License, Version 2.0, January 2004
jdk1.8+
Java CI GraalVM Maven
Gitter

Contract ABI and Recursive Length Prefix made easy for the JVM.

ABI spec: https://solidity.readthedocs.io/en/latest/abi-spec.html

RLP spec: https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp

SHA-256 (headlong-13.1.1.jar): e544a0fa0c6bf341434d9a204628ddf216a45999bd489dd41274264c06a4ef5c

Usage

ABI package

Encoding Function Calls

  1. Function baz = Function.parse("baz(uint32,bool)"); // canonicalizes and parses any signature
  2. // or
  3. Function f2 = Function.fromJson("{\"type\":\"function\",\"name\":\"foo\",\"inputs\":[{\"name\":\"complex_nums\",\"type\":\"tuple[]\",\"components\":[{\"name\":\"real\",\"type\":\"fixed168x10\"},{\"name\":\"imaginary\",\"type\":\"fixed168x10\"}]}]}");
  4. Pair<Long, Boolean> bazArgs = Tuple.of(69L, true);
  5. Tuple complexNums = Single.of(new Tuple[] { Tuple.of(new BigDecimal("0.0090000000"), new BigDecimal("1.9500000000")) });
  6. // Two equivalent styles:
  7. ByteBuffer bazCall = baz.encodeCall(bazArgs);
  8. ByteBuffer bazCall2 = baz.encodeCallWithArgs(69L, true);
  9. System.out.println("baz call hex:\n" + Strings.encode(bazCall) + "\n"); // hexadecimal encoding (without 0x prefix)
  10. Tuple recoveredArgs = baz.decodeCall(bazCall2); // decode the encoding back to the original args
  11. System.out.println("baz args:\n" + recoveredArgs + "\n"); // toString()
  12. System.out.println("equal:\n" + recoveredArgs.equals(bazArgs) + "\n"); // test for equality
  13. System.out.println("baz call debug:\n" + baz.annotateCall(bazCall.array()) + "\n"); // human-readable, for debugging function calls (expects input to start with 4-byte selector)
  14. System.out.println("baz args debug:\n" + baz.getInputs().annotate(bazArgs) + "\n"); // human-readable, for debugging encodings without a selector
  15. System.out.println("f2 call debug:\n" + f2.annotateCall(complexNums) + "\n");
  16. System.out.println("f2 args debug:\n" + f2.getInputs().annotate(complexNums));

Decoding Return Values

  1. Function foo = Function.parse("foo((fixed[],int8)[1][][5])", "(int,string)");
  2. // decode return type (int256,string)
  3. Tuple decoded = foo.decodeReturn(
  4. FastHex.decode(
  5. "000000000000000000000000000000000000000000000000000000000000002A"
  6. + "0000000000000000000000000000000000000000000000000000000000000040"
  7. + "000000000000000000000000000000000000000000000000000000000000000e"
  8. + "59616f62616e6745696768747939000000000000000000000000000000000000"
  9. )
  10. );
  11. System.out.println(decoded.equals(Tuple.of(BigInteger.valueOf(42L), "YaobangEighty9")));
  1. Function fooTwo = Function.parse("fooTwo()", "(uint8)");
  2. int returned = fooTwo.decodeSingletonReturn(FastHex.decode("00000000000000000000000000000000000000000000000000000000000000FF")); // uint8 corresponds to int
  3. System.out.println(returned);

Using TupleType

  1. TupleType<Tuple> tt = TupleType.parse("(bool,address,int72[][])");
  2. ByteBuffer b0 = tt.encode(Tuple.of(false, Address.wrap("0x52908400098527886E0F7030069857D2E4169EE7"), new BigInteger[0][]));
  3. // Tuple t = tt.decode(b0); // decode the tuple (has the side effect of advancing the ByteBuffer's position)
  4. // or...
  5. Address a = tt.decode(b0, 1); // decode only index 1
  6. System.out.println(a);
  7. Tuple t2 = tt.decode(b0, 0, 2); // decode only indices 0 and 2
  8. System.out.println(t2);
  9. ByteBuffer b1 = tt.<ABIType<BigInteger[][]>>get(2).encode(new BigInteger[][] { }); // encode only int72[][]

Misc

  1. Event<?> event = Event.fromJson("{\"type\":\"event\",\"name\":\"an_event\",\"inputs\":[{\"name\":\"a\",\"type\":\"bytes\",\"indexed\":true},{\"name\":\"b\",\"type\":\"uint256\",\"indexed\":false}],\"anonymous\":true}");
  2. Tuple args = event.decodeArgs(new byte[][] { new byte[32] }, new byte[32]);
  3. System.out.println(event);
  4. System.out.println(args);
  5. // create any type directly (advanced)
  6. ArrayType<ABIType<Object>, ?, Object> at = TypeFactory.create("(address,int)[]");
  7. ArrayType<TupleType<Tuple>, Tuple, Tuple[]> at2 = TypeFactory.create("(address,int)[]");
  8. ArrayType<TupleType<Pair<Address, BigInteger>>, Pair<Address, BigInteger>, Pair<Address, BigInteger>[]> at3 = TypeFactory.create("(address,int)[]");
  9. ABIType<Object> unknown = TypeFactory.create(at.getCanonicalType());

RLP package

  1. // for an example class Student implementing some example interface
  2. public Student(byte[] rlp) {
  3. Iterator<RLPItem> iter = RLPDecoder.RLP_STRICT.sequenceIterator(rlp);
  4. this.name = iter.next().asString(Strings.UTF_8);
  5. this.gpa = iter.next().asFloat(false);
  6. this.publicKey = iter.next().asBytes();
  7. this.balance = new BigDecimal(iter.next().asBigInt(), iter.next().asInt());
  8. }
  9. @Override
  10. public Object[] toObjectArray() {
  11. return new Object[] {
  12. // instances of byte[]
  13. Strings.decode(name, Strings.UTF_8),
  14. FloatingPoint.toBytes(gpa),
  15. publicKey,
  16. balance.unscaledValue().toByteArray(),
  17. Integers.toBytes(balance.scale())
  18. // include an Object[] or Iterable and its elements will be encoded as an RLP list (which may include other lists)
  19. };
  20. }
  21. @Override
  22. public byte[] toRLP() {
  23. return RLPEncoder.sequence(toObjectArray());
  24. }

Build

Now available in Maven Central Repository.

Or build locally:

Clone the project and install to your local maven repository using gradle publishToMavenLocal or mvn install, then declare it as a dependency:

  1. implementation("com.esaulpaugh:headlong:13.2.0-SNAPSHOT")
  1. <dependency>
  2. <groupId>com.esaulpaugh</groupId>
  3. <artifactId>headlong</artifactId>
  4. <version>13.2.0-SNAPSHOT</version>
  5. </dependency>

Alternatively:

  • Run gradle build or gradle jar which output to build/libs
  • Use mvn package which outputs to target
  • Execute ant all build-jar which outputs to build/lib
  • Add headlong as a project dependency

Benchmarks

Screenshot

temurin 1.8.0_442 on Intel Xeon Gold 6140, Ubuntu 24.10

Screenshot
graalvm-jdk-23.0.2 (aarch64 JIT) on Apple M3 Max

Command line interface

https://github.com/esaulpaugh/headlong-cli

Example Android app

https://github.com/esaulpaugh/headlong-android

Misc

Also includes optimized implementations of:

  • EIP-778 Ethereum Node Records
  • EIP-55 Mixed-case checksum address encoding
  • Keccak
  • hexadecimal

headlong depends on gson v2.1 or greater at runtime and v2.12.0 or greater at compile time. Test suite should take less than one minute to run. Test packages require junit. Jar size is ~133 KiB. Java 8+.

For better contract ABI JSON parsing performance, consider constructing an ABIParser with a Set<TypeEnum> by which to filter objects by type. For best performance, json should be compact and “type” should be the first key in functions, events, and errors. This can be done via ABIJSON.optimize(String).

See the wiki for more, such as packed encoding (and decoding) and RLP Object Notation: https://github.com/esaulpaugh/headlong/wiki

Licensed under Apache 2.0 terms