Skip to content

Commit

Permalink
EIP3668 implement ccipReadFetch
Browse files Browse the repository at this point in the history
  • Loading branch information
andrii-kl committed May 2, 2022
1 parent 29560ca commit 95b0251
Show file tree
Hide file tree
Showing 6 changed files with 219 additions and 13 deletions.
86 changes: 78 additions & 8 deletions abi/src/main/java/org/web3j/abi/datatypes/ens/OffchainLookup.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,42 @@
*/
package org.web3j.abi.datatypes.ens;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import org.web3j.abi.FunctionReturnDecoder;
import org.web3j.abi.TypeReference;
import org.web3j.abi.datatypes.Address;
import org.web3j.abi.datatypes.DynamicArray;
import org.web3j.abi.datatypes.DynamicBytes;
import org.web3j.abi.datatypes.DynamicStruct;
import org.web3j.abi.datatypes.Type;
import org.web3j.abi.datatypes.Utf8String;
import org.web3j.abi.datatypes.generated.Bytes4;
import org.web3j.utils.Numeric;

/** https://eips.ethereum.org/EIPS/eip-3668#client-lookup-protocol */
public class OffchainLookup extends DynamicStruct {

public String sender;
public List<String> urls;
public byte[] callData;
public byte[] callbackFunction;
public byte[] extraData;
private String sender;
private List<String> urls;
private byte[] callData;
private byte[] callbackFunction;
private byte[] extraData;

private static final List outputParameters = new ArrayList<TypeReference<Type>>();

static {
outputParameters.addAll(
Arrays.asList(
new TypeReference<Address>() {},
new TypeReference<DynamicArray<Utf8String>>() {},
new TypeReference<DynamicBytes>() {},
new TypeReference<Bytes4>() {},
new TypeReference<DynamicBytes>() {}));
}

public OffchainLookup(
String sender,
Expand All @@ -42,8 +60,8 @@ public OffchainLookup(
new DynamicArray<>(
Utf8String.class,
urls.stream().map(Utf8String::new).collect(Collectors.toList())),
new Bytes4(callData),
new DynamicBytes(callbackFunction),
new Bytes4(callData),
new DynamicBytes(extraData));
this.sender = sender;
this.urls = urls;
Expand All @@ -55,8 +73,8 @@ public OffchainLookup(
public OffchainLookup(
Address sender,
DynamicArray<Utf8String> urls,
Bytes4 callData,
DynamicBytes callbackFunction,
DynamicBytes callData,
Bytes4 callbackFunction,
DynamicBytes extraData) {
super(sender, urls, callData, callbackFunction, extraData);
this.sender = sender.getValue();
Expand All @@ -65,4 +83,56 @@ public OffchainLookup(
this.callbackFunction = callbackFunction.getValue();
this.extraData = extraData.getValue();
}

public static OffchainLookup build(byte[] bytes) {
List<Type> resultList =
FunctionReturnDecoder.decode(Numeric.toHexString(bytes), outputParameters);

return new OffchainLookup(
(Address) resultList.get(0),
(DynamicArray<Utf8String>) resultList.get(1),
(DynamicBytes) resultList.get(2),
(Bytes4) resultList.get(3),
(DynamicBytes) resultList.get(4));
}

public String getSender() {
return sender;
}

public void setSender(String sender) {
this.sender = sender;
}

public List<String> getUrls() {
return urls;
}

public void setUrls(List<String> urls) {
this.urls = urls;
}

public byte[] getCallData() {
return callData;
}

public void setCallData(byte[] callData) {
this.callData = callData;
}

public byte[] getCallbackFunction() {
return callbackFunction;
}

public void setCallbackFunction(byte[] callbackFunction) {
this.callbackFunction = callbackFunction;
}

public byte[] getExtraData() {
return extraData;
}

public void setExtraData(byte[] extraData) {
this.extraData = extraData;
}
}
110 changes: 107 additions & 3 deletions core/src/main/java/org/web3j/ens/EnsResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,21 @@
*/
package org.web3j.ens;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.web3j.abi.datatypes.ens.OffchainLookup;
import org.web3j.crypto.Keys;
import org.web3j.crypto.WalletUtils;
import org.web3j.ens.contracts.generated.ENS;
Expand All @@ -29,9 +44,13 @@
import org.web3j.utils.Numeric;
import org.web3j.utils.Strings;

import static org.web3j.service.HSMHTTPRequestProcessor.JSON;

/** Resolution logic for contract addresses. According to https://eips.ethereum.org/EIPS/eip-2544 */
public class EnsResolver {

private static final Logger log = LoggerFactory.getLogger(EnsResolver.class);

public static final long DEFAULT_SYNC_THRESHOLD = 1000 * 60 * 3;
public static final String REVERSE_NAME_SUFFIX = ".addr.reverse";

Expand Down Expand Up @@ -130,11 +149,35 @@ public String resolve(String ensName) {

if (supportWildcard) {
String callData = resolver.addr(nameHash).encodeFunctionCall();

byte[] result =
String resultHex =
resolver.resolve(nameHash, Numeric.hexStringToByteArray(callData))
.send();
return Numeric.toHexString(result);

if (EnsUtils.EIP_3668_CCIP_INTERFACE_ID.equals(resultHex.substring(0, 10))) {

OffchainLookup offchainLookup =
OffchainLookup.build(
Numeric.hexStringToByteArray(resultHex.substring(10)));

// TODO Check the sender of the OffchainLookup matches the transaction
// if(offchainLookup.getSender() !== to) {
// throw new Error("Cannot handle OffchainLookup
// raised inside nested call");
// }

String gatewayResult =
ccipReadFetch(
offchainLookup.getUrls(),
offchainLookup.getSender(),
Numeric.toHexString(offchainLookup.getCallData()));

if (gatewayResult == null) {
log.warn("CCIP Read disabled or provided no URLs.");
return null;
}
}

return resultHex;
} else {
try {
contractAddress = resolver.addr(nameHash).send();
Expand All @@ -158,6 +201,67 @@ public String resolve(String ensName) {
}
}

private String ccipReadFetch(List<String> urls, String sender, String data) {
OkHttpClient client = new OkHttpClient();
List<String> errorMessages = new ArrayList<>();

for (String url : urls) {
// URL expansion
String href = url.replace("{sender}", sender).replace("{data}", data);

Request request;
Request.Builder builder = new Request.Builder().url(href);

if (url.contains("{data}")) {
request = builder.get().build();
} else {
request =
builder.post(RequestBody.create(data, JSON))
.addHeader("Content-Type", "application/json")
.build();
}

try (okhttp3.Response response = client.newCall(request).execute()) {
ResponseBody responseBody = response.body();
if (response.isSuccessful()) {
if (responseBody != null) {
String result =
new BufferedReader(new InputStreamReader(responseBody.byteStream()))
.lines()
.collect(Collectors.joining("\n"));

return result;
} else {
return null;
}
} else {
int statusCode = response.code();
// 4xx indicates the result is not present; stop
if (statusCode >= 400 && statusCode < 500) {
log.error(
"Response error during CCIP fetch: url {}, error: {}",
url,
response.message());
return null;
}

// 5xx indicates server issue; try the next url
errorMessages.add(response.message());

log.warn(
"Response error 500 during CCIP fetch: url {}, error: {}",
url,
response.message());
}
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
;

return null;
};

/**
* Reverse name resolution as documented in the <a
* href="https://docs.ens.domains/contract-api-reference/reverseregistrar">specification</a>.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import io.reactivex.Flowable;
import io.reactivex.functions.Function;

import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
Expand All @@ -15,6 +17,7 @@
import org.web3j.abi.datatypes.Event;
import org.web3j.abi.datatypes.Type;
import org.web3j.abi.datatypes.Utf8String;
import org.web3j.abi.datatypes.ens.OffchainLookup;
import org.web3j.abi.datatypes.generated.Bytes32;
import org.web3j.crypto.Credentials;
import org.web3j.protocol.Web3j;
Expand All @@ -27,6 +30,7 @@
import org.web3j.tx.Contract;
import org.web3j.tx.TransactionManager;
import org.web3j.tx.gas.ContractGasProvider;
import org.web3j.utils.Numeric;

/**
* <p>Auto generated code.
Expand Down Expand Up @@ -116,12 +120,13 @@ public RemoteFunctionCall<byte[]> makeSignatureHash(String target, BigInteger ex
return executeRemoteCallSingleValueReturn(function, byte[].class);
}

public RemoteFunctionCall<byte[]> resolve(byte[] name, byte[] data) {
public RemoteFunctionCall<String> resolve(byte[] name, byte[] data) {
final org.web3j.abi.datatypes.Function function = new org.web3j.abi.datatypes.Function(FUNC_RESOLVE,
Arrays.<Type>asList(new org.web3j.abi.datatypes.DynamicBytes(name),
new org.web3j.abi.datatypes.DynamicBytes(data)),
Arrays.<TypeReference<?>>asList(new TypeReference<DynamicBytes>() {}));
return executeRemoteCallSingleValueReturn(function, byte[].class);

return new RemoteFunctionCall<>(function, () -> Numeric.cleanString(executeCallWithoutDecoding(function)));
}

public RemoteFunctionCall<byte[]> resolveWithProof(byte[] response, byte[] extraData) {
Expand Down
5 changes: 5 additions & 0 deletions core/src/main/java/org/web3j/tx/Contract.java
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,11 @@ private List<Type> executeCall(Function function) throws IOException {
return FunctionReturnDecoder.decode(value, function.getOutputParameters());
}

protected String executeCallWithoutDecoding(Function function) throws IOException {
String encodedFunction = FunctionEncoder.encode(function);
return call(contractAddress, encodedFunction, defaultBlockParameter);
}

@SuppressWarnings("unchecked")
protected <T extends Type> T executeCallSingleValueReturn(Function function)
throws IOException {
Expand Down
1 change: 1 addition & 0 deletions core/src/main/java/org/web3j/utils/EnsUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public class EnsUtils {

// Wildcard resolution
public static final byte[] ENSIP_10_INTERFACE_ID = Numeric.hexStringToByteArray("0x9061b923");
public static final String EIP_3668_CCIP_INTERFACE_ID = "0x556f1830";

public static boolean isAddressEmpty(String address) {
return EMPTY_ADDRESS.equals(address);
Expand Down
21 changes: 21 additions & 0 deletions utils/src/main/java/org/web3j/utils/Numeric.java
Original file line number Diff line number Diff line change
Expand Up @@ -253,4 +253,25 @@ public static byte asByte(int m, int n) {
public static boolean isIntegerValue(BigDecimal value) {
return value.signum() == 0 || value.scale() <= 0 || value.stripTrailingZeros().scale() <= 0;
}

public static String hexToAscii(String hexStr) {
String hex = cleanHexPrefix(cleanString(hexStr));

StringBuilder output = new StringBuilder();

for (int i = 0; i < hex.length(); i += 2) {
String str = hex.substring(i, i + 2);
output.append((char) Integer.parseInt(str, 16));
}

return output.toString();
}

public static String cleanString(String string) {
if (string != null) {
return string.replace("\"", "");
} else {
return null;
}
}
}

0 comments on commit 95b0251

Please sign in to comment.