https://github.com/esaulpaugh/headlong
High-performance Contract ABI and RLP for Ethereum
https://github.com/esaulpaugh/headlong
abi blockchain codec contract contract-abi encoding enr ether ethereum java rlp
Last synced: 15 days ago
JSON representation
High-performance Contract ABI and RLP for Ethereum
- Host: GitHub
- URL: https://github.com/esaulpaugh/headlong
- Owner: esaulpaugh
- License: apache-2.0
- Created: 2018-08-05T20:11:14.000Z (over 6 years ago)
- Default Branch: master
- Last Pushed: 2024-10-29T08:06:51.000Z (6 months ago)
- Last Synced: 2024-10-29T09:24:25.522Z (6 months ago)
- Topics: abi, blockchain, codec, contract, contract-abi, encoding, enr, ether, ethereum, java, rlp
- Language: Java
- Homepage:
- Size: 7.36 MB
- Stars: 79
- Watchers: 10
- Forks: 20
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
- Code of conduct: .github/CODE_OF_CONDUCT.md
Awesome Lists containing this project
- DeFi-Developer-Road-Map - headlong - type-safe Contract ABI and Recursive Length Prefix library in Java (Roadmap)
README
[](https://central.sonatype.com/artifact/com.esaulpaugh/headlong)
[](https://www.apache.org/licenses/LICENSE-2.0)
[](https://openjdk.java.net/)
[](https://github.com/esaulpaugh/headlong/actions/workflows/graalvm.yml)
[](https://gitter.im/esaulpaugh-headlong/community)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
```java
Function baz = Function.parse("baz(uint32,bool)"); // canonicalizes and parses any signature
// or
Function f2 = Function.fromJson("{\"type\":\"function\",\"name\":\"foo\",\"inputs\":[{\"name\":\"complex_nums\",\"type\":\"tuple[]\",\"components\":[{\"name\":\"real\",\"type\":\"fixed168x10\"},{\"name\":\"imaginary\",\"type\":\"fixed168x10\"}]}]}");Pair bazArgs = Tuple.of(69L, true);
Tuple complexNums = Single.of(new Tuple[] { Tuple.of(new BigDecimal("0.0090000000"), new BigDecimal("1.9500000000")) });// Two equivalent styles:
ByteBuffer bazCall = baz.encodeCall(bazArgs);
ByteBuffer bazCall2 = baz.encodeCallWithArgs(69L, true);System.out.println("baz call hex:\n" + Strings.encode(bazCall) + "\n"); // hexadecimal encoding (without 0x prefix)
Tuple recoveredArgs = baz.decodeCall(bazCall2); // decode the encoding back to the original args
System.out.println("baz args:\n" + recoveredArgs + "\n"); // toString()
System.out.println("equal:\n" + recoveredArgs.equals(bazArgs) + "\n"); // test for equalitySystem.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)
System.out.println("baz args debug:\n" + baz.getInputs().annotate(bazArgs) + "\n"); // human-readable, for debugging encodings without a selector
System.out.println("f2 call debug:\n" + f2.annotateCall(complexNums) + "\n");
System.out.println("f2 args debug:\n" + f2.getInputs().annotate(complexNums));
```#### Decoding Return Values
```java
Function foo = Function.parse("foo((fixed[],int8)[1][][5])", "(int,string)");// decode return type (int256,string)
Tuple decoded = foo.decodeReturn(
FastHex.decode(
"000000000000000000000000000000000000000000000000000000000000002A"
+ "0000000000000000000000000000000000000000000000000000000000000040"
+ "000000000000000000000000000000000000000000000000000000000000000e"
+ "59616f62616e6745696768747939000000000000000000000000000000000000"
)
);System.out.println(decoded.equals(Tuple.of(BigInteger.valueOf(42L), "YaobangEighty9")));
``````java
Function fooTwo = Function.parse("fooTwo()", "(uint8)");
int returned = fooTwo.decodeSingletonReturn(FastHex.decode("00000000000000000000000000000000000000000000000000000000000000FF")); // uint8 corresponds to int
System.out.println(returned);
```#### Using TupleType
```java
TupleType tt = TupleType.parse("(bool,address,int72[][])");
ByteBuffer b0 = tt.encode(Tuple.of(false, Address.wrap("0x52908400098527886E0F7030069857D2E4169EE7"), new BigInteger[0][]));
// Tuple t = tt.decode(b0); // decode the tuple (has the side effect of advancing the ByteBuffer's position)
// or...
Address a = tt.decode(b0, 1); // decode only index 1
System.out.println(a);
Tuple t2 = tt.decode(b0, 0, 2); // decode only indices 0 and 2
System.out.println(t2);ByteBuffer b1 = tt.>get(2).encode(new BigInteger[][] { }); // encode only int72[][]
```#### Misc
```java
Event> event = Event.fromJson("{\"type\":\"event\",\"name\":\"an_event\",\"inputs\":[{\"name\":\"a\",\"type\":\"bytes\",\"indexed\":true},{\"name\":\"b\",\"type\":\"uint256\",\"indexed\":false}],\"anonymous\":true}");
Tuple args = event.decodeArgs(new byte[][] { new byte[32] }, new byte[32]);
System.out.println(event);
System.out.println(args);// create any type directly (advanced)
, Pair[]> at3 = TypeFactory.create("(address,int)[]");
ArrayType, ?, Object> at = TypeFactory.create("(address,int)[]");
ArrayType, Tuple, Tuple[]> at2 = TypeFactory.create("(address,int)[]");
ArrayType>, Pair
ABIType unknown = TypeFactory.create(at.getCanonicalType());
```### RLP package
```java
// for an example class Student implementing some example interface
public Student(byte[] rlp) {
Iterator iter = RLPDecoder.RLP_STRICT.sequenceIterator(rlp);
this.name = iter.next().asString(Strings.UTF_8);
this.gpa = iter.next().asFloat(false);
this.publicKey = iter.next().asBytes();
this.balance = new BigDecimal(iter.next().asBigInt(), iter.next().asInt());
}@Override
public Object[] toObjectArray() {
return new Object[] {
// instances of byte[]
Strings.decode(name, Strings.UTF_8),
FloatingPoint.toBytes(gpa),
publicKey,
balance.unscaledValue().toByteArray(),
Integers.toBytes(balance.scale())
// include an Object[] or Iterable and its elements will be encoded as an RLP list (which may include other lists)
};
}@Override
public byte[] toRLP() {
return RLPEncoder.sequence(toObjectArray());
}
```## 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:
```kotlin
implementation("com.esaulpaugh:headlong:13.2.0-SNAPSHOT")
``````xml
com.esaulpaugh
headlong
13.2.0-SNAPSHOT```
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

temurin 1.8.0_442 on Intel Xeon Gold 6140, Ubuntu 24.10

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
* hexadecimalheadlong 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` 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