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
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ jobs:
uses: hiero-ledger/hiero-solo-action@dd0048139ef1e40fd6067f01bf94eb42a67294f4 # v0.15
with:
installMirrorNode: true
hieroVersion: v0.69.0-alpha.1
hieroVersion: v0.70

- name: Build SDK
run: ./gradlew assemble
Expand Down Expand Up @@ -302,7 +302,7 @@ jobs:
uses: hiero-ledger/hiero-solo-action@dd0048139ef1e40fd6067f01bf94eb42a67294f4 # v0.15
with:
installMirrorNode: true
hieroVersion: v0.69.0-alpha.1
hieroVersion: v0.70

- name: Build SDK
run: ./gradlew assemble
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public byte[] getEthereumData() {
}

/**
* Sets the raw Ethereum transaction (RLP encoded type 0, 1, and 2). Complete
* Sets the raw Ethereum transaction (RLP encoded type 0, 1, 2 and 4). Complete
* unless the callDataFileId is set.
*
* @param ethereumData raw ethereum transaction bytes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,13 @@ static EthereumTransactionData fromBytes(byte[] bytes) {
if (rlpItem.isList()) {
return EthereumTransactionDataLegacy.fromBytes(bytes);
} else {
return EthereumTransactionDataEip1559.fromBytes(bytes);
var typeByte = rlpItem.asByte();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I other Java repositories we decided to not use var since it makes the code often less readable. We should do the same in the SDK.


return switch (typeByte) {
case 0x02 -> EthereumTransactionDataEip1559.fromBytes(bytes);
case 0x04 -> EthereumTransactionDataEip7702.fromBytes(bytes);
default -> throw new IllegalArgumentException("rlp type byte " + typeByte + "is not supported");
};
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
// SPDX-License-Identifier: Apache-2.0
package com.hedera.hashgraph.sdk;

import com.esaulpaugh.headlong.rlp.RLPDecoder;
import com.esaulpaugh.headlong.rlp.RLPEncoder;
import com.esaulpaugh.headlong.rlp.RLPItem;
import com.esaulpaugh.headlong.util.Integers;
import com.google.common.base.MoreObjects;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.bouncycastle.util.encoders.Hex;

/**
* The ethereum transaction data, in the format defined in
* <a href="https://eips.ethereum.org/EIPS/eip-7702">EIP-7702</a>
*/
public class EthereumTransactionDataEip7702 extends EthereumTransactionData {

/**
* ID of the chain.
*/
public byte[] chainId;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we do not have any setters for all the fields we should make them final. If all fields are final we can convert the class to a record


/**
* Transaction's nonce.
*/
public byte[] nonce;

/**
* An 'optional' additional fee in Ethereum that is paid directly to miners in order to incentivize them to include
* your transaction in a block. Not used in Hedera.
*/
public byte[] maxPriorityGas;

/**
* The maximum amount, in tinybars, that the payer of the hedera transaction is willing to pay to complete the
* transaction.
*/
public byte[] maxGas;

/**
* The amount of gas available for the transaction.
*/
public byte[] gasLimit;

/**
* The receiver of the transaction.
*/
public byte[] to;

/**
* The transaction value.
*/
public byte[] value;

/**
* Specifies an array of addresses and storage keys that the transaction plans to access.
*/
public List<byte[]> accessList;

/**
* The list of delegation authorizations.
*/
public List<AuthorizationTuple> authorizationList;

/**
* Recovery parameter used to ease the signature verification.
*/
public byte[] recoveryId;

/**
* The R value of the signature.
*/
public byte[] r;

/**
* The S value of the signature.
*/
public byte[] s;

EthereumTransactionDataEip7702(
HeaderData headerData,
byte[] callData,
List<byte[]> accessList,
List<AuthorizationTuple> authorizationList,
SignatureData signatureData) {
super(callData);

this.chainId = headerData.chainId();
this.nonce = headerData.nonce();
this.maxPriorityGas = headerData.maxPriorityGas();
this.maxGas = headerData.maxGas();
this.gasLimit = headerData.gasLimit();
this.to = headerData.to();
this.value = headerData.value();
this.accessList = accessList;
this.authorizationList = authorizationList;
this.recoveryId = signatureData.recoveryId();
this.r = signatureData.r();
this.s = signatureData.s();
}

/**
* Convert a byte array to an ethereum transaction data.
*
* @param bytes the byte array
* @return the ethereum transaction data
*/
public static EthereumTransactionDataEip7702 fromBytes(byte[] bytes) {
var decoder = RLPDecoder.RLP_STRICT.sequenceIterator(bytes);
var rlpItem = decoder.next();

// typed transaction?
byte typeByte = rlpItem.asByte();
if (typeByte != 4) {
throw new IllegalArgumentException("rlp type byte " + typeByte + " is not supported");
}
rlpItem = decoder.next();
if (!rlpItem.isList()) {
throw new IllegalArgumentException("expected RLP element list");
}
List<RLPItem> rlpList = rlpItem.asRLPList().elements();
if (rlpList.size() != 13) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a lot of magic numbers in this code block. Can we externalize them as constants and add doc to them pls

throw new IllegalArgumentException("expected 13 RLP encoded elements, found " + rlpList.size());
}

var accessList = new ArrayList<byte[]>();
for (var accessListItem : rlpList.get(8).asRLPList().elements()) {
accessList.add(accessListItem.data());
}

var authorizationList = new ArrayList<AuthorizationTuple>();
for (var authorizationTuple : rlpList.get(9).asRLPList().elements()) {
var tupleElements = authorizationTuple.asRLPList().elements();
if (tupleElements.size() != 6) {
throw new IllegalArgumentException("invalid authorization list entry: must have 6 elements");
}
authorizationList.add(new AuthorizationTuple(
tupleElements.get(0).data(),
tupleElements.get(1).data(),
tupleElements.get(2).data(),
tupleElements.get(3).data(),
tupleElements.get(4).data(),
tupleElements.get(5).data()));
}

var headerData = new HeaderData(
rlpList.get(0).data(),
rlpList.get(1).data(),
rlpList.get(2).data(),
rlpList.get(3).data(),
rlpList.get(4).data(),
rlpList.get(5).data(),
rlpList.get(6).data());

var signatureData = new SignatureData(
rlpList.get(10).data(), rlpList.get(11).data(), rlpList.get(12).data());

return new EthereumTransactionDataEip7702(
headerData, rlpList.get(7).data(), accessList, authorizationList, signatureData);
}

