@witnet/ethers
Version:
Wit/Oracle SDK Framework package for Solidity projects
272 lines (255 loc) • 9.49 kB
JavaScript
const cbor = require("cbor")
const framework = require("witnet-solidity-bridge/utils")
const { utils, Witnet } = require("@witnet/sdk")
module.exports = {
buildWitOracleRequestFromTemplate,
deployWitOracleRequest,
deployWitOracleRequestTemplate,
encodeWitnetRadon,
flattenRadonAssets: utils.radon.assets.flatten,
getFromFromArgs,
getRealmNetworkFromArgs: framework.getRealmNetworkFromArgs,
getRealmNetworkFromString: framework.getRealmNetworkFromString,
getWitnetArtifactsFromArgs,
getWitOracleRequestMethodString: framework.getWitOracleRequestMethodString,
isDryRun: framework.isDryRun,
isNullAddress,
orderObjectKeys,
processDryRunJson,
loadAddresses,
saveAddresses,
readJsonFromFile: framework.readJsonFromFile,
overwriteJsonFile: framework.overwriteJsonFile,
traceHeader: framework.traceHeader,
traceTx,
verifyRadonReducer,
verifyRadonRetrieval,
}
async function buildWitOracleRequestFromTemplate (web3, from, templateContract, args) {
// convert all args values to string
args = args.map(subargs => subargs.map(v => v.toString()))
const requestAddr = await templateContract.methods["buildWitOracleRequest(string[][])"].call(args, { from })
if ((await web3.eth.getCode(requestAddr)).length <= 3) {
const tx = await templateContract.methods["buildWitOracleRequest(string[][])"](args, { from })
console.info(" ", "> Template settlement hash:", tx.receipt.transactionHash)
console.info(" ", "> Template settlement gas: ", tx.receipt.gasUsed)
}
return requestAddr
}
async function deployWitOracleRequest (web3, from, registry, factory, request, templateArtifact, key) {
if (request instanceof Witnet.RadonRequest) {
const sources = await verifyRadonRetrievals(from, registry, request?.retrieve)
if (key) framework.traceHeader(`Building '\x1b[1;37m${key}\x1b[0m'...`)
let requestAddr = await factory.buildWitOracleRequest.call(
sources,
encodeWitnetRadon(request.aggregate),
encodeWitnetRadon(request.tally),
{ from }
)
if (isNullAddress(requestAddr) || (await web3.eth.getCode(requestAddr)).length <= 3) {
const tx = await factory.buildWitOracleRequest(
sources,
encodeWitnetRadon(request.aggregate),
encodeWitnetRadon(request.tally),
{ from }
)
traceTx(tx.receipt)
tx.logs = tx.logs.filter(log => log.event === "WitOracleRequestBuilt")
requestAddr = tx.logs[0].args.request
}
return requestAddr
}
}
async function verifyRadonRetrievals (from, registry, retrievals) {
const sources = []
for (let j = 0; j < retrievals.length; j++) {
sources.push(
await verifyRadonRetrieval(from, registry, retrievals[j])
)
}
return sources
}
async function deployWitOracleRequestTemplate (web3, from, registry, factory, template, key) {
const sources = await verifyRadonRetrievals(from, registry, template.specs.retrieve)
if (key) framework.traceHeader(`Building '\x1b[1;37m${key}\x1b[0m'...`)
let templateAddr = await factory.buildWitOracleRequestTemplate.call(
sources,
encodeWitnetRadon(template.specs.aggregate),
encodeWitnetRadon(template.specs.tally),
{ from }
)
if (isNullAddress(templateAddr) || (await web3.eth.getCode(templateAddr)).length <= 3) {
const tx = await factory.buildWitOracleRequestTemplate(
sources,
encodeWitnetRadon(template.specs.aggregate),
encodeWitnetRadon(template.specs.tally),
{ from }
)
traceTx(tx.receipt)
tx.logs = tx.logs.filter(log => log.event === "WitOracleRequestTemplateBuilt")
templateAddr = tx.logs[0].args.template
}
return templateAddr
};
function encodeWitnetRadon (T) {
if (T instanceof Witnet.RadonReducers.RadonReducer) {
return [
T.opcode,
T.filters?.map(filter => encodeWitnetRadon(filter)) || [],
]
} else if (T instanceof Witnet.RadonFilters.RadonFilter) {
return [
T.opcode,
`0x${T.args ? cbor.encode(T.args).toString("hex") : ""}`,
]
} else if (T instanceof Witnet.RadonRetrieval) {
return [
T.method,
T.url || "",
T.body || "",
T.headers || [],
encodeWitnetRadon(T.script) || "0x80",
]
} else if (T instanceof Witnet.RadonScriptWrapper) {
return T.toBytecode()
}
return T
};
function getFromFromArgs () {
const fromIndex = process.argv.indexOf("--from")
if (fromIndex >= 0) {
return process.argv[fromIndex + 1]
} else {
return null
}
};
function isNullAddress (addr) {
return !addr ||
addr === "" ||
addr === "0x0000000000000000000000000000000000000000"
}
function orderObjectKeys (unordered) {
return Object.keys(unordered).sort().reduce(
(obj, key) => {
obj[key] = unordered[key]
return obj
}, {}
)
}
function processDryRunJson (dryrun) {
let error = ""
const msecs = []
dryrun?.retrieve.forEach(retrieve => {
msecs.push(retrieve?.running_time?.secs * 1000 + retrieve?.running_time?.nanos / 1000000)
})
const itWorks = !("RadonError" in dryrun?.aggregate?.result)
if (!itWorks) {
error = `Aggregation failed: ${unescape(dryrun?.aggregate?.result?.RadonError)}`
}
const nokSources = Object.values(
dryrun?.retrieve.filter((source, index) => {
const nok = "RadonError" in source.result
if (nok && !error) {
error = `Source #${index + 1}: ${unescape(source.result?.RadonError)}`
}
return nok
})
).length
const totalSources = Object.values(dryrun?.retrieve).length
const status = itWorks ? (nokSources > 0 ? "WARN" : "OK") : "FAIL"
return {
error,
itWorks,
nokSources,
totalSources,
runningTime: Math.round(msecs.reduce((a, b) => a > b ? a : b)) / 1000,
status,
tally: dryrun?.tally.result,
}
}
async function verifyRadonReducer (from, registry, reducer) {
let hash
if (reducer instanceof Witnet.Reducers.Class) {
hash = await registry.verifyRadonReducer.call(encodeWitnetRadon(reducer), { from })
try {
await registry.lookupRadonReducer.call(hash, { from })
} catch {
// register new reducer, otherwise:
framework.traceHeader("Verifying Radon Reducer ...")
console.info(` > Hash: \x1b[35m${hash}\x1b[0m`)
console.info(` > Reducer: \x1b[1;35m${reducer.toString()}\x1b[0m`)
const tx = await registry.verifyRadonReducer(encodeWitnetRadon(reducer), { from })
traceTx(tx.receipt)
}
} else {
throw TypeError(`Witnet Radon Reducer: invalid type: '\x1b[1;31m${reducer}\x1b[0m'`)
}
return hash
};
async function verifyRadonRetrieval (from, registry, source) {
// get actual hash for this data source
let hash
if (source) {
try {
const args = encodeWitnetRadon(source)
hash = await registry.methods["verifyRadonRetrieval(uint8,string,string,string[2][],bytes)"].call(...args, { from })
} catch (e) {
throw EvalError(`Cannot check if Witnet Radon Source is already verified: ${e}`)
}
// checks whether hash is already registered
try {
await registry.lookupRadonRetrieval.call(hash, { from })
} catch {
// register new source, otherwise:
framework.traceHeader("Verifying Radon Retrieval ...")
console.info(` > Hash: \x1b[32m${hash}\x1b[0m`)
if (source?.url) {
console.info(` > URL: \x1b[1;32m${source.url}\x1b[0m`)
}
console.info(` > Method: \x1b[1;32m${framework.getWitOracleRequestMethodString(source?.method)}\x1b[0m`)
if (source?.body) {
console.info(` > Body: \x1b[1;32m${source.body}\x1b[0m`)
}
if (source?.headers && source?.headers[0] && source?.headers[0][0] !== "") {
console.info(` > Headers: \x1b[1;32m${source.headers}\x1b[0m`)
}
if (source?.script) {
console.info(` > Script: \x1b[1;33m${source.script.toString()}\x1b[0m`)
}
if (source?.argsCount) {
console.info(` > Total args: \x1b[1;33m${source.argsCount}\x1b[0m`)
}
const tx = await registry
.methods["verifyRadonRetrieval(uint8,string,string,string[2][],bytes)"]
.sendTransaction(...encodeWitnetRadon(source), { from })
traceTx(tx.receipt)
}
} else {
throw TypeError(`Witnet Radon Source: invalid type: '\x1b[1;31m${source}\x1b[0m'`)
}
return hash
};
function loadAddresses (path) {
const fs = require("fs")
const filename = `${path}/addresses.json`
return JSON.parse(fs.readFileSync(filename))
}
function saveAddresses (path, addrs) {
const fs = require("fs")
const filename = `${path}/addresses.json`
const json = { ...JSON.parse(fs.readFileSync(filename)), ...addrs }
fs.writeFileSync(filename, JSON.stringify(json, null, 4), { flag: "w+" })
};
function traceTx (receipt) {
console.log(" ", "> Transaction block:", receipt.blockNumber)
console.log(" ", "> Transaction hash: ", receipt.transactionHash)
console.log(" ", "> Transaction gas: ", receipt.gasUsed)
};
function getWitnetArtifactsFromArgs () {
let selection = []
const artifactsIndex = process.argv.indexOf("--artifacts")
if (artifactsIndex >= 0) {
selection = process.argv.slice(artifactsIndex + 1)
}
return selection
};