@hyperlane-xyz/core
Version:
Core solidity contracts for Hyperlane
466 lines (446 loc) • 12.3 kB
text/typescript
/**
* @packageDocumentation
*
* This file provides convenience functions to interact with existing solidity contract abstraction libraries, such as
* @truffle/contract and ethers.js specifically for our `Oracle.sol` solidity smart contract.
*/
import { BigNumberish } from '@ethersproject/bignumber/lib/bignumber'
import { ethers } from 'ethers'
import { makeDebug } from './debug'
import { addCBORMapDelimiters, stripHexPrefix, toHex } from './helpers'
const debug = makeDebug('oracle')
/**
* Transaction options such as gasLimit, gasPrice, data, ...
*/
type TxOptions = Omit<ethers.providers.TransactionRequest, 'to' | 'from'>
/**
* A run request is an event emitted by `Oracle.sol` which triggers a job run
* on a receiving chainlink node watching for RunRequests coming from that
* specId + optionally requester.
*/
export interface RunRequest {
/**
* The ID of the job spec this request is targeting
*
* @solformat bytes32
*/
specId: string
/**
* The requester of the run
*
* @solformat address
*/
requester: string
/**
* The ID of the request, check Oracle.sol#oracleRequest to see how its computed
*
* @solformat bytes32
*/
requestId: string
/**
* The amount of LINK used for payment
*
* @solformat uint256
*/
payment: string
/**
* The address of the contract instance to callback with the fulfillment result
*
* @solformat address
*/
callbackAddr: string
/**
* The function selector of the method that the oracle should call after fulfillment
*
* @solformat bytes4
*/
callbackFunc: string
/**
* The expiration that the node should respond by before the requester can cancel
*
* @solformat uint256
*/
expiration: string
/**
* The specified data version
*
* @solformat uint256
*/
dataVersion: number
/**
* The CBOR encoded payload of the request
*
* @solformat bytes
*/
data: Buffer
/**
* The hash of the signature of the OracleRequest event.
* ```solidity
* event OracleRequest(
* bytes32 indexed specId,
* address requester,
* bytes32 requestId,
* uint256 payment,
* address callbackAddr,
* bytes4 callbackFunctionId,
* uint256 cancelExpiration,
* uint256 dataVersion,
* bytes data
* );
* ```
* Note: this is a property used for testing purposes only.
* It is not part of the actual run request.
*
* @solformat bytes32
*/
topic: string
}
/**
* Convert the javascript format of the parameters needed to call the
* ```solidity
* function fulfillOracleRequest(
* bytes32 _requestId,
* uint256 _payment,
* address _callbackAddress,
* bytes4 _callbackFunctionId,
* uint256 _expiration,
* bytes32 _data
* )
* ```
* method on an Oracle.sol contract.
*
* @param runRequest The run request to flatten into the correct order to perform the `fulfillOracleRequest` function
* @param response The response to fulfill the run request with, if it is an ascii string, it is converted to bytes32 string
* @param txOpts Additional ethereum tx options
*/
export function convertFufillParams(
runRequest: RunRequest,
response: string,
txOpts: TxOptions = {},
): [string, string, string, string, string, string, TxOptions] {
const d = debug.extend('fulfillOracleRequestParams')
d('Response param: %s', response)
const bytes32Len = 32 * 2 + 2
const convertedResponse =
response.length < bytes32Len
? ethers.utils.formatBytes32String(response)
: response
d('Converted Response param: %s', convertedResponse)
return [
runRequest.requestId,
runRequest.payment,
runRequest.callbackAddr,
runRequest.callbackFunc,
runRequest.expiration,
convertedResponse,
txOpts,
]
}
/**
* Convert the javascript format of the parameters needed to call the
* ```solidity
* function fulfillOracleRequest2(
* bytes32 _requestId,
* uint256 _payment,
* address _callbackAddress,
* bytes4 _callbackFunctionId,
* uint256 _expiration,
* bytes memory _data
* )
* ```
* method on an Oracle.sol contract.
*
* @param runRequest The run request to flatten into the correct order to perform the `fulfillOracleRequest` function
* @param response The response to fulfill the run request with, if it is an ascii string, it is converted to bytes32 string
* @param txOpts Additional ethereum tx options
*/
export function convertFulfill2Params(
runRequest: RunRequest,
responseTypes: string[],
responseValues: string[],
txOpts: TxOptions = {},
): [string, string, string, string, string, string, TxOptions] {
const d = debug.extend('fulfillOracleRequestParams')
d('Response param: %s', responseValues)
const types = [...responseTypes]
const values = [...responseValues]
types.unshift('bytes32')
values.unshift(runRequest.requestId)
const convertedResponse = ethers.utils.defaultAbiCoder.encode(types, values)
d('Encoded Response param: %s', convertedResponse)
return [
runRequest.requestId,
runRequest.payment,
runRequest.callbackAddr,
runRequest.callbackFunc,
runRequest.expiration,
convertedResponse,
txOpts,
]
}
/**
* Convert the javascript format of the parameters needed to call the
* ```solidity
* function cancelOracleRequest(
* bytes32 _requestId,
* uint256 _payment,
* bytes4 _callbackFunc,
* uint256 _expiration
* )
* ```
* method on an Oracle.sol contract.
*
* @param runRequest The run request to flatten into the correct order to perform the `cancelOracleRequest` function
* @param txOpts Additional ethereum tx options
*/
export function convertCancelParams(
runRequest: RunRequest,
txOpts: TxOptions = {},
): [string, string, string, string, TxOptions] {
return [
runRequest.requestId,
runRequest.payment,
runRequest.callbackFunc,
runRequest.expiration,
txOpts,
]
}
/**
* Convert the javascript format of the parameters needed to call the
* ```solidity
* function cancelOracleRequestByRequester(
* uint256 nonce,
* uint256 _payment,
* bytes4 _callbackFunc,
* uint256 _expiration
* )
* ```
* method on an Oracle.sol contract.
*
* @param nonce The nonce used to generate the request ID
* @param runRequest The run request to flatten into the correct order to perform the `cancelOracleRequest` function
* @param txOpts Additional ethereum tx options
*/
export function convertCancelByRequesterParams(
runRequest: RunRequest,
nonce: number,
txOpts: TxOptions = {},
): [number, string, string, string, TxOptions] {
return [
nonce,
runRequest.payment,
runRequest.callbackFunc,
runRequest.expiration,
txOpts,
]
}
/**
* Abi encode parameters to call the `oracleRequest` method on the Oracle.sol contract.
* ```solidity
* function oracleRequest(
* address _sender,
* uint256 _payment,
* bytes32 _specId,
* address _callbackAddress,
* bytes4 _callbackFunctionId,
* uint256 _nonce,
* uint256 _dataVersion,
* bytes _data
* )
* ```
*
* @param specId The Job Specification ID
* @param callbackAddr The callback contract address for the response
* @param callbackFunctionId The callback function id for the response
* @param nonce The nonce sent by the requester
* @param data The CBOR payload of the request
*/
export function encodeOracleRequest(
specId: string,
callbackAddr: string,
callbackFunctionId: string,
nonce: number,
data: BigNumberish,
dataVersion: BigNumberish = 1,
): string {
const oracleRequestSighash = '0x40429946'
return encodeRequest(
oracleRequestSighash,
specId,
callbackAddr,
callbackFunctionId,
nonce,
data,
dataVersion,
)
}
/**
* Abi encode parameters to call the `operatorRequest` method on the Operator.sol contract.
* ```solidity
* function operatorRequest(
* address _sender,
* uint256 _payment,
* bytes32 _specId,
* address _callbackAddress,
* bytes4 _callbackFunctionId,
* uint256 _nonce,
* uint256 _dataVersion,
* bytes _data
* )
* ```
*
* @param specId The Job Specification ID
* @param callbackAddr The callback contract address for the response
* @param callbackFunctionId The callback function id for the response
* @param nonce The nonce sent by the requester
* @param data The CBOR payload of the request
*/
export function encodeRequestOracleData(
specId: string,
callbackFunctionId: string,
nonce: number,
data: BigNumberish,
dataVersion: BigNumberish = 2,
): string {
const sendOperatorRequestSigHash = '0x3c6d41b9'
const requestInputs = [
{ name: '_sender', type: 'address' },
{ name: '_payment', type: 'uint256' },
{ name: '_specId', type: 'bytes32' },
{ name: '_callbackFunctionId', type: 'bytes4' },
{ name: '_nonce', type: 'uint256' },
{ name: '_dataVersion', type: 'uint256' },
{ name: '_data', type: 'bytes' },
]
const encodedParams = ethers.utils.defaultAbiCoder.encode(
requestInputs.map((i) => i.type),
[
ethers.constants.AddressZero,
0,
specId,
callbackFunctionId,
nonce,
dataVersion,
data,
],
)
return `${sendOperatorRequestSigHash}${stripHexPrefix(encodedParams)}`
}
function encodeRequest(
oracleRequestSighash: string,
specId: string,
callbackAddr: string,
callbackFunctionId: string,
nonce: number,
data: BigNumberish,
dataVersion: BigNumberish = 1,
): string {
const oracleRequestInputs = [
{ name: '_sender', type: 'address' },
{ name: '_payment', type: 'uint256' },
{ name: '_specId', type: 'bytes32' },
{ name: '_callbackAddress', type: 'address' },
{ name: '_callbackFunctionId', type: 'bytes4' },
{ name: '_nonce', type: 'uint256' },
{ name: '_dataVersion', type: 'uint256' },
{ name: '_data', type: 'bytes' },
]
const encodedParams = ethers.utils.defaultAbiCoder.encode(
oracleRequestInputs.map((i) => i.type),
[
ethers.constants.AddressZero,
0,
specId,
callbackAddr,
callbackFunctionId,
nonce,
dataVersion,
data,
],
)
return `${oracleRequestSighash}${stripHexPrefix(encodedParams)}`
}
/**
* Extract a javascript representation of a run request from the data
* contained within a EVM log.
* ```solidity
* event OracleRequest(
* bytes32 indexed specId,
* address requester,
* bytes32 requestId,
* uint256 payment,
* address callbackAddr,
* bytes4 callbackFunctionId,
* uint256 cancelExpiration,
* uint256 dataVersion,
* bytes data
* );
* ```
*
* @param log The log to extract the run request from
*/
export function decodeRunRequest(log?: ethers.providers.Log): RunRequest {
if (!log) {
throw Error('No logs found to decode')
}
const ORACLE_REQUEST_TYPES = [
'address',
'bytes32',
'uint256',
'address',
'bytes4',
'uint256',
'uint256',
'bytes',
]
const [
requester,
requestId,
payment,
callbackAddress,
callbackFunc,
expiration,
version,
data,
] = ethers.utils.defaultAbiCoder.decode(ORACLE_REQUEST_TYPES, log.data)
return {
specId: log.topics[1],
requester,
requestId: toHex(requestId),
payment: toHex(payment),
callbackAddr: callbackAddress,
callbackFunc: toHex(callbackFunc),
expiration: toHex(expiration),
data: addCBORMapDelimiters(Buffer.from(stripHexPrefix(data), 'hex')),
dataVersion: version.toNumber(),
topic: log.topics[0],
}
}
/**
* Extract a javascript representation of a ConcreteChainlinked#Request event
* from an EVM log.
* ```solidity
* event Request(
* bytes32 id,
* address callbackAddress,
* bytes4 callbackfunctionSelector,
* bytes data
* );
* ```
* The request event is emitted from the `ConcreteChainlinked.sol` testing contract.
*
* @param log The log to decode
*/
export function decodeCCRequest(
log: ethers.providers.Log,
): ethers.utils.Result {
const d = debug.extend('decodeRunABI')
d('params %o', log)
const REQUEST_TYPES = ['bytes32', 'address', 'bytes4', 'bytes']
const decodedValue = ethers.utils.defaultAbiCoder.decode(
REQUEST_TYPES,
log.data,
)
d('decoded value %o', decodedValue)
return decodedValue
}