Added CCIP support to provider.call (#2478).
This commit is contained in:
parent
33a029e457
commit
ae23bb76a9
@ -34,6 +34,7 @@ export type TransactionRequest = {
|
||||
maxFeePerGas?: BigNumberish;
|
||||
|
||||
customData?: Record<string, any>;
|
||||
ccipReadEnabled?: boolean;
|
||||
}
|
||||
|
||||
export interface TransactionResponse extends Transaction {
|
||||
|
@ -10,7 +10,7 @@ import { version } from "./_version";
|
||||
const logger = new Logger(version);
|
||||
|
||||
const allowedTransactionKeys: Array<string> = [
|
||||
"accessList", "chainId", "customData", "data", "from", "gasLimit", "gasPrice", "maxFeePerGas", "maxPriorityFeePerGas", "nonce", "to", "type", "value"
|
||||
"accessList", "ccipReadEnabled", "chainId", "customData", "data", "from", "gasLimit", "gasPrice", "maxFeePerGas", "maxPriorityFeePerGas", "nonce", "to", "type", "value"
|
||||
];
|
||||
|
||||
const forwardErrors = [
|
||||
|
@ -23,6 +23,7 @@ export interface Overrides {
|
||||
type?: number;
|
||||
accessList?: AccessListish;
|
||||
customData?: Record<string, any>;
|
||||
ccipReadEnabled?: boolean;
|
||||
};
|
||||
|
||||
export interface PayableOverrides extends Overrides {
|
||||
@ -58,6 +59,7 @@ export interface PopulatedTransaction {
|
||||
maxPriorityFeePerGas?: BigNumber;
|
||||
|
||||
customData?: Record<string, any>;
|
||||
ccipReadEnabled?: boolean;
|
||||
};
|
||||
|
||||
export type EventFilter = {
|
||||
@ -110,7 +112,8 @@ const allowedTransactionKeys: { [ key: string ]: boolean } = {
|
||||
chainId: true, data: true, from: true, gasLimit: true, gasPrice:true, nonce: true, to: true, value: true,
|
||||
type: true, accessList: true,
|
||||
maxFeePerGas: true, maxPriorityFeePerGas: true,
|
||||
customData: true
|
||||
customData: true,
|
||||
ccipReadEnabled: true
|
||||
}
|
||||
|
||||
async function resolveName(resolver: Signer | Provider, nameOrPromise: string | Promise<string>): Promise<string> {
|
||||
@ -274,6 +277,10 @@ async function populateTransaction(contract: Contract, fragment: FunctionFragmen
|
||||
tx.customData = shallowCopy(ro.customData);
|
||||
}
|
||||
|
||||
if (ro.ccipReadEnabled) {
|
||||
tx.ccipReadEnabled = !!ro.ccipReadEnabled;
|
||||
}
|
||||
|
||||
// Remove the overrides
|
||||
delete overrides.nonce;
|
||||
delete overrides.gasLimit;
|
||||
@ -288,6 +295,7 @@ async function populateTransaction(contract: Contract, fragment: FunctionFragmen
|
||||
delete overrides.maxPriorityFeePerGas;
|
||||
|
||||
delete overrides.customData;
|
||||
delete overrides.ccipReadEnabled;
|
||||
|
||||
// Make sure there are no stray overrides, which may indicate a
|
||||
// typo or using an unsupported key.
|
||||
|
@ -6,7 +6,7 @@ import {
|
||||
} from "@ethersproject/abstract-provider";
|
||||
import { Base58 } from "@ethersproject/basex";
|
||||
import { BigNumber, BigNumberish } from "@ethersproject/bignumber";
|
||||
import { arrayify, concat, hexConcat, hexDataLength, hexDataSlice, hexlify, hexValue, hexZeroPad, isHexString } from "@ethersproject/bytes";
|
||||
import { arrayify, BytesLike, concat, hexConcat, hexDataLength, hexDataSlice, hexlify, hexValue, hexZeroPad, isHexString } from "@ethersproject/bytes";
|
||||
import { HashZero } from "@ethersproject/constants";
|
||||
import { dnsEncode, namehash } from "@ethersproject/hash";
|
||||
import { getNetwork, Network, Networkish } from "@ethersproject/networks";
|
||||
@ -24,6 +24,8 @@ const logger = new Logger(version);
|
||||
|
||||
import { Formatter } from "./formatter";
|
||||
|
||||
const MAX_CCIP_REDIRECTS = 10;
|
||||
|
||||
//////////////////////////////
|
||||
// Event Serializeing
|
||||
|
||||
@ -250,18 +252,19 @@ const matchers = [
|
||||
new RegExp("^eip155:[0-9]+/(erc[0-9]+):(.*)$", "i"),
|
||||
];
|
||||
|
||||
function _parseString(result: string): null | string {
|
||||
function _parseString(result: string, start: number): null | string {
|
||||
try {
|
||||
return toUtf8String(_parseBytes(result));
|
||||
return toUtf8String(_parseBytes(result, start));
|
||||
} catch(error) { }
|
||||
return null;
|
||||
}
|
||||
|
||||
function _parseBytes(result: string): null | string {
|
||||
function _parseBytes(result: string, start: number): null | string {
|
||||
if (result === "0x") { return null; }
|
||||
|
||||
const offset = BigNumber.from(hexDataSlice(result, 0, 32)).toNumber();
|
||||
const offset = BigNumber.from(hexDataSlice(result, start, start + 32)).toNumber();
|
||||
const length = BigNumber.from(hexDataSlice(result, offset, offset + 32)).toNumber();
|
||||
|
||||
return hexDataSlice(result, offset + 32, offset + 32 + length);
|
||||
}
|
||||
|
||||
@ -295,6 +298,32 @@ function bytesPad(value: Uint8Array): Uint8Array {
|
||||
return result;
|
||||
}
|
||||
|
||||
// ABI Encodes a series of (bytes, bytes, ...)
|
||||
function encodeBytes(datas: Array<BytesLike>) {
|
||||
const result: Array<Uint8Array> = [ ];
|
||||
|
||||
let byteCount = 0;
|
||||
|
||||
// Add place-holders for pointers as we add items
|
||||
for (let i = 0; i < datas.length; i++) {
|
||||
result.push(null);
|
||||
byteCount += 32;
|
||||
}
|
||||
|
||||
for (let i = 0; i < datas.length; i++) {
|
||||
const data = arrayify(datas[i]);
|
||||
|
||||
// Update the bytes offset
|
||||
result[i] = numPad(byteCount);
|
||||
|
||||
// The length and padded value of data
|
||||
result.push(numPad(data.length));
|
||||
result.push(bytesPad(data));
|
||||
byteCount += 32 + Math.ceil(data.length / 32) * 32;
|
||||
}
|
||||
|
||||
return hexConcat(result);
|
||||
}
|
||||
|
||||
export class Resolver implements EnsResolver {
|
||||
readonly provider: BaseProvider;
|
||||
@ -346,41 +375,13 @@ export class Resolver implements EnsResolver {
|
||||
if (await this.supportsWildcard()) {
|
||||
parseBytes = true;
|
||||
|
||||
const p0 = arrayify(dnsEncode(this.name));
|
||||
const p1 = arrayify(tx.data);
|
||||
|
||||
// selector("resolve(bytes,bytes)")
|
||||
const bytes: Array<string | Uint8Array> = [ "0x9061b923" ];
|
||||
let byteCount = 0;
|
||||
|
||||
// Place-holder pointer to p0
|
||||
const placeHolder0 = bytes.length;
|
||||
bytes.push("0x");
|
||||
byteCount += 32;
|
||||
|
||||
// Place-holder pointer to p1
|
||||
const placeHolder1 = bytes.length;
|
||||
bytes.push("0x");
|
||||
byteCount += 32;
|
||||
|
||||
// The length and padded value of p0
|
||||
bytes[placeHolder0] = numPad(byteCount);
|
||||
bytes.push(numPad(p0.length));
|
||||
bytes.push(bytesPad(p0));
|
||||
byteCount += 32 + Math.ceil(p0.length / 32) * 32;
|
||||
|
||||
// The length and padded value of p0
|
||||
bytes[placeHolder1] = numPad(byteCount);
|
||||
bytes.push(numPad(p1.length));
|
||||
bytes.push(bytesPad(p1));
|
||||
byteCount += 32 + Math.ceil(p1.length / 32) * 32;
|
||||
|
||||
tx.data = hexConcat(bytes);
|
||||
tx.data = hexConcat([ "0x9061b923", encodeBytes([ dnsEncode(this.name), tx.data ]) ]);
|
||||
}
|
||||
|
||||
try {
|
||||
let result = await this.provider.call(tx);
|
||||
if (parseBytes) { result = _parseBytes(result); }
|
||||
if (parseBytes) { result = _parseBytes(result, 0); }
|
||||
return result;
|
||||
} catch (error) {
|
||||
if (error.code === Logger.errors.CALL_EXCEPTION) { return null; }
|
||||
@ -390,7 +391,7 @@ export class Resolver implements EnsResolver {
|
||||
|
||||
async _fetchBytes(selector: string, parameters?: string): Promise<null | string> {
|
||||
const result = await this._fetch(selector, parameters);
|
||||
if (result != null) { return _parseBytes(result); }
|
||||
if (result != null) { return _parseBytes(result, 0); }
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -561,7 +562,7 @@ export class Resolver implements EnsResolver {
|
||||
data: hexConcat([ selector, tokenId ])
|
||||
};
|
||||
|
||||
let metadataUrl = _parseString(await this.provider.call(tx))
|
||||
let metadataUrl = _parseString(await this.provider.call(tx), 0);
|
||||
if (metadataUrl == null) { return null; }
|
||||
linkage.push({ type: "metadata-url-base", content: metadataUrl });
|
||||
|
||||
@ -700,6 +701,8 @@ export class BaseProvider extends Provider implements EnsProvider {
|
||||
|
||||
readonly anyNetwork: boolean;
|
||||
|
||||
disableCcipRead: boolean;
|
||||
|
||||
|
||||
/**
|
||||
* ready
|
||||
@ -721,6 +724,8 @@ export class BaseProvider extends Provider implements EnsProvider {
|
||||
|
||||
this._emitted = { block: -2 };
|
||||
|
||||
this.disableCcipRead = false;
|
||||
|
||||
this.formatter = new.target.getFormatter();
|
||||
|
||||
// If network is any, this Provider allows the underlying
|
||||
@ -822,6 +827,46 @@ export class BaseProvider extends Provider implements EnsProvider {
|
||||
return getNetwork((network == null) ? "homestead": network);
|
||||
}
|
||||
|
||||
async ccipReadFetch(tx: Transaction, calldata: string, urls: Array<string>): Promise<null | string> {
|
||||
if (this.disableCcipRead || urls.length === 0) { return null; }
|
||||
|
||||
const sender = (tx.from || "0x0000000000000000000000000000000000000000").toLowerCase();
|
||||
const data = calldata.toLowerCase();
|
||||
|
||||
const errorMessages: Array<string> = [ ];
|
||||
|
||||
for (let i = 0; i < urls.length; i++) {
|
||||
const url = urls[i];
|
||||
|
||||
// URL expansion
|
||||
const href = url.replace("{sender}", sender).replace("{data}", data);
|
||||
|
||||
// If no {data} is present, use POST; otherwise GET
|
||||
const json: string | null = (url.indexOf("{data}") >= 0) ? null: JSON.stringify({ data, sender });
|
||||
|
||||
const result = await fetchJson({ url: href, errorPassThrough: true }, json, (value, response) => {
|
||||
value.status = response.statusCode;
|
||||
return value;
|
||||
});
|
||||
|
||||
if (result.data) { return result.data; }
|
||||
|
||||
const errorMessage = (result.message || "unknown error");
|
||||
|
||||
// 4xx indicates the result is not present; stop
|
||||
if (result.status >= 400 && result.status < 500) {
|
||||
return logger.throwError(`response not found during CCIP fetch: ${ errorMessage }`, Logger.errors.SERVER_ERROR, { url, errorMessage });
|
||||
}
|
||||
|
||||
// 5xx indicates server issue; try the next url
|
||||
errorMessages.push(errorMessage);
|
||||
}
|
||||
|
||||
return logger.throwError(`error encountered during CCIP fetch: ${ errorMessages.map((m) => JSON.stringify(m)).join(", ") }`, Logger.errors.SERVER_ERROR, {
|
||||
urls, errorMessages
|
||||
});
|
||||
}
|
||||
|
||||
// Fetches the blockNumber, but will reuse any result that is less
|
||||
// than maxAge old or has been requested since the last request
|
||||
async _getInternalBlockNumber(maxAge: number): Promise<number> {
|
||||
@ -1512,22 +1557,105 @@ export class BaseProvider extends Provider implements EnsProvider {
|
||||
return this.formatter.filter(await resolveProperties(result));
|
||||
}
|
||||
|
||||
async call(transaction: Deferrable<TransactionRequest>, blockTag?: BlockTag | Promise<BlockTag>): Promise<string> {
|
||||
await this.getNetwork();
|
||||
const params = await resolveProperties({
|
||||
transaction: this._getTransactionRequest(transaction),
|
||||
blockTag: this._getBlockTag(blockTag)
|
||||
});
|
||||
async _call(transaction: TransactionRequest, blockTag: BlockTag, attempt: number): Promise<string> {
|
||||
if (attempt >= MAX_CCIP_REDIRECTS) {
|
||||
logger.throwError("CCIP read exceeded maximum redirections", Logger.errors.SERVER_ERROR, {
|
||||
redirects: attempt, transaction
|
||||
});
|
||||
}
|
||||
|
||||
const txSender = transaction.to;
|
||||
|
||||
const result = await this.perform("call", { transaction, blockTag });
|
||||
|
||||
// CCIP Read request via OffchainLookup(address,string[],bytes,bytes4,bytes)
|
||||
if (attempt >= 0 && blockTag === "latest" && txSender != null && result.substring(0, 10) === "0x556f1830" && (hexDataLength(result) % 32 === 4)) {
|
||||
try {
|
||||
const data = hexDataSlice(result, 4);
|
||||
|
||||
// Check the sender of the OffchainLookup matches the transaction
|
||||
const sender = hexDataSlice(data, 0, 32);
|
||||
if (!BigNumber.from(sender).eq(txSender)) {
|
||||
logger.throwError("CCIP Read sender did not match", Logger.errors.CALL_EXCEPTION, {
|
||||
name: "OffchainLookup",
|
||||
signature: "OffchainLookup(address,string[],bytes,bytes4,bytes)",
|
||||
transaction, data: result
|
||||
});
|
||||
}
|
||||
|
||||
// Read the URLs from the response
|
||||
const urls: Array<string> = [];
|
||||
const urlsOffset = BigNumber.from(hexDataSlice(data, 32, 64)).toNumber();
|
||||
const urlsLength = BigNumber.from(hexDataSlice(data, urlsOffset, urlsOffset + 32)).toNumber();
|
||||
const urlsData = hexDataSlice(data, urlsOffset + 32);
|
||||
for (let u = 0; u < urlsLength; u++) {
|
||||
const url = _parseString(urlsData, u * 32);
|
||||
if (url == null) {
|
||||
logger.throwError("CCIP Read contained corrupt URL string", Logger.errors.CALL_EXCEPTION, {
|
||||
name: "OffchainLookup",
|
||||
signature: "OffchainLookup(address,string[],bytes,bytes4,bytes)",
|
||||
transaction, data: result
|
||||
});
|
||||
}
|
||||
urls.push(url);
|
||||
}
|
||||
|
||||
// Get the CCIP calldata to forward
|
||||
const calldata = _parseBytes(data, 64);
|
||||
|
||||
// Get the callbackSelector (bytes4)
|
||||
if (!BigNumber.from(hexDataSlice(data, 100, 128)).isZero()) {
|
||||
logger.throwError("CCIP Read callback selector included junk", Logger.errors.CALL_EXCEPTION, {
|
||||
name: "OffchainLookup",
|
||||
signature: "OffchainLookup(address,string[],bytes,bytes4,bytes)",
|
||||
transaction, data: result
|
||||
});
|
||||
}
|
||||
const callbackSelector = hexDataSlice(data, 96, 100);
|
||||
|
||||
// Get the extra data to send back to the contract as context
|
||||
const extraData = _parseBytes(data, 128);
|
||||
|
||||
const ccipResult = await this.ccipReadFetch(<Transaction>transaction, calldata, urls);
|
||||
if (ccipResult == null) {
|
||||
logger.throwError("CCIP Read disabled or provided no URLs", Logger.errors.CALL_EXCEPTION, {
|
||||
name: "OffchainLookup",
|
||||
signature: "OffchainLookup(address,string[],bytes,bytes4,bytes)",
|
||||
transaction, data: result
|
||||
});
|
||||
}
|
||||
|
||||
const tx = {
|
||||
to: txSender,
|
||||
data: hexConcat([ callbackSelector, encodeBytes([ ccipResult, extraData ]) ])
|
||||
};
|
||||
|
||||
return this._call(tx, blockTag, attempt + 1);
|
||||
|
||||
} catch (error) {
|
||||
if (error.code === Logger.errors.SERVER_ERROR) { throw error; }
|
||||
}
|
||||
}
|
||||
|
||||
const result = await this.perform("call", params);
|
||||
try {
|
||||
return hexlify(result);
|
||||
} catch (error) {
|
||||
return logger.throwError("bad result from backend", Logger.errors.SERVER_ERROR, {
|
||||
method: "call",
|
||||
params, result, error
|
||||
params: { transaction, blockTag }, result, error
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async call(transaction: Deferrable<TransactionRequest>, blockTag?: BlockTag | Promise<BlockTag>): Promise<string> {
|
||||
await this.getNetwork();
|
||||
const resolved = await resolveProperties({
|
||||
transaction: this._getTransactionRequest(transaction),
|
||||
blockTag: this._getBlockTag(blockTag),
|
||||
ccipReadEnabled: Promise.resolve(transaction.ccipReadEnabled)
|
||||
});
|
||||
return this._call(resolved.transaction, resolved.blockTag, resolved.ccipReadEnabled ? 0: -1);
|
||||
}
|
||||
|
||||
async estimateGas(transaction: Deferrable<TransactionRequest>): Promise<BigNumber> {
|
||||
@ -1843,7 +1971,7 @@ export class BaseProvider extends Provider implements EnsProvider {
|
||||
const name = _parseString(await this.call({
|
||||
to: resolverAddr,
|
||||
data: ("0x691f3431" + namehash(node).substring(2))
|
||||
}));
|
||||
}), 0);
|
||||
|
||||
const addr = await this.resolveName(name);
|
||||
if (addr != address) { return null; }
|
||||
@ -1877,7 +2005,7 @@ export class BaseProvider extends Provider implements EnsProvider {
|
||||
const name = _parseString(await this.call({
|
||||
to: resolverAddress,
|
||||
data: ("0x691f3431" + namehash(node).substring(2))
|
||||
}));
|
||||
}), 0);
|
||||
resolver = await this.getResolver(name);
|
||||
} catch (error) {
|
||||
if (error.code !== Logger.errors.CALL_EXCEPTION) { throw error; }
|
||||
|
@ -1393,3 +1393,155 @@ describe("Resolve ENS avatar", function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Test EIP-2544 ENS wildcards", function() {
|
||||
const provider = <ethers.providers.BaseProvider>(providerFunctions[0].create("ropsten"));
|
||||
|
||||
it("Resolves recursively", async function() {
|
||||
const resolver = await provider.getResolver("ricmoose.hatch.eth");
|
||||
assert.equal(resolver.address, "0x8fc4C380c5d539aE631daF3Ca9182b40FB21D1ae", "found the correct resolver");
|
||||
assert.equal(await resolver.supportsWildcard(), true, "supportsWildcard");
|
||||
assert.equal((await resolver.getAvatar()).url, "https://static.ricmoo.com/uploads/profile-06cb9c3031c9.jpg", "gets passed-through avatar");
|
||||
assert.equal(await resolver.getAddress(), "0x4FaBE0A3a4DDd9968A7b4565184Ad0eFA7BE5411", "gets resolved address");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Test CCIP execution", function() {
|
||||
const address = "0xAe375B05A08204C809b3cA67C680765661998886";
|
||||
const ABI = [
|
||||
//'error OffchainLookup(address sender, string[] urls, bytes callData, bytes4 callbackFunction, bytes extraData)',
|
||||
'function testGet(bytes callData) view returns (bytes32)',
|
||||
'function testGetFail(bytes callData) view returns (bytes32)',
|
||||
'function testGetSenderFail(bytes callData) view returns (bytes32)',
|
||||
'function testGetFallback(bytes callData) view returns (bytes32)',
|
||||
'function testGetMissing(bytes callData) view returns (bytes32)',
|
||||
'function testPost(bytes callData) view returns (bytes32)',
|
||||
'function verifyTest(bytes result, bytes extraData) pure returns (bytes32)'
|
||||
];
|
||||
|
||||
const provider = providerFunctions[0].create("ropsten");
|
||||
const contract = new ethers.Contract(address, ABI, provider);
|
||||
|
||||
// This matches the verify method in the Solidity contract against the
|
||||
// processed data from the endpoint
|
||||
const verify = function(sender: string, data: string, result: string): void {
|
||||
const check = ethers.utils.concat([
|
||||
ethers.utils.arrayify(ethers.utils.arrayify(sender).length),
|
||||
sender,
|
||||
ethers.utils.arrayify(ethers.utils.arrayify(data).length),
|
||||
data
|
||||
]);
|
||||
assert.equal(result, ethers.utils.keccak256(check), "response is equal");
|
||||
}
|
||||
|
||||
it("testGet passes under normal operation", async function() {
|
||||
this.timeout(60000);
|
||||
const data = "0x1234";
|
||||
const result = await contract.testGet(data, { ccipReadEnabled: true });
|
||||
verify(ethers.constants.AddressZero, data, result);
|
||||
});
|
||||
|
||||
it("testGet should fail with CCIP not explicitly enabled by overrides", async function() {
|
||||
this.timeout(60000);
|
||||
|
||||
try {
|
||||
const data = "0x1234";
|
||||
const result = await contract.testGet(data);
|
||||
console.log(result);
|
||||
assert.fail("throw-failed");
|
||||
} catch (error: any) {
|
||||
if (error.message === "throw-failed") { throw error; }
|
||||
if (error.code !== "CALL_EXCEPTION") {
|
||||
console.log(error);
|
||||
assert.fail("failed");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("testGet should fail with CCIP explicitly disabled on provider", async function() {
|
||||
this.timeout(60000);
|
||||
|
||||
const provider = providerFunctions[0].create("ropsten");
|
||||
(<ethers.providers.BaseProvider>provider).disableCcipRead = true;
|
||||
const contract = new ethers.Contract(address, ABI, provider);
|
||||
|
||||
try {
|
||||
const data = "0x1234";
|
||||
const result = await contract.testGet(data, { ccipReadEnabled: true });
|
||||
console.log(result);
|
||||
assert.fail("throw-failed");
|
||||
} catch (error: any) {
|
||||
if (error.message === "throw-failed") { throw error; }
|
||||
if (error.code !== "CALL_EXCEPTION") {
|
||||
console.log(error);
|
||||
assert.fail("failed");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("testGetFail should fail if all URLs 5xx", async function() {
|
||||
this.timeout(60000);
|
||||
|
||||
try {
|
||||
const data = "0x1234";
|
||||
const result = await contract.testGetFail(data, { ccipReadEnabled: true });
|
||||
console.log(result);
|
||||
assert.fail("throw-failed");
|
||||
} catch (error: any) {
|
||||
if (error.message === "throw-failed") { throw error; }
|
||||
if (error.code !== "SERVER_ERROR" || (error.errorMessages || []).pop() !== "hello world") {
|
||||
console.log(error);
|
||||
assert.fail("failed");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("testGetSenderFail should fail if sender does not match", async function() {
|
||||
this.timeout(60000);
|
||||
|
||||
try {
|
||||
const data = "0x1234";
|
||||
const result = await contract.testGetSenderFail(data, { ccipReadEnabled: true });
|
||||
console.log(result);
|
||||
assert.fail("throw-failed");
|
||||
} catch (error: any) {
|
||||
if (error.message === "throw-failed") { throw error; }
|
||||
if (error.code !== "CALL_EXCEPTION") {
|
||||
console.log(error);
|
||||
assert.fail("failed");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("testGetMissing should fail if early URL 4xx", async function() {
|
||||
this.timeout(60000);
|
||||
|
||||
try {
|
||||
const data = "0x1234";
|
||||
const result = await contract.testGetMissing(data, { ccipReadEnabled: true });
|
||||
console.log(result);
|
||||
assert.fail("throw-failed");
|
||||
} catch (error: any) {
|
||||
if (error.message === "throw-failed") { throw error; }
|
||||
if (error.code !== "SERVER_ERROR" || error.errorMessage !== "hello world") {
|
||||
console.log(error);
|
||||
assert.fail("failed");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("testGetFallback passes if any URL returns coorectly", async function() {
|
||||
this.timeout(60000);
|
||||
const data = "0x123456";
|
||||
const result = await contract.testGetFallback(data, { ccipReadEnabled: true });
|
||||
verify(ethers.constants.AddressZero, data, result);
|
||||
});
|
||||
|
||||
it("testPost passes under normal operation", async function() {
|
||||
this.timeout(60000);
|
||||
const data = "0x1234";
|
||||
const result = await contract.testPost(data, { ccipReadEnabled: true });
|
||||
verify(ethers.constants.AddressZero, data, result);
|
||||
});
|
||||
|
||||
})
|
||||
|
@ -50,6 +50,7 @@ export type ConnectionInfo = {
|
||||
throttleCallback?: (attempt: number, url: string) => Promise<boolean>,
|
||||
|
||||
skipFetchSetup?: boolean;
|
||||
errorPassThrough?: boolean;
|
||||
|
||||
timeout?: number,
|
||||
};
|
||||
@ -98,6 +99,8 @@ export function _fetchData<T = Uint8Array>(connection: string | ConnectionInfo,
|
||||
logger.assertArgument((throttleSlotInterval > 0 && (throttleSlotInterval % 1) === 0),
|
||||
"invalid connection throttle slot interval", "connection.throttleSlotInterval", throttleSlotInterval);
|
||||
|
||||
const errorPassThrough = ((typeof(connection) === "object") ? !!(connection.errorPassThrough): false);
|
||||
|
||||
const headers: { [key: string]: Header } = { };
|
||||
|
||||
let url: string = null;
|
||||
@ -288,8 +291,7 @@ export function _fetchData<T = Uint8Array>(connection: string | ConnectionInfo,
|
||||
|
||||
if (allow304 && response.statusCode === 304) {
|
||||
body = null;
|
||||
|
||||
} else if (response.statusCode < 200 || response.statusCode >= 300) {
|
||||
} else if (!errorPassThrough && (response.statusCode < 200 || response.statusCode >= 300)) {
|
||||
runningTimeout.cancel();
|
||||
logger.throwError("bad response", Logger.errors.SERVER_ERROR, {
|
||||
status: response.statusCode,
|
||||
|
Loading…
Reference in New Issue
Block a user