Skip to content

Commit

Permalink
Added CCIP support to provider.call (ethers-io#2478).
Browse files Browse the repository at this point in the history
  • Loading branch information
ricmoo authored and Woodpile37 committed Jan 14, 2024
1 parent ba658d2 commit ce7d0f3
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 4 deletions.
1 change: 1 addition & 0 deletions packages/abstract-provider/src.ts/index.ts
Expand Up @@ -34,6 +34,7 @@ export type TransactionRequest = {
maxFeePerGas?: BigNumberish;

customData?: Record<string, any>;
ccipReadEnabled?: boolean;
}

export interface TransactionResponse extends Transaction {
Expand Down
2 changes: 1 addition & 1 deletion packages/abstract-signer/src.ts/index.ts
Expand Up @@ -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 = [
Expand Down
10 changes: 9 additions & 1 deletion packages/contracts/src.ts/index.ts
Expand Up @@ -23,6 +23,7 @@ export interface Overrides {
type?: number;
accessList?: AccessListish;
customData?: Record<string, any>;
ccipReadEnabled?: boolean;
};

export interface PayableOverrides extends Overrides {
Expand Down Expand Up @@ -58,6 +59,7 @@ export interface PopulatedTransaction {
maxPriorityFeePerGas?: BigNumber;

customData?: Record<string, any>;
ccipReadEnabled?: boolean;
};

export type EventFilter = {
Expand Down Expand Up @@ -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> {
Expand Down Expand Up @@ -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;
Expand All @@ -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.
Expand Down
152 changes: 152 additions & 0 deletions packages/tests/src.ts/test-providers.ts
Expand Up @@ -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);
});

})
6 changes: 4 additions & 2 deletions packages/web/src.ts/index.ts
Expand Up @@ -50,6 +50,7 @@ export type ConnectionInfo = {
throttleCallback?: (attempt: number, url: string) => Promise<boolean>,

skipFetchSetup?: boolean;
errorPassThrough?: boolean;

timeout?: number,
};
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down

0 comments on commit ce7d0f3

Please sign in to comment.