diff --git a/CHANGELOG.md b/CHANGELOG.md
index 80fa0e6c30..041ff645d8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,7 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
* bump snapshot version to 4.13.1 [#2160](https://github.com/hyperledger-web3j/web3j/pull/2160)
* Upgrade to https://github.com/Consensys/tuweni/releases/tag/v2.7.0 [#2170](https://github.com/LFDT-web3j/web3j/pull/2170)
+* Add support for code generation of custom error type introduced with `solidity v0.8.4` [#2173](https://github.com/LFDT-web3j/web3j/pull/2173)
### BREAKING CHANGES
diff --git a/abi/src/main/java/org/web3j/abi/CustomErrorEncoder.java b/abi/src/main/java/org/web3j/abi/CustomErrorEncoder.java
new file mode 100644
index 0000000000..91356072a3
--- /dev/null
+++ b/abi/src/main/java/org/web3j/abi/CustomErrorEncoder.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2019 Web3 Labs Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.web3j.abi;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.web3j.abi.datatypes.CustomError;
+import org.web3j.abi.datatypes.Type;
+import org.web3j.crypto.Hash;
+import org.web3j.utils.Numeric;
+
+/**
+ * Ethereum custom error encoding. Further limited details are available here.
+ */
+public class CustomErrorEncoder {
+
+ private CustomErrorEncoder() {}
+
+ public static String encode(CustomError error) {
+ return calculateSignatureHash(buildErrorSignature(error.getName(), error.getParameters()));
+ }
+
+ static String buildErrorSignature(
+ String errorName, List> parameters) {
+
+ StringBuilder result = new StringBuilder();
+ result.append(errorName);
+ result.append("(");
+ String params =
+ parameters.stream().map(Utils::getTypeName).collect(Collectors.joining(","));
+ result.append(params);
+ result.append(")");
+ return result.toString();
+ }
+
+ public static String calculateSignatureHash(String errorSignature) {
+ byte[] input = errorSignature.getBytes();
+ byte[] hash = Hash.sha3(input);
+ return Numeric.toHexString(hash);
+ }
+}
diff --git a/abi/src/main/java/org/web3j/abi/datatypes/CustomError.java b/abi/src/main/java/org/web3j/abi/datatypes/CustomError.java
new file mode 100644
index 0000000000..fa2dbfd764
--- /dev/null
+++ b/abi/src/main/java/org/web3j/abi/datatypes/CustomError.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2019 Web3 Labs Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.web3j.abi.datatypes;
+
+import java.util.List;
+
+import org.web3j.abi.TypeReference;
+
+import static org.web3j.abi.Utils.convert;
+
+/** CustomError wrapper type. */
+public class CustomError {
+ private String name;
+ private List> parameters;
+
+ public CustomError(String name, List> parameters) {
+ this.name = name;
+ this.parameters = convert(parameters);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public List> getParameters() {
+ return parameters;
+ }
+}
diff --git a/abi/src/test/java/org/web3j/abi/CustomErrorEncoderTest.java b/abi/src/test/java/org/web3j/abi/CustomErrorEncoderTest.java
new file mode 100644
index 0000000000..65e2e9fbfb
--- /dev/null
+++ b/abi/src/test/java/org/web3j/abi/CustomErrorEncoderTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2019 Web3 Labs Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.web3j.abi;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import org.web3j.abi.datatypes.Address;
+import org.web3j.abi.datatypes.CustomError;
+import org.web3j.abi.datatypes.DynamicArray;
+import org.web3j.abi.datatypes.Utf8String;
+import org.web3j.abi.datatypes.generated.Uint256;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.web3j.abi.Utils.convert;
+
+public class CustomErrorEncoderTest {
+ @Test
+ public void testCalculateSignatureHash() {
+ assertEquals(
+ CustomErrorEncoder.calculateSignatureHash("InvalidAccess(address,string,uint256)"),
+ ("0xcb5157bf1b439b9573ea7a95f7c00cc33f832ed728345c2bd29146ce58bbab57"));
+
+ assertEquals(
+ CustomErrorEncoder.calculateSignatureHash("RandomError(address[],bytes)"),
+ ("0xbf37b77ddf0fbbf29ee6a3ebda3d177c2d438123b10571806c57958230d9f905"));
+ }
+
+ @Test
+ public void testEncode() {
+ CustomError error =
+ new CustomError(
+ "InvalidAccess",
+ Arrays.>asList(
+ new TypeReference() {},
+ new TypeReference() {},
+ new TypeReference() {}));
+
+ assertEquals(
+ CustomErrorEncoder.encode(error),
+ "0xcb5157bf1b439b9573ea7a95f7c00cc33f832ed728345c2bd29146ce58bbab57");
+ }
+
+ @Test
+ public void testBuildErrorSignature() {
+ List> parameters =
+ Arrays.>asList(
+ new TypeReference() {},
+ new TypeReference() {},
+ new TypeReference() {});
+
+ assertEquals(
+ "InvalidAccess(address,string,uint256)",
+ CustomErrorEncoder.buildErrorSignature("InvalidAccess", convert(parameters)));
+ }
+
+ @Test
+ void testBuildErrorSignatureWithDynamicStructs() {
+ List> parameters =
+ Arrays.asList(
+ new TypeReference() {},
+ new TypeReference() {});
+
+ assertEquals(
+ "DynamicStructError((((string,string)[])[],uint256),(string,string))",
+ CustomErrorEncoder.buildErrorSignature("DynamicStructError", convert(parameters)));
+ }
+
+ @Test
+ void testBuildErrorSignatureWithDynamicArrays() {
+ List> parameters =
+ Arrays.asList(new TypeReference>() {});
+
+ assertEquals(
+ "DynamicArrayError((((string,string)[])[],uint256)[])",
+ EventEncoder.buildMethodSignature("DynamicArrayError", convert(parameters)));
+ }
+}
diff --git a/abi/src/test/java/org/web3j/abi/datatypes/CustomErrorTest.java b/abi/src/test/java/org/web3j/abi/datatypes/CustomErrorTest.java
new file mode 100644
index 0000000000..daad67d9e9
--- /dev/null
+++ b/abi/src/test/java/org/web3j/abi/datatypes/CustomErrorTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2020 Web3 Labs Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.web3j.abi.datatypes;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import org.web3j.abi.TypeReference;
+import org.web3j.abi.datatypes.generated.Uint256;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class CustomErrorTest {
+
+ @Test
+ public void testCreation() {
+
+ List> parameters =
+ Arrays.>asList(
+ new TypeReference() {}, new TypeReference() {});
+ CustomError event = new CustomError("MyError", parameters);
+
+ assertEquals(event.getName(), "MyError");
+
+ Iterator> expectedParameter = parameters.iterator();
+ for (TypeReference> actualParameter : event.getParameters()) {
+ assertEquals(expectedParameter.next(), actualParameter);
+ }
+ }
+}
diff --git a/codegen/src/main/java/org/web3j/codegen/SolidityFunctionWrapper.java b/codegen/src/main/java/org/web3j/codegen/SolidityFunctionWrapper.java
index a27b2a8918..d95fb7c9b8 100644
--- a/codegen/src/main/java/org/web3j/codegen/SolidityFunctionWrapper.java
+++ b/codegen/src/main/java/org/web3j/codegen/SolidityFunctionWrapper.java
@@ -51,6 +51,7 @@
import org.web3j.abi.TypeReference;
import org.web3j.abi.datatypes.Address;
import org.web3j.abi.datatypes.Array;
+import org.web3j.abi.datatypes.CustomError;
import org.web3j.abi.datatypes.DynamicArray;
import org.web3j.abi.datatypes.DynamicStruct;
import org.web3j.abi.datatypes.Event;
@@ -106,6 +107,7 @@ public class SolidityFunctionWrapper extends Generator {
private static final String FUNC_NAME_PREFIX = "FUNC_";
private static final String TYPE_FUNCTION = "function";
private static final String TYPE_EVENT = "event";
+ private static final String TYPE_ERROR = "error";
private static final String TYPE_CONSTRUCTOR = "constructor";
private static final ClassName LOG = ClassName.get(Log.class);
@@ -439,6 +441,143 @@ private FieldSpec createEventDefinition(
.build();
}
+ void buildCustomErrorDefinitions(
+ @NotNull AbiDefinition abiDefinition,
+ TypeSpec.Builder classBuilder,
+ Map customErrorsOccurrences)
+ throws ClassNotFoundException {
+ if (abiDefinition.getName().isEmpty()) {
+ System.out.println(
+ "\nWarning: Blank name field found in custom error abi definition. "
+ + "No code will be generated for this abi definition.");
+ return;
+ }
+
+ List parameters = new ArrayList<>();
+ for (AbiDefinition.NamedType namedType : abiDefinition.getInputs()) {
+ final TypeName typeName;
+ if (namedType.getType().equals("tuple")) {
+ typeName = structClassNameMap.get(namedType.structIdentifier());
+ } else if (namedType.getType().startsWith("tuple")
+ && namedType.getType().contains("[")) {
+ typeName = buildStructArrayTypeName(namedType, false);
+ } else {
+ typeName = buildTypeName(namedType.getType(), useJavaPrimitiveTypes);
+ }
+ parameters.add(new NamedTypeName(namedType, typeName));
+ }
+
+ classBuilder.addField(
+ createCustomErrorDefinition(
+ abiDefinition.getName(), parameters, customErrorsOccurrences));
+ }
+
+ /**
+ * Generates code for CustomError instance definition. For example, with name of {@code
+ * InvalidAddress}, generates:
+ *
+ *
{@code
+ * public static final Event INVALIDADDRESS_ERROR = new CustomError(...);
+ * ;
+ * }
+ */
+ @NotNull
+ private FieldSpec createCustomErrorDefinition(
+ String customErrorName,
+ List parameters,
+ Map customErrorsOccurrences) {
+
+ return FieldSpec.builder(
+ CustomError.class,
+ buildCustomErrorDefinitionName(customErrorName, customErrorsOccurrences))
+ .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
+ .initializer(buildVariableLengthCustomErrorInitializer(customErrorName, parameters))
+ .build();
+ }
+
+ /**
+ * Generates code for CustomError instance. For example, with abi definition of:
+ *
+ *