public byte[] toBytes() {
List<Object> encodedAuthorizationList = new ArrayList<>();
for (var tuple : authorizationList) {
encodedAuthorizationList.add(
List.of(tuple.chainId(), tuple.address(), tuple.nonce(), tuple.yParity(), tuple.r(), tuple.s()));
}

List<Object> encodedAccessList = new ArrayList<>(accessList);

return RLPEncoder.sequence(
Integers.toBytes(0x04),
List.of(
chainId,
nonce,
maxPriorityGas,
maxGas,
gasLimit,
to,
value,
callData,
encodedAccessList,
encodedAuthorizationList,
recoveryId,
r,
s));
}

public String toString() {
return MoreObjects.toStringHelper(this)
.add("chainId", Hex.toHexString(chainId))
.add("nonce", Hex.toHexString(nonce))
.add("maxPriorityGas", Hex.toHexString(maxPriorityGas))
.add("maxGas", Hex.toHexString(maxGas))
.add("gasLimit", Hex.toHexString(gasLimit))
.add("to", Hex.toHexString(to))
.add("value", Hex.toHexString(value))
.add("callData", Hex.toHexString(callData))
.add("accessList", accessList.stream().map(Hex::toHexString).collect(Collectors.toList()))
.add(
"authorizationList",
authorizationList.stream()
.map(AuthorizationTuple::toString)
.collect(Collectors.toList()))
.add("recoveryId", Hex.toHexString(recoveryId))
.add("r", Hex.toHexString(r))
.add("s", Hex.toHexString(s))
.toString();
}

/**
* A helper record to hold core transaction fields for EIP-7702 transactions.
*/
record HeaderData(
byte[] chainId,
byte[] nonce,
byte[] maxPriorityGas,
byte[] maxGas,
byte[] gasLimit,
byte[] to,
byte[] value) {}

/**
* A helper record to hold signature data for EIP-7702 transactions.
*/
record SignatureData(byte[] recoveryId, byte[] r, byte[] s) {}

/**
* A tuple describing an authorization entry for EIP-7702 transactions.
*/
public record AuthorizationTuple(byte[] chainId, byte[] address, byte[] nonce, byte[] yParity, byte[] r, byte[] s) {
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("chainId", Hex.toHexString(chainId))
.add("address", Hex.toHexString(address))
.add("nonce", Hex.toHexString(nonce))
.add("yParity", Hex.toHexString(yParity))
.add("r", Hex.toHexString(r))
.add("s", Hex.toHexString(s))
.toString();
}
}
}
Loading