diff --git a/.gitignore b/.gitignore index 76c0cff3..a15e6dd7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ node_modules +coverage out package-lock.json dist diff --git a/package-lock.json b/package-lock.json index fc6feb3f..da6100d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,11 +10,13 @@ "license": "MIT", "dependencies": { "@usecannon/cli": "^2.17.0", + "debug": "^4.3.7", "node-fetch": "^2.6.12", "typechain": "^8.3.1", "viem": "^2.20.0" }, "devDependencies": { + "@types/debug": "^4.1.12", "@types/jest": "^29.5.12", "@types/node-fetch": "^2.6.4", "@typescript-eslint/eslint-plugin": "^6.16.0", @@ -2821,6 +2823,16 @@ "@types/node": "*" } }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", @@ -2895,6 +2907,13 @@ "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==", "license": "MIT" }, + "node_modules/@types/ms": { + "version": "0.7.34", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "20.4.8", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.8.tgz", @@ -13313,6 +13332,15 @@ "@types/node": "*" } }, + "@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dev": true, + "requires": { + "@types/ms": "*" + } + }, "@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", @@ -13384,6 +13412,12 @@ "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==" }, + "@types/ms": { + "version": "0.7.34", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==", + "dev": true + }, "@types/node": { "version": "20.4.8", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.8.tgz", diff --git a/package.json b/package.json index 6aa7465d..7335c17c 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "homepage": "https://github.com/Synthetixio/erc7412#readme", "dependencies": { "@usecannon/cli": "^2.17.0", + "debug": "^4.3.7", "node-fetch": "^2.6.12", "typechain": "^8.3.1", "viem": "^2.20.0" @@ -36,6 +37,7 @@ "@pythnetwork/pyth-sdk-solidity": "^2.2.1" }, "devDependencies": { + "@types/debug": "^4.1.12", "@types/jest": "^29.5.12", "@types/node-fetch": "^2.6.4", "@typescript-eslint/eslint-plugin": "^6.16.0", diff --git a/src/index.ts b/src/index.ts index c658e790..e73262a4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,9 +3,13 @@ import IERC7412 from '../out/IERC7412.sol/IERC7412.json' import { type OracleAdapter } from './types' import { parseError } from './parseError' +import Debug from 'debug' + import ITrustedMulticallForwarder from '../out/ITrustedMulticallForwarder.sol/ITrustedMulticallForwarder.json' import { getWETHAddress } from './constants' +const debug = Debug('erc7412') + const TRUSTED_MULTICALL_FORWARDER_ADDRESS: viem.Address = '0xE2C5658cC5C448B48141168f3e475dF8f65A1e3e' export const LEGACY_ODR_ERROR = [ @@ -52,11 +56,9 @@ export async function callWithOffchainData( try { result = await client.call(makeTrustedForwarderMulticall([...prependedTxns, ...transactions] as TransactionRequest[])) } catch (caughtErr) { - console.error('an error occured', caughtErr) prependedTxns.push(...(await resolvePrependTransaction(caughtErr, client, adapters))) continue } - console.log('got result', result) if (result.data === undefined) { throw new Error('missing return data from multicall') } @@ -89,6 +91,7 @@ export function resolveAdapterCalls( data: parseError(origError as viem.CallExecutionError) }) } + debug('parsing error of type', err.errorName) if (err.errorName === 'Errors') { const errorsList = err.args?.[0] as viem.Hex[] @@ -114,7 +117,7 @@ export function resolveAdapterCalls( return { [oracleAddress]: [{ query: oracleQuery, fee }] } } } catch (err) { - console.log('had unexpected failure', err) + console.error('had unexpected failure', err) } // if we get to this point then we cant parse the error so we should make sure to send the original @@ -166,5 +169,7 @@ export async function resolvePrependTransaction( } } + debug('adding oracle update calls', priceUpdateTxs.length) + return priceUpdateTxs } diff --git a/test/pyth.mjs b/test/pyth.mjs index ceb56130..a3cd0ffe 100644 --- a/test/pyth.mjs +++ b/test/pyth.mjs @@ -1,236 +1,37 @@ -import eip7412 from '../dist/src/index.js' +import { callWithOffchainData } from '../dist/src/index.js' import { PythAdapter } from '../dist/src/oracles/pyth.js' +import * as cannon from '@usecannon/builder' import { Contract, ethers } from 'ethers' -import * as viem from 'viem' -import { baseGoerli } from 'viem/chains' - -const Multicall3ABI = [ - { - inputs: [ - { - components: [ - { - internalType: 'address', - name: 'target', - type: 'address' - }, - { - internalType: 'bool', - name: 'allowFailure', - type: 'bool' - }, - { - internalType: 'uint256', - name: 'value', - type: 'uint256' - }, - { - internalType: 'bytes', - name: 'callData', - type: 'bytes' - } - ], - internalType: 'struct Multicall3.Call3Value[]', - name: 'calls', - type: 'tuple[]' - } - ], - name: 'aggregate3Value', - outputs: [ - { - components: [ - { - internalType: 'bool', - name: 'success', - type: 'bool' - }, - { - internalType: 'bytes', - name: 'returnData', - type: 'bytes' - } - ], - internalType: 'struct Multicall3.Result[]', - name: 'returnData', - type: 'tuple[]' - } - ], - stateMutability: 'payable', - type: 'function' - } -] - -const MulticallThroughAbi = [ - { - inputs: [ - { - internalType: 'address[]', - name: 'to', - type: 'address[]' - }, - { - internalType: 'bytes[]', - name: 'data', - type: 'bytes[]' - }, - { - internalType: 'uint256[]', - name: 'values', - type: 'uint256[]' - } - ], - name: 'multicallThrough', - outputs: [ - { - internalType: 'bytes[]', - name: 'results', - type: 'bytes[]' - } - ], - stateMutability: 'payable', - type: 'function' - } -] - // make an ethers provider like we would have in the browser -const provider = new ethers.providers.JsonRpcProvider('https://goerli.base.org') - -async function generate7412CompatibleCall(client, multicallFunc, txn) { - const adapters = [] +const provider = new ethers.providers.JsonRpcProvider( + process.env.RPC_URL || 'https://arbitrum-sepolia.publicnode.com' +) - // NOTE: add other providers here as needed - adapters.push(new PythAdapter('https://hermes.pyth.network/')) - - const converter = new eip7412.EIP7412(adapters, multicallFunc) - - return await converter.enableERC7412(client, [txn]) -} - -export async function hookForReadCall(txn) { - const viemClient = viem.createPublicClient({ - chain: baseGoerli, - // NOTE: this can also be `custom(window.ethereum)` if preferred - transport: viem.custom({ - request: ({ method, params }) => { - return provider.send(method, params) - } - }) +;(async () => { + const contractData = await cannon.getCannonContract({ + package: 'synthetix-omnibus', + chainId: 421614, + contractName: 'perpsFactory.PerpsMarketProxy' }) - const multicall3Addr = '0xcbc8bDF9358BB3F5005B893a32b477e6B2F9f688' - const multicallFunc = function makeMulticall3Call(calls) { - const ret = viem.encodeFunctionData({ - abi: Multicall3ABI, - functionName: 'aggregate3Value', - args: [ - calls.map((call) => ({ - target: call.to, - callData: call.data, - value: call.value || 0n, - allowFailure: false - })) - ] - }) - - let totalValue = 0n - for (const call of calls) { - totalValue += call.value || 0n - } + const contract = new Contract(contractData.address, contractData.abi) - return { - account: txn.from || txn.account, - to: multicall3Addr, - data: ret, - value: totalValue.toString() - } - } - - // NOTE: pyth TransactionRequest is basically compatible with ethers TransactionRequest so we can just cast it - return generate7412CompatibleCall(viemClient, multicallFunc, txn) -} - -export async function hookForWriteCall(txn) { - const viemClient = viem.createPublicClient({ - chain: baseGoerli, - transport: viem.custom({ - request: ({ method, params }) => { - return provider.send(method, params) - } - }) - }) - const multicallFunc = function makeMulticallThroughCall(calls) { - const ret = viem.encodeFunctionData({ - abi: MulticallThroughAbi, - functionName: 'multicallThrough', - args: [ - calls.map((c) => c.to), - calls.map((c) => c.data), - calls.map((c) => c.value) - ] - }) + const data = contract.interface.encodeFunctionData('reportedDebt', [6]) - let totalValue = 0n - for (const call of calls) { - totalValue += call.value || 0n - } - - return { - account: txn.from || txn.account, - to: txn.to, - data: ret, - value: totalValue.toString() - } + const call = { + to: contractData.address, + data } - return generate7412CompatibleCall(viemClient, multicallFunc, txn) -} - -;(async () => { - // example call - // const call = await hookForReadCall({ - // to: '0x9863Dae3f4b5F4Ffe3A841a21565d57F2BA10E87', // perps competition market address - // data: '0x41c2e8bd0000000000000000000000000000000000000000000000000000000000000064', // call to `computeFee` on the above contract. triggers a OracleDataRequired. - // }); - - // console.log(await provider.call(call)); - - const contract = new Contract('0xEa7a8f0fDD16Ccd46BA541Fb657a0A7FD7E36261', [ - { - inputs: [ - { - internalType: 'bytes32', - name: 'priceId', - type: 'bytes32' - }, - { - internalType: 'uint64', - name: 'requestedTime', - type: 'uint64' - } - ], - name: 'getBenchmarkPrice', - outputs: [ - { - internalType: 'int256', - name: '', - type: 'int256' - } - ], - stateMutability: 'view', - type: 'function' - } - ]) - - const data = contract.interface.encodeFunctionData('getBenchmarkPrice', [ - '0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace', - '1705009034' - ]) + const adapters = [] + adapters.push(new PythAdapter('https://hermes.pyth.network/')) - const call = await hookForReadCall({ - to: '0xEa7a8f0fDD16Ccd46BA541Fb657a0A7FD7E36261', // Pyth Wrapper contract address - data // call to `getBenchMarkPrice` on the above contract. triggers a OracleDataRequired. - }) - console.log(await provider.call(call)) + const result = await callWithOffchainData( + [call], + { request: (r) => provider.send(r.method, r.params) }, + adapters + ) + console.log('completed sucessfully', result) })()