@iexec/iexec-oracle-factory-wrapper
Version:
A wrapper for creating API based oracles for ethereum on the top of iExec
136 lines (129 loc) • 4.33 kB
text/typescript
import { ethers } from 'ethers';
import { getReaderDefaults } from '../config/config.js';
import { READ_ABI } from '../config/contract.js';
import {
OracleValue,
ReadOracleOptions,
ReadOracleParams,
} from '../types/common.js';
import { EthersProviderConsumer } from '../types/internal.js';
import {
NoValueError,
ValidationError,
WorkflowError,
} from '../utils/errors.js';
import { formatOracleGetNumber } from '../utils/format.js';
import { computeOracleId, isOracleId } from '../utils/hash.js';
import { getParamSet } from '../utils/utils.js';
import { readDataTypeSchema } from '../utils/validators.js';
/**
* Reads data from an oracle based on the provided parameters.
* @param paramSetOrCidOrOracleId Param set, CID, or oracle ID.
* @param dataType Type of data to read.
* @param ethersProvider Ethereum provider.
* @param ipfsGateway IPFS gateway URL.
* @param oracleContract Address of the oracle contract.
* @returns Promise resolving to the Oracle data.
* @throws {NoValueError} If no value is stored for the oracle.
* @throws {ValidationError} If there is a validation error.
* @throws {WorkflowError} If there is an unexpected workflow error.
*/
const readOracle = async ({
paramSetOrCidOrOracleId,
dataType,
ethersProvider,
ipfsGateway,
oracleContract,
}: ReadOracleParams &
ReadOracleOptions &
EthersProviderConsumer): Promise<OracleValue> => {
const chainId = await ethersProvider
.getNetwork()
.then((res) => `${res.chainId}`);
const ORACLE_CONTRACT_ADDRESS =
oracleContract ||
getReaderDefaults(Number(chainId)).ORACLE_CONTRACT_ADDRESS;
let readDataType;
let oracleId;
if (isOracleId(paramSetOrCidOrOracleId)) {
oracleId = paramSetOrCidOrOracleId;
readDataType = await readDataTypeSchema().validate(
!dataType ? 'raw' : dataType
);
} else {
if (dataType) {
throw Error(
'dataType option is only allowed when reading oracle from oracleId'
);
}
const { paramSet } = await getParamSet({
paramSetOrCid: paramSetOrCidOrOracleId,
ipfsGateway,
}).catch((e) => {
if (e instanceof ValidationError) {
throw e;
} else {
throw new WorkflowError({
message: 'Failed to load paramSet',
errorCause: e,
});
}
});
readDataType = paramSet.dataType;
oracleId = await computeOracleId(paramSet);
}
const oracleSmartContract = new ethers.Contract(
ORACLE_CONTRACT_ADDRESS,
READ_ABI,
ethersProvider
);
const [rawValue, rawDateBn] = await oracleSmartContract
.getRaw(oracleId)
.catch(() => {
throw Error(`Failed to read value from oracle with oracleId ${oracleId}`);
});
const rawDateNumber = parseInt(rawDateBn.toString());
if (rawDateNumber == 0) {
throw new NoValueError(`No value stored for oracleId ${oracleId}`);
}
switch (readDataType) {
case 'boolean': {
const [result, dateBn] = await oracleSmartContract
.getBool(oracleId)
.catch(() => {
throw Error(
`Failed to read boolean from oracle with oracleId ${oracleId}\nThis may occur when:\n- No value is stored\n- Stored value is not boolean dataType`
);
});
return { value: result, date: parseInt(dateBn.toString()) };
}
case 'number': {
const [resultBn, dateBn] = await oracleSmartContract
.getInt(oracleId)
.catch(() => {
throw Error(
`Failed to read number from oracle with oracleId ${oracleId}\nThis may occur when:\n- No value is stored\n- Stored value is not number dataType`
);
});
const resultNumber = formatOracleGetNumber(resultBn);
return { value: resultNumber, date: parseInt(dateBn.toString()) };
}
case 'string': {
const [resultString, dateBn] = await oracleSmartContract
.getString(oracleId)
.catch(() => {
throw Error(
`Failed to read string from oracle with oracleId ${oracleId}\nThis may occur when:\n- No value is stored\n- Stored value is not string dataType`
);
});
return { value: resultString, date: parseInt(dateBn.toString()) };
}
default: {
return {
value: rawValue,
date: rawDateNumber,
};
}
}
};
export { readOracle };