From 63569ef9214958c74d959b10c7e56ea692d77dc7 Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Mon, 19 Oct 2020 12:48:54 -0600 Subject: [PATCH 01/26] feat: add Agoric adapter --- agoric/README.md | 20 +++++++++ agoric/adapter.js | 42 ++++++++++++++++++ agoric/index.js | 4 ++ agoric/package.json | 15 +++++++ agoric/test/adapter_test.js | 85 +++++++++++++++++++++++++++++++++++++ package.json | 1 + 6 files changed, 167 insertions(+) create mode 100644 agoric/README.md create mode 100644 agoric/adapter.js create mode 100644 agoric/index.js create mode 100644 agoric/package.json create mode 100644 agoric/test/adapter_test.js diff --git a/agoric/README.md b/agoric/README.md new file mode 100644 index 0000000000..2aa86011bc --- /dev/null +++ b/agoric/README.md @@ -0,0 +1,20 @@ +# Chainlink External Adapter for agoric + +## Input Params + +- `base`, `from`, or `coin`: The symbol of the currency to query +- `quote`, `to`, or `market`: The symbol of the currency to convert to +- `endpoint`: Optional endpoint param + +## Output + +```json +{ + "jobRunID": "278c97ffadb54a5bbb93cfec5f7b5503", + "data": { + "price": 77777.77, + "result": 77777.77 + }, + "statusCode": 200 +} +``` diff --git a/agoric/adapter.js b/agoric/adapter.js new file mode 100644 index 0000000000..59b885b5c4 --- /dev/null +++ b/agoric/adapter.js @@ -0,0 +1,42 @@ +const { Requester, Validator } = require('@chainlink/external-adapter') + +const customError = (data) => { + if (data.Response === 'Error') return true + return false +} + +const customParams = { + base: ['base', 'from', 'coin'], + quote: ['quote', 'to', 'market'], + endpoint: false, +} + +const execute = (input, callback) => { + const validator = new Validator(input, customParams) + if (validator.error) return callback(validator.error.statusCode, validator.error) + + const jobRunID = validator.validated.id + const endpoint = validator.validated.data.endpoint || 'price' + const url = `http://localhost:18081/${endpoint}` + const base = validator.validated.data.base.toUpperCase() + const quote = validator.validated.data.quote.toUpperCase() + + const params = { + base, + quote, + } + + const config = { + url, + params, + } + + Requester.request(config, customError) + .then((response) => { + response.data.result = Requester.validateResultNumber(response.data, ['price']) + callback(response.status, Requester.success(jobRunID, response)) + }) + .catch((error) => callback(500, Requester.errored(jobRunID, error))) +} + +module.exports.execute = execute diff --git a/agoric/index.js b/agoric/index.js new file mode 100644 index 0000000000..d5dfd92b9f --- /dev/null +++ b/agoric/index.js @@ -0,0 +1,4 @@ +const { expose } = require('@chainlink/ea-bootstrap') +const { execute } = require('./adapter') + +module.exports = expose(execute) diff --git a/agoric/package.json b/agoric/package.json new file mode 100644 index 0000000000..1c3121cb73 --- /dev/null +++ b/agoric/package.json @@ -0,0 +1,15 @@ +{ + "name": "@agoric/chainlink-external-adapter", + "version": "0.1.0", + "license": "MIT", + "main": "index.js", + "scripts": { + "server": "node -e 'require(\"./index.js\").server()'", + "lint": "eslint --ignore-path ../.eslintignore . --ext .js,.jsx,.ts,.tsx", + "lint:fix": "eslint --ignore-path ../.eslintignore . --ext .js,.jsx,.ts,.tsx --fix", + "test": "yarn _mocha --timeout 0", + "test:unit": "yarn _mocha --grep @integration --invert --timeout 0", + "test:integration": "yarn _mocha --grep @integration --timeout 0" + }, + "dependencies": {} +} diff --git a/agoric/test/adapter_test.js b/agoric/test/adapter_test.js new file mode 100644 index 0000000000..3bdd1b9f2f --- /dev/null +++ b/agoric/test/adapter_test.js @@ -0,0 +1,85 @@ +const { assert } = require('chai') +const { assertSuccess, assertError } = require('@chainlink/external-adapter') +const { execute } = require('../adapter') + +describe('execute', () => { + const jobID = '1' + + context('successful calls @integration', () => { + const requests = [ + { + name: 'id not supplied', + testData: { data: { base: 'ETH', quote: 'USD' } }, + }, + { + name: 'base/quote', + testData: { id: jobID, data: { base: 'ETH', quote: 'USD' } }, + }, + { + name: 'from/to', + testData: { id: jobID, data: { from: 'ETH', to: 'USD' } }, + }, + { + name: 'coin/market', + testData: { id: jobID, data: { coin: 'ETH', market: 'USD' } }, + }, + ] + + requests.forEach((req) => { + it(`${req.name}`, (done) => { + execute(req.testData, (statusCode, data) => { + assertSuccess({ expected: 200, actual: statusCode }, data, jobID) + assert.isAbove(data.result, 0) + assert.isAbove(data.data.result, 0) + done() + }) + }) + }) + }) + + context('validation error', () => { + const requests = [ + { name: 'empty body', testData: {} }, + { name: 'empty data', testData: { data: {} } }, + { + name: 'base not supplied', + testData: { id: jobID, data: { quote: 'USD' } }, + }, + { + name: 'quote not supplied', + testData: { id: jobID, data: { base: 'ETH' } }, + }, + ] + + requests.forEach((req) => { + it(`${req.name}`, (done) => { + execute(req.testData, (statusCode, data) => { + assertError({ expected: 400, actual: statusCode }, data, jobID) + done() + }) + }) + }) + }) + + context('error calls @integration', () => { + const requests = [ + { + name: 'unknown base', + testData: { id: jobID, data: { base: 'not_real', quote: 'USD' } }, + }, + { + name: 'unknown quote', + testData: { id: jobID, data: { base: 'ETH', quote: 'not_real' } }, + }, + ] + + requests.forEach((req) => { + it(`${req.name}`, (done) => { + execute(req.testData, (statusCode, data) => { + assertError({ expected: 500, actual: statusCode }, data, jobID) + done() + }) + }) + }) + }) +}) diff --git a/package.json b/package.json index 1fd00ad4eb..cf521c1554 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "covid-tracker", "messari", "coinlore", + "agoric", "trueusd", "cryptoid", "blockchair", From 009615d3428205c9ac5b4c885002a7817e9017be Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Wed, 21 Oct 2020 13:20:20 -0600 Subject: [PATCH 02/26] fix: use agoric_oracle_query_id --- agoric/README.md | 22 ++++++--- agoric/adapter.js | 114 ++++++++++++++++++++++++++++++++++------------ 2 files changed, 100 insertions(+), 36 deletions(-) diff --git a/agoric/README.md b/agoric/README.md index 2aa86011bc..6cdbc93da3 100644 --- a/agoric/README.md +++ b/agoric/README.md @@ -1,20 +1,28 @@ # Chainlink External Adapter for agoric +## Build Docker Container + +```sh +(cd .. && make docker adapter=agoric name=agoric/chainlink) +``` + ## Input Params -- `base`, `from`, or `coin`: The symbol of the currency to query -- `quote`, `to`, or `market`: The symbol of the currency to convert to -- `endpoint`: Optional endpoint param +Set the `$AG_SOLO_ORACLE` environment variable to something like: http://localhost:8000/api/oracle + +All request data will be passed through verbatim. + +- `agoric_oracle_query_id`: The Agoric oracle queryId. If unset, the result is not + posted to the Agoric oracle contract +- `result`: The result to return to the Agoric oracle contract + ## Output ```json { "jobRunID": "278c97ffadb54a5bbb93cfec5f7b5503", - "data": { - "price": 77777.77, - "result": 77777.77 - }, + "data": , "statusCode": 200 } ``` diff --git a/agoric/adapter.js b/agoric/adapter.js index 59b885b5c4..70413a9523 100644 --- a/agoric/adapter.js +++ b/agoric/adapter.js @@ -1,42 +1,98 @@ -const { Requester, Validator } = require('@chainlink/external-adapter') +const http = require('http') -const customError = (data) => { - if (data.Response === 'Error') return true - return false +const oracleAPI = process.env.AG_SOLO_ORACLE_URL +if (!oracleAPI) { + throw Error(`Must supply $AG_SOLO_ORACLE_URL`) } +const oracleUrl = new URL(oracleAPI) -const customParams = { - base: ['base', 'from', 'coin'], - quote: ['quote', 'to', 'market'], - endpoint: false, +const Nat = (n) => { + if (!Number.isSafeInteger(n)) { + throw Error(`${n} is not a safe integer`) + } + return n } -const execute = (input, callback) => { - const validator = new Validator(input, customParams) - if (validator.error) return callback(validator.error.statusCode, validator.error) - - const jobRunID = validator.validated.id - const endpoint = validator.validated.data.endpoint || 'price' - const url = `http://localhost:18081/${endpoint}` - const base = validator.validated.data.base.toUpperCase() - const quote = validator.validated.data.quote.toUpperCase() +// FIXME: Ideally, these would be the same. +const LINK_DECIMALS = 18 +const LINK_AGORIC_DECIMALS = 6 +if (LINK_AGORIC_DECIMALS > LINK_DECIMALS) { + throw Error( + `LINK_AGORIC_DECIMALS ${LINK_AGORIC_DECIMALS} must be less than or equal to ${LINK_DECIMALS}`, + ) +} - const params = { - base, - quote, +const getRequiredFee = (str) => { + const digits = str + const significant = digits.substr( + 0, + Math.max(0, digits.length - (LINK_DECIMALS - LINK_AGORIC_DECIMALS)), + ) + const roundUp = digits[significant.length] && parseInt(digits[significant.length], 10) >= 5 + let requiredFee = Nat(parseInt(significant, 10)) + if (roundUp) { + requiredFee += 1 } + return Nat(requiredFee) +} - const config = { - url, - params, - } +const send = (obj) => + new Promise((resolve, reject) => { + const data = JSON.stringify(obj) + const req = http.request( + { + hostname: oracleUrl.hostname, + port: oracleUrl.port, + path: oracleUrl.pathname, + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Content-Length': data.length, + }, + }, + (res) => { + if (res.statusCode === 200) { + resolve(res.statusCode) + } else { + reject(res.statusCode) + } + }, + ) + req.on('error', reject) + req.write(data) + req.end() + }) - Requester.request(config, customError) - .then((response) => { - response.data.result = Requester.validateResultNumber(response.data, ['price']) - callback(response.status, Requester.success(jobRunID, response)) +const execute = async (input, callback) => { + const queryId = input.data.agoric_oracle_query_id + try { + if (queryId) { + const requiredFee = getRequiredFee(input.data.payment) + await send({ + type: 'oracleServer/reply', + data: { queryId, reply: input.data.result, requiredFee }, + }) + } + callback(200, { + jobRunID: input.id, + data: input.data, + statusCode: 200, + }) + } catch (e) { + const error = `${(e && e.stack) || e}` + if (queryId) { + send({ + type: 'oracleServer/error', + data: { queryId, error }, + }) + } + callback(400, { + jobRunID: input.id, + status: 'errored', + error: error, + statusCode: 400, }) - .catch((error) => callback(500, Requester.errored(jobRunID, error))) + } } module.exports.execute = execute From 7d14c913d99e563f36b42aeebbcc7c52741df526 Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Thu, 22 Oct 2020 10:38:44 -0600 Subject: [PATCH 03/26] fix: make more explicit --- agoric/README.md | 6 ++---- agoric/adapter.js | 32 +++++++++++++++++++++++++------- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/agoric/README.md b/agoric/README.md index 6cdbc93da3..53cfc84233 100644 --- a/agoric/README.md +++ b/agoric/README.md @@ -10,19 +10,17 @@ Set the `$AG_SOLO_ORACLE` environment variable to something like: http://localhost:8000/api/oracle -All request data will be passed through verbatim. - - `agoric_oracle_query_id`: The Agoric oracle queryId. If unset, the result is not posted to the Agoric oracle contract +- `payment`: The user-provided fee in $LINK - `result`: The result to return to the Agoric oracle contract - ## Output ```json { "jobRunID": "278c97ffadb54a5bbb93cfec5f7b5503", - "data": , + "data": { result }, "statusCode": 200 } ``` diff --git a/agoric/adapter.js b/agoric/adapter.js index 70413a9523..df82cb4ac9 100644 --- a/agoric/adapter.js +++ b/agoric/adapter.js @@ -1,3 +1,4 @@ +const { Validator } = require('@chainlink/external-adapter') const http = require('http') const oracleAPI = process.env.AG_SOLO_ORACLE_URL @@ -6,6 +7,12 @@ if (!oracleAPI) { } const oracleUrl = new URL(oracleAPI) +const customParams = { + agoric_oracle_query_id: false, + result: false, + payment: false, +} + const Nat = (n) => { if (!Number.isSafeInteger(n)) { throw Error(`${n} is not a safe integer`) @@ -64,18 +71,29 @@ const send = (obj) => }) const execute = async (input, callback) => { - const queryId = input.data.agoric_oracle_query_id + let queryId + let jobRunID = input.id + let errorStatus = 400 try { + const validator = new Validator(input, customParams) + if (validator.error) { + errorStatus = validator.error.statusCode + throw validator.error + } + jobRunID = validator.validated.id + queryId = validator.validated.data.agoric_oracle_query_id + const result = validator.validated.data.result + const payment = validator.validated.data.payment if (queryId) { - const requiredFee = getRequiredFee(input.data.payment) + const requiredFee = getRequiredFee(payment) await send({ type: 'oracleServer/reply', - data: { queryId, reply: input.data.result, requiredFee }, + data: { queryId, reply: result, requiredFee }, }) } callback(200, { - jobRunID: input.id, - data: input.data, + jobRunID, + data: { result }, statusCode: 200, }) } catch (e) { @@ -87,10 +105,10 @@ const execute = async (input, callback) => { }) } callback(400, { - jobRunID: input.id, + jobRunID, status: 'errored', error: error, - statusCode: 400, + statusCode: errorStatus, }) } } From 0e41f3bff598bcd53d958357654fec0885359fc7 Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Thu, 22 Oct 2020 11:15:38 -0600 Subject: [PATCH 04/26] test: remove agoric/test --- agoric/package.json | 6 +-- agoric/test/adapter_test.js | 85 ------------------------------------- 2 files changed, 3 insertions(+), 88 deletions(-) delete mode 100644 agoric/test/adapter_test.js diff --git a/agoric/package.json b/agoric/package.json index 1c3121cb73..0ee23a1f6f 100644 --- a/agoric/package.json +++ b/agoric/package.json @@ -7,9 +7,9 @@ "server": "node -e 'require(\"./index.js\").server()'", "lint": "eslint --ignore-path ../.eslintignore . --ext .js,.jsx,.ts,.tsx", "lint:fix": "eslint --ignore-path ../.eslintignore . --ext .js,.jsx,.ts,.tsx --fix", - "test": "yarn _mocha --timeout 0", - "test:unit": "yarn _mocha --grep @integration --invert --timeout 0", - "test:integration": "yarn _mocha --grep @integration --timeout 0" + "test": "exit 0 || yarn _mocha --timeout 0", + "test:unit": "exit 0 || yarn _mocha --grep @integration --invert --timeout 0", + "test:integration": "exit 0 || yarn _mocha --grep @integration --timeout 0" }, "dependencies": {} } diff --git a/agoric/test/adapter_test.js b/agoric/test/adapter_test.js deleted file mode 100644 index 3bdd1b9f2f..0000000000 --- a/agoric/test/adapter_test.js +++ /dev/null @@ -1,85 +0,0 @@ -const { assert } = require('chai') -const { assertSuccess, assertError } = require('@chainlink/external-adapter') -const { execute } = require('../adapter') - -describe('execute', () => { - const jobID = '1' - - context('successful calls @integration', () => { - const requests = [ - { - name: 'id not supplied', - testData: { data: { base: 'ETH', quote: 'USD' } }, - }, - { - name: 'base/quote', - testData: { id: jobID, data: { base: 'ETH', quote: 'USD' } }, - }, - { - name: 'from/to', - testData: { id: jobID, data: { from: 'ETH', to: 'USD' } }, - }, - { - name: 'coin/market', - testData: { id: jobID, data: { coin: 'ETH', market: 'USD' } }, - }, - ] - - requests.forEach((req) => { - it(`${req.name}`, (done) => { - execute(req.testData, (statusCode, data) => { - assertSuccess({ expected: 200, actual: statusCode }, data, jobID) - assert.isAbove(data.result, 0) - assert.isAbove(data.data.result, 0) - done() - }) - }) - }) - }) - - context('validation error', () => { - const requests = [ - { name: 'empty body', testData: {} }, - { name: 'empty data', testData: { data: {} } }, - { - name: 'base not supplied', - testData: { id: jobID, data: { quote: 'USD' } }, - }, - { - name: 'quote not supplied', - testData: { id: jobID, data: { base: 'ETH' } }, - }, - ] - - requests.forEach((req) => { - it(`${req.name}`, (done) => { - execute(req.testData, (statusCode, data) => { - assertError({ expected: 400, actual: statusCode }, data, jobID) - done() - }) - }) - }) - }) - - context('error calls @integration', () => { - const requests = [ - { - name: 'unknown base', - testData: { id: jobID, data: { base: 'not_real', quote: 'USD' } }, - }, - { - name: 'unknown quote', - testData: { id: jobID, data: { base: 'ETH', quote: 'not_real' } }, - }, - ] - - requests.forEach((req) => { - it(`${req.name}`, (done) => { - execute(req.testData, (statusCode, data) => { - assertError({ expected: 500, actual: statusCode }, data, jobID) - done() - }) - }) - }) - }) -}) From 9c644280c9bfcb3abf32433d6eac3dfc1bd6403e Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Thu, 22 Oct 2020 12:35:07 -0600 Subject: [PATCH 05/26] fix: another attempt to plumb through the 'Task Run Data' --- agoric/adapter.js | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/agoric/adapter.js b/agoric/adapter.js index df82cb4ac9..37971f4f77 100644 --- a/agoric/adapter.js +++ b/agoric/adapter.js @@ -8,9 +8,9 @@ if (!oracleAPI) { const oracleUrl = new URL(oracleAPI) const customParams = { - agoric_oracle_query_id: false, - result: false, - payment: false, + queryId: ['request_id'], + result: ['result'], + payment: ['payment'], } const Nat = (n) => { @@ -81,22 +81,21 @@ const execute = async (input, callback) => { throw validator.error } jobRunID = validator.validated.id - queryId = validator.validated.data.agoric_oracle_query_id - const result = validator.validated.data.result - const payment = validator.validated.data.payment - if (queryId) { - const requiredFee = getRequiredFee(payment) - await send({ - type: 'oracleServer/reply', - data: { queryId, reply: result, requiredFee }, - }) - } + queryId = validator.validated.data.queryId + const { result, payment } = validator.validated.data + const requiredFee = getRequiredFee(payment) + await send({ + type: 'oracleServer/reply', + data: { queryId, reply: result, requiredFee }, + }) callback(200, { jobRunID, data: { result }, + status: 'success', statusCode: 200, }) } catch (e) { + console.error('Have error', e) const error = `${(e && e.stack) || e}` if (queryId) { send({ @@ -107,7 +106,7 @@ const execute = async (input, callback) => { callback(400, { jobRunID, status: 'errored', - error: error, + error, statusCode: errorStatus, }) } From 161914fd725e3a5e15393e882bef1652b4c605ed Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Thu, 22 Oct 2020 13:22:30 -0600 Subject: [PATCH 06/26] fix: use the promise-based API --- agoric/README.md | 5 ++-- agoric/adapter.js | 62 +++++++++++++++++------------------------------ 2 files changed, 24 insertions(+), 43 deletions(-) diff --git a/agoric/README.md b/agoric/README.md index 53cfc84233..8aaaa2ae68 100644 --- a/agoric/README.md +++ b/agoric/README.md @@ -10,8 +10,7 @@ Set the `$AG_SOLO_ORACLE` environment variable to something like: http://localhost:8000/api/oracle -- `agoric_oracle_query_id`: The Agoric oracle queryId. If unset, the result is not - posted to the Agoric oracle contract +- `request_id`: The Agoric oracle queryId - `payment`: The user-provided fee in $LINK - `result`: The result to return to the Agoric oracle contract @@ -20,7 +19,7 @@ Set the `$AG_SOLO_ORACLE` environment variable to something like: http://localho ```json { "jobRunID": "278c97ffadb54a5bbb93cfec5f7b5503", - "data": { result }, + "data": { "result": "..." }, "statusCode": 200 } ``` diff --git a/agoric/adapter.js b/agoric/adapter.js index 37971f4f77..276de40d66 100644 --- a/agoric/adapter.js +++ b/agoric/adapter.js @@ -1,4 +1,4 @@ -const { Validator } = require('@chainlink/external-adapter') +const { Requester, Validator } = require('@chainlink/external-adapter') const http = require('http') const oracleAPI = process.env.AG_SOLO_ORACLE_URL @@ -70,46 +70,28 @@ const send = (obj) => req.end() }) -const execute = async (input, callback) => { - let queryId - let jobRunID = input.id - let errorStatus = 400 - try { - const validator = new Validator(input, customParams) - if (validator.error) { - errorStatus = validator.error.statusCode - throw validator.error - } - jobRunID = validator.validated.id - queryId = validator.validated.data.queryId - const { result, payment } = validator.validated.data - const requiredFee = getRequiredFee(payment) - await send({ - type: 'oracleServer/reply', - data: { queryId, reply: result, requiredFee }, - }) - callback(200, { - jobRunID, - data: { result }, - status: 'success', - statusCode: 200, - }) - } catch (e) { - console.error('Have error', e) - const error = `${(e && e.stack) || e}` - if (queryId) { - send({ - type: 'oracleServer/error', - data: { queryId, error }, - }) - } - callback(400, { - jobRunID, - status: 'errored', - error, - statusCode: errorStatus, - }) +const execute = async (request) => { + const validator = new Validator(request, customParams) + if (validator.error) { + throw validator.error } + + const jobRunID = validator.validated.id + const queryId = validator.validated.data.queryId + + const { result, payment } = validator.validated.data + const requiredFee = getRequiredFee(payment) + + await send({ + type: 'oracleServer/reply', + data: { queryId, reply: result, requiredFee }, + }) + + return Requester.success(jobRunID, { + data: { result }, + result, + status: 200, + }) } module.exports.execute = execute From d232abca25f0570abaf18cf0e1d25c46c0070d39 Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Thu, 22 Oct 2020 16:44:20 -0600 Subject: [PATCH 07/26] fix: make request_id numeric --- agoric/adapter.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/agoric/adapter.js b/agoric/adapter.js index 276de40d66..bcf4ac1de1 100644 --- a/agoric/adapter.js +++ b/agoric/adapter.js @@ -8,7 +8,7 @@ if (!oracleAPI) { const oracleUrl = new URL(oracleAPI) const customParams = { - queryId: ['request_id'], + request_id: ['request_id'], result: ['result'], payment: ['payment'], } @@ -77,9 +77,9 @@ const execute = async (request) => { } const jobRunID = validator.validated.id - const queryId = validator.validated.data.queryId - const { result, payment } = validator.validated.data + const { request_id, result, payment } = validator.validated.data + const queryId = Number(request_id) const requiredFee = getRequiredFee(payment) await send({ From 1881af856e546fdade5fd0355525d20fcd64a7b9 Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Thu, 22 Oct 2020 17:38:50 -0600 Subject: [PATCH 08/26] fix: cast payment from number to string --- agoric/adapter.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/agoric/adapter.js b/agoric/adapter.js index bcf4ac1de1..81f44d221f 100644 --- a/agoric/adapter.js +++ b/agoric/adapter.js @@ -29,7 +29,8 @@ if (LINK_AGORIC_DECIMALS > LINK_DECIMALS) { ) } -const getRequiredFee = (str) => { +const getRequiredFee = (value) => { + const str = String(value || 0) const digits = str const significant = digits.substr( 0, From 0f6ac662b514686f432bfc3947328abd3890c832 Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Wed, 4 Nov 2020 10:59:10 -0600 Subject: [PATCH 09/26] fix: properly export the Agoric async adapter and string queryId --- agoric/adapter.js | 3 +-- agoric/index.js | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/agoric/adapter.js b/agoric/adapter.js index 81f44d221f..250db270c0 100644 --- a/agoric/adapter.js +++ b/agoric/adapter.js @@ -79,8 +79,7 @@ const execute = async (request) => { const jobRunID = validator.validated.id - const { request_id, result, payment } = validator.validated.data - const queryId = Number(request_id) + const { request_id: queryId, result, payment } = validator.validated.data const requiredFee = getRequiredFee(payment) await send({ diff --git a/agoric/index.js b/agoric/index.js index d5dfd92b9f..e0d153032b 100644 --- a/agoric/index.js +++ b/agoric/index.js @@ -1,4 +1,4 @@ -const { expose } = require('@chainlink/ea-bootstrap') +const { expose, util } = require('@chainlink/ea-bootstrap') const { execute } = require('./adapter') -module.exports = expose(execute) +module.exports = expose(util.wrapExecute(execute)) From aa83764c262a68e0f21ed93b1710116dc02dd3ab Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Tue, 10 Nov 2020 11:00:29 -0600 Subject: [PATCH 10/26] refactor: separate concerns and add tests --- agoric/.eslintrc.js | 3 + agoric/index.js | 4 - agoric/package.json | 38 ++++++-- agoric/{adapter.js => src/adapter.ts} | 51 ++-------- agoric/src/httpSender.ts | 34 +++++++ agoric/src/index.ts | 14 +++ agoric/test/adapter.test.ts | 128 ++++++++++++++++++++++++++ agoric/tsconfig.json | 10 ++ 8 files changed, 229 insertions(+), 53 deletions(-) create mode 100644 agoric/.eslintrc.js delete mode 100644 agoric/index.js rename agoric/{adapter.js => src/adapter.ts} (52%) create mode 100644 agoric/src/httpSender.ts create mode 100644 agoric/src/index.ts create mode 100644 agoric/test/adapter.test.ts create mode 100644 agoric/tsconfig.json diff --git a/agoric/.eslintrc.js b/agoric/.eslintrc.js new file mode 100644 index 0000000000..11f16f9a15 --- /dev/null +++ b/agoric/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + ...require('../.eslintrc.ts.js'), +} diff --git a/agoric/index.js b/agoric/index.js deleted file mode 100644 index e0d153032b..0000000000 --- a/agoric/index.js +++ /dev/null @@ -1,4 +0,0 @@ -const { expose, util } = require('@chainlink/ea-bootstrap') -const { execute } = require('./adapter') - -module.exports = expose(util.wrapExecute(execute)) diff --git a/agoric/package.json b/agoric/package.json index 0ee23a1f6f..80fb9157ca 100644 --- a/agoric/package.json +++ b/agoric/package.json @@ -1,15 +1,39 @@ { "name": "@agoric/chainlink-external-adapter", - "version": "0.1.0", + "version": "0.2.0", "license": "MIT", - "main": "index.js", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "files": [ + "dist" + ], + "repository": { + "url": "https://github.com/smartcontractkit/external-adapters-js", + "type": "git" + }, "scripts": { - "server": "node -e 'require(\"./index.js\").server()'", + "prepublishOnly": "yarn build && yarn test:unit", + "build": "tsc", "lint": "eslint --ignore-path ../.eslintignore . --ext .js,.jsx,.ts,.tsx", "lint:fix": "eslint --ignore-path ../.eslintignore . --ext .js,.jsx,.ts,.tsx --fix", - "test": "exit 0 || yarn _mocha --timeout 0", - "test:unit": "exit 0 || yarn _mocha --grep @integration --invert --timeout 0", - "test:integration": "exit 0 || yarn _mocha --grep @integration --timeout 0" + "test": "mocha --exit --timeout 3000 -r ts-node/register 'test/**/*.test.ts'", + "test:unit": "mocha --exit --grep @integration --invert -r ts-node/register 'test/**/*.test.ts'", + "test:integration": "mocha --exit --timeout 3000 --grep @integration -r ts-node/register 'test/**/*.test.ts'", + "server": "node -e 'require(\"./index.js\").server()'", + "server:dist": "node -e 'require(\"./dist/index.js\").server()'", + "start": "yarn server:dist" + }, + "devDependencies": { + "@types/chai": "^4.2.11", + "@types/express": "^4.17.6", + "@types/mocha": "^7.0.2", + "@types/node": "^14.0.13", + "@typescript-eslint/eslint-plugin": "^3.9.0", + "@typescript-eslint/parser": "^3.9.0", + "ts-node": "^8.10.2", + "typescript": "^3.9.7" }, - "dependencies": {} + "dependencies": { + + } } diff --git a/agoric/adapter.js b/agoric/src/adapter.ts similarity index 52% rename from agoric/adapter.js rename to agoric/src/adapter.ts index 250db270c0..e6603f39c0 100644 --- a/agoric/adapter.js +++ b/agoric/src/adapter.ts @@ -1,11 +1,6 @@ -const { Requester, Validator } = require('@chainlink/external-adapter') -const http = require('http') - -const oracleAPI = process.env.AG_SOLO_ORACLE_URL -if (!oracleAPI) { - throw Error(`Must supply $AG_SOLO_ORACLE_URL`) -} -const oracleUrl = new URL(oracleAPI) +import { Execute } from '@chainlink/types' +import { Requester, Validator } from '@chainlink/external-adapter' +import { HTTPSender } from './httpSender'; const customParams = { request_id: ['request_id'], @@ -13,7 +8,7 @@ const customParams = { payment: ['payment'], } -const Nat = (n) => { +const Nat = (n: number) => { if (!Number.isSafeInteger(n)) { throw Error(`${n} is not a safe integer`) } @@ -29,50 +24,24 @@ if (LINK_AGORIC_DECIMALS > LINK_DECIMALS) { ) } -const getRequiredFee = (value) => { +export const getRequiredFee = (value: string | number) => { const str = String(value || 0) const digits = str const significant = digits.substr( 0, Math.max(0, digits.length - (LINK_DECIMALS - LINK_AGORIC_DECIMALS)), ) + const roundUp = digits[significant.length] && parseInt(digits[significant.length], 10) >= 5 - let requiredFee = Nat(parseInt(significant, 10)) + let requiredFee = Nat(parseInt(significant || '0', 10)) if (roundUp) { requiredFee += 1 } return Nat(requiredFee) } -const send = (obj) => - new Promise((resolve, reject) => { - const data = JSON.stringify(obj) - const req = http.request( - { - hostname: oracleUrl.hostname, - port: oracleUrl.port, - path: oracleUrl.pathname, - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Content-Length': data.length, - }, - }, - (res) => { - if (res.statusCode === 200) { - resolve(res.statusCode) - } else { - reject(res.statusCode) - } - }, - ) - req.on('error', reject) - req.write(data) - req.end() - }) - -const execute = async (request) => { - const validator = new Validator(request, customParams) +export const makeExecute: (send: HTTPSender) => Execute = send => async (input) => { + const validator = new Validator(input, customParams) if (validator.error) { throw validator.error } @@ -93,5 +62,3 @@ const execute = async (request) => { status: 200, }) } - -module.exports.execute = execute diff --git a/agoric/src/httpSender.ts b/agoric/src/httpSender.ts new file mode 100644 index 0000000000..12a2a5b7dc --- /dev/null +++ b/agoric/src/httpSender.ts @@ -0,0 +1,34 @@ +import { URL } from 'url' +import http from 'http' + +export type HTTPSender = (obj: { type: string; data: unknown }) => Promise + +export const makeHTTPSender: (url: string) => HTTPSender = (url) => { + const urlObj = new URL(url) + return (obj) => + new Promise((resolve, reject) => { + const data = JSON.stringify(obj) + const req = http.request( + { + hostname: urlObj.hostname, + port: urlObj.port, + path: urlObj.pathname, + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Content-Length': data.length, + }, + }, + (res) => { + if (res.statusCode === 200) { + resolve(res.statusCode) + } else { + reject(res.statusCode) + } + }, + ) + req.on('error', reject) + req.write(data) + req.end() + }) +} diff --git a/agoric/src/index.ts b/agoric/src/index.ts new file mode 100644 index 0000000000..955e2d7c77 --- /dev/null +++ b/agoric/src/index.ts @@ -0,0 +1,14 @@ +import { expose, util } from '@chainlink/ea-bootstrap' +import { makeExecute } from './adapter' +import { makeHTTPSender } from './httpSender' + +const oracleAPI = process.env.AG_SOLO_ORACLE_URL +if (!oracleAPI) { + throw Error(`Must supply $AG_SOLO_ORACLE_URL`) +} + +const send = makeHTTPSender(oracleAPI) + +const execute = makeExecute(send) + +export = { NAME: 'Agoric', execute, ...expose(util.wrapExecute(execute)) } diff --git a/agoric/test/adapter.test.ts b/agoric/test/adapter.test.ts new file mode 100644 index 0000000000..14aedf7189 --- /dev/null +++ b/agoric/test/adapter.test.ts @@ -0,0 +1,128 @@ +import { assert } from 'chai' +import { Requester, assertSuccess, assertError } from '@chainlink/external-adapter' +import { AdapterRequest } from '@chainlink/types' +import { makeExecute } from '../src/adapter' +import { HTTPSender } from '../src/httpSender' + +describe('execute', () => { + const jobID = '1' + const mockSend: HTTPSender = obj => Promise.resolve(200); + + context.skip('successful calls @integration', () => { + // TODO: Set up a web server to receive the posts. + const execute = makeExecute(mockSend); + const requests = [ + { + name: 'id not supplied', + testData: { data: { base: 'ETH', quote: 'USD' } }, + }, + { + name: 'base/quote', + testData: { id: jobID, data: { base: 'ETH', quote: 'USD' } }, + }, + { + name: 'from/to', + testData: { id: jobID, data: { from: 'ETH', to: 'USD' } }, + }, + { + name: 'coin/market', + testData: { id: jobID, data: { coin: 'ETH', market: 'USD' } }, + }, + ] + + requests.forEach((req) => { + it(`${req.name}`, async () => { + const data = await execute(req.testData as AdapterRequest) + assertSuccess({ expected: 200, actual: data.statusCode }, data, jobID) + assert.isAbove(data.result, 0) + assert.isAbove(data.data.result, 0) + }) + }) + }) + + context('validation', () => { + const execute = makeExecute(mockSend) + const requests = [ + { + name: 'normal request_id', + status: 200, + testData: { + id: jobID, + data: { request_id: '4', payment: '10000000000000000', result: 'abc' }, + }, + }, + { + name: 'push request_id', + status: 200, + testData: { + id: jobID, + data: { request_id: 'push-3', payment: '10000000000000000', result: 'def' }, + }, + }, + { + name: 'zero payment', + status: 200, + testData: { id: jobID, data: { request_id: '99', payment: '0', result: 'ghi' } }, + }, + { status: 400, name: 'empty body', testData: {} }, + { status: 400, name: 'empty data', testData: { data: {} } }, + { + status: 400, + name: 'payment not supplied', + testData: { id: jobID, data: { request_id: '3', result: 'abc' } }, + }, + { + status: 400, + name: 'request_id not supplied', + testData: { id: jobID, data: { payment: '0', result: 'def' } }, + }, + { + status: 400, + name: 'result not supplied', + testData: { id: jobID, data: { request_id: '3', payment: '0' } }, + }, + ] + + requests.forEach((req) => { + it(`${req.name}`, async () => { + try { + const data = await execute(req.testData as AdapterRequest) + console.log(data) + assertSuccess({ expected: req.status, actual: data.statusCode }, data, jobID) + } catch (error) { + const errorResp = Requester.errored(jobID, error) + assertError( + { expected: req.status, actual: errorResp.statusCode }, + errorResp, + jobID, + ) + } + }) + }) + }) + + context.skip('error calls @integration', () => { + const execute = makeExecute(mockSend) + const requests = [ + { + name: 'unknown base', + testData: { id: jobID, data: { base: 'not_real', quote: 'USD' } }, + }, + { + name: 'unknown quote', + testData: { id: jobID, data: { base: 'ETH', quote: 'not_real' } }, + }, + ] + + requests.forEach((req) => { + it(`${req.name}`, async () => { + try { + await execute(req.testData as AdapterRequest) + } catch (error) { + const errorResp = Requester.errored(jobID, error) + assertError({ expected: 500, actual: errorResp.statusCode }, errorResp, jobID) + } + }) + }) + }) +}) diff --git a/agoric/tsconfig.json b/agoric/tsconfig.json new file mode 100644 index 0000000000..3b4ccf41fa --- /dev/null +++ b/agoric/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "typeRoots": ["../node_modules/@types", "../typings", "./typings"] + }, + "include": ["src/**/*"], + "exclude": ["dist", "**/*.spec.ts", "**/*.test.ts"] +} From 94f9d4610fdc777dad2d7bd49fefeed2070b95ad Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Tue, 10 Nov 2020 11:40:30 -0600 Subject: [PATCH 11/26] fix: more robust adapter; send errors to the oracleServer --- agoric/src/adapter.ts | 46 ++++++++------ agoric/test/adapter.test.ts | 118 ++++++++++++++++++++++-------------- 2 files changed, 98 insertions(+), 66 deletions(-) diff --git a/agoric/src/adapter.ts b/agoric/src/adapter.ts index e6603f39c0..7cc89854b8 100644 --- a/agoric/src/adapter.ts +++ b/agoric/src/adapter.ts @@ -1,6 +1,6 @@ import { Execute } from '@chainlink/types' import { Requester, Validator } from '@chainlink/external-adapter' -import { HTTPSender } from './httpSender'; +import { HTTPSender } from './httpSender' const customParams = { request_id: ['request_id'], @@ -24,7 +24,7 @@ if (LINK_AGORIC_DECIMALS > LINK_DECIMALS) { ) } -export const getRequiredFee = (value: string | number) => { +export const getRequiredFee = (value: string | number): number => { const str = String(value || 0) const digits = str const significant = digits.substr( @@ -40,25 +40,33 @@ export const getRequiredFee = (value: string | number) => { return Nat(requiredFee) } -export const makeExecute: (send: HTTPSender) => Execute = send => async (input) => { - const validator = new Validator(input, customParams) - if (validator.error) { - throw validator.error - } +export const makeExecute: (send: HTTPSender) => Execute = (send) => async (input) => { + try { + const validator = new Validator(input, customParams) + if (validator.error) { + throw validator.error + } - const jobRunID = validator.validated.id + const jobRunID = validator.validated.id - const { request_id: queryId, result, payment } = validator.validated.data - const requiredFee = getRequiredFee(payment) + const { request_id: queryId, result, payment } = validator.validated.data + const requiredFee = getRequiredFee(payment) - await send({ - type: 'oracleServer/reply', - data: { queryId, reply: result, requiredFee }, - }) + await send({ + type: 'oracleServer/reply', + data: { queryId, reply: result, requiredFee }, + }) - return Requester.success(jobRunID, { - data: { result }, - result, - status: 200, - }) + return Requester.success(jobRunID, { + data: { result }, + result, + status: 200, + }) + } catch (e) { + await send({ + type: 'oracleServer/error', + data: { queryId: input.data && input.data.request_id, error: `${(e && e.message) || e}` }, + }) + throw e + } } diff --git a/agoric/test/adapter.test.ts b/agoric/test/adapter.test.ts index 14aedf7189..b1f1d60d90 100644 --- a/agoric/test/adapter.test.ts +++ b/agoric/test/adapter.test.ts @@ -6,41 +6,95 @@ import { HTTPSender } from '../src/httpSender' describe('execute', () => { const jobID = '1' - const mockSend: HTTPSender = obj => Promise.resolve(200); - context.skip('successful calls @integration', () => { - // TODO: Set up a web server to receive the posts. - const execute = makeExecute(mockSend); + context('HTTP calls', () => { const requests = [ { - name: 'id not supplied', - testData: { data: { base: 'ETH', quote: 'USD' } }, + name: 'request_id not supplied', + status: 400, + testData: { data: { payment: '0', result: 'abc' } }, + sends: [ + { + type: 'oracleServer/error', + data: { + queryId: undefined, + error: `Required parameter not supplied: request_id`, + }, + }, + ], }, { - name: 'base/quote', - testData: { id: jobID, data: { base: 'ETH', quote: 'USD' } }, + name: 'payment not supplied', + status: 400, + testData: { data: { request_id: '3939', result: 'abc' } }, + sends: [ + { + type: 'oracleServer/error', + data: { + queryId: '3939', + error: `Required parameter not supplied: payment`, + }, + }, + ], }, { - name: 'from/to', - testData: { id: jobID, data: { from: 'ETH', to: 'USD' } }, + name: 'push request', + status: 200, + testData: { id: jobID, data: { request_id: 'push-3', payment: '12', result: 'abc' } }, + sends: [ + { + type: 'oracleServer/reply', + data: { + queryId: 'push-3', + reply: 'abc', + requiredFee: 0, + }, + }, + ], }, { - name: 'coin/market', - testData: { id: jobID, data: { coin: 'ETH', market: 'USD' } }, + name: 'normal request', + status: 200, + testData: { + id: jobID, + data: { request_id: 'push-3', payment: '120000000000000', result: 'abc' }, + }, + sends: [ + { + type: 'oracleServer/reply', + data: { + queryId: 'push-3', + reply: 'abc', + requiredFee: 120, + }, + }, + ], }, ] requests.forEach((req) => { it(`${req.name}`, async () => { - const data = await execute(req.testData as AdapterRequest) - assertSuccess({ expected: 200, actual: data.statusCode }, data, jobID) - assert.isAbove(data.result, 0) - assert.isAbove(data.data.result, 0) + const sends: Array<{ type: string; data: unknown }> = [] + const spySend: HTTPSender = async (obj) => { + sends.push(obj) + return 200 + } + const execute = makeExecute(spySend) + + try { + const data = await execute(req.testData as AdapterRequest) + assertSuccess({ expected: req.status, actual: data.statusCode }, data, jobID) + } catch (error) { + const errorResp = Requester.errored(jobID, error) + assertError({ expected: req.status, actual: errorResp.statusCode }, errorResp, jobID) + } + assert.deepEqual(sends, req.sends) }) }) }) context('validation', () => { + const mockSend: HTTPSender = () => Promise.resolve(200) const execute = makeExecute(mockSend) const requests = [ { @@ -87,40 +141,10 @@ describe('execute', () => { it(`${req.name}`, async () => { try { const data = await execute(req.testData as AdapterRequest) - console.log(data) assertSuccess({ expected: req.status, actual: data.statusCode }, data, jobID) - } catch (error) { - const errorResp = Requester.errored(jobID, error) - assertError( - { expected: req.status, actual: errorResp.statusCode }, - errorResp, - jobID, - ) - } - }) - }) - }) - - context.skip('error calls @integration', () => { - const execute = makeExecute(mockSend) - const requests = [ - { - name: 'unknown base', - testData: { id: jobID, data: { base: 'not_real', quote: 'USD' } }, - }, - { - name: 'unknown quote', - testData: { id: jobID, data: { base: 'ETH', quote: 'not_real' } }, - }, - ] - - requests.forEach((req) => { - it(`${req.name}`, async () => { - try { - await execute(req.testData as AdapterRequest) } catch (error) { const errorResp = Requester.errored(jobID, error) - assertError({ expected: 500, actual: errorResp.statusCode }, errorResp, jobID) + assertError({ expected: req.status, actual: errorResp.statusCode }, errorResp, jobID) } }) }) From 2bb6e4f938412fe777d8ee06f878c46de1967042 Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Tue, 10 Nov 2020 12:44:51 -0600 Subject: [PATCH 12/26] feat!: surface errors from the oracle backend --- agoric/package.json | 1 + agoric/src/adapter.ts | 36 ++++++++++++++++++--- agoric/src/httpSender.ts | 44 ++++++++----------------- agoric/test/adapter.test.ts | 64 +++++++++++++++++++++++++++++++++++-- 4 files changed, 107 insertions(+), 38 deletions(-) diff --git a/agoric/package.json b/agoric/package.json index 80fb9157ca..a044ac028e 100644 --- a/agoric/package.json +++ b/agoric/package.json @@ -30,6 +30,7 @@ "@types/node": "^14.0.13", "@typescript-eslint/eslint-plugin": "^3.9.0", "@typescript-eslint/parser": "^3.9.0", + "axios": "^0.19.2", "ts-node": "^8.10.2", "typescript": "^3.9.7" }, diff --git a/agoric/src/adapter.ts b/agoric/src/adapter.ts index 7cc89854b8..8486484cfa 100644 --- a/agoric/src/adapter.ts +++ b/agoric/src/adapter.ts @@ -1,6 +1,6 @@ import { Execute } from '@chainlink/types' import { Requester, Validator } from '@chainlink/external-adapter' -import { HTTPSender } from './httpSender' +import { Action, HTTPSender, HTTPSenderReply } from './httpSender' const customParams = { request_id: ['request_id'], @@ -40,6 +40,30 @@ export const getRequiredFee = (value: string | number): number => { return Nat(requiredFee) } +export interface ActionResponseData { + success: boolean + error: unknown +} +export const assertGoodReply = (sentType: string, reply: HTTPSenderReply) => { + if (reply.status < 200 || reply.status >= 300) { + throw Error(`${sentType} reply status ${reply.status} is not 2xx`) + } + + const obj = reply.response as Action + if (!obj) { + throw Error(`${sentType} no response data`) + } + + if (obj.type !== `${sentType}Response`) { + throw Error(`${sentType} response type ${obj.type} is not ${sentType}Response`) + } + + const data = obj.data as ActionResponseData + if (!data.success) { + throw Error(`${obj.type} error ${data.error}`) + } +} + export const makeExecute: (send: HTTPSender) => Execute = (send) => async (input) => { try { const validator = new Validator(input, customParams) @@ -52,10 +76,12 @@ export const makeExecute: (send: HTTPSender) => Execute = (send) => async (input const { request_id: queryId, result, payment } = validator.validated.data const requiredFee = getRequiredFee(payment) - await send({ + const obj = { type: 'oracleServer/reply', data: { queryId, reply: result, requiredFee }, - }) + } + const reply = await send(obj) + assertGoodReply(obj.type, reply) return Requester.success(jobRunID, { data: { result }, @@ -63,10 +89,10 @@ export const makeExecute: (send: HTTPSender) => Execute = (send) => async (input status: 200, }) } catch (e) { - await send({ + send({ type: 'oracleServer/error', data: { queryId: input.data && input.data.request_id, error: `${(e && e.message) || e}` }, - }) + }).catch((e2) => console.error(`Cannot reflect error`, e, `to caller:`, e2)) throw e } } diff --git a/agoric/src/httpSender.ts b/agoric/src/httpSender.ts index 12a2a5b7dc..bd5b027921 100644 --- a/agoric/src/httpSender.ts +++ b/agoric/src/httpSender.ts @@ -1,34 +1,16 @@ -import { URL } from 'url' -import http from 'http' +import axios from 'axios' -export type HTTPSender = (obj: { type: string; data: unknown }) => Promise +export interface Action { + type: string + data: unknown +} +export interface HTTPSenderReply { + status: number + response: unknown +} +export type HTTPSender = (obj: Action) => Promise -export const makeHTTPSender: (url: string) => HTTPSender = (url) => { - const urlObj = new URL(url) - return (obj) => - new Promise((resolve, reject) => { - const data = JSON.stringify(obj) - const req = http.request( - { - hostname: urlObj.hostname, - port: urlObj.port, - path: urlObj.pathname, - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Content-Length': data.length, - }, - }, - (res) => { - if (res.statusCode === 200) { - resolve(res.statusCode) - } else { - reject(res.statusCode) - } - }, - ) - req.on('error', reject) - req.write(data) - req.end() - }) +export const makeHTTPSender: (url: string) => HTTPSender = (url) => async (obj) => { + const response = await axios.post(url, obj) + return { status: response.status, response: response.data } } diff --git a/agoric/test/adapter.test.ts b/agoric/test/adapter.test.ts index b1f1d60d90..51ff9cfe58 100644 --- a/agoric/test/adapter.test.ts +++ b/agoric/test/adapter.test.ts @@ -22,6 +22,12 @@ describe('execute', () => { }, }, ], + receive: { + type: 'oracleServer/errorResponse', + data: { + success: true, + }, + }, }, { name: 'payment not supplied', @@ -36,6 +42,12 @@ describe('execute', () => { }, }, ], + receive: { + type: 'oracleServer/errorResponse', + data: { + success: true, + }, + }, }, { name: 'push request', @@ -51,6 +63,12 @@ describe('execute', () => { }, }, ], + receive: { + type: 'oracleServer/replyResponse', + data: { + success: true, + }, + }, }, { name: 'normal request', @@ -69,6 +87,44 @@ describe('execute', () => { }, }, ], + receive: { + type: 'oracleServer/replyResponse', + data: { + success: true, + }, + }, + }, + { + name: 'bad request_id', + status: 500, + testData: { + id: jobID, + data: { request_id: 'bad', payment: '120000000000000', result: 'abc' }, + }, + sends: [ + { + type: 'oracleServer/reply', + data: { + queryId: 'bad', + reply: 'abc', + requiredFee: 120, + }, + }, + { + type: 'oracleServer/error', + data: { + queryId: 'bad', + error: 'oracleServer/reply reply status 500 is not 2xx', + }, + }, + ], + receive: { + type: 'oracleServer/replyResponse', + data: { + success: false, + error: 'unrecognized queryId bad', + }, + }, }, ] @@ -77,7 +133,7 @@ describe('execute', () => { const sends: Array<{ type: string; data: unknown }> = [] const spySend: HTTPSender = async (obj) => { sends.push(obj) - return 200 + return { status: req.status, response: req.receive } } const execute = makeExecute(spySend) @@ -94,7 +150,11 @@ describe('execute', () => { }) context('validation', () => { - const mockSend: HTTPSender = () => Promise.resolve(200) + const mockSend: HTTPSender = () => + Promise.resolve({ + status: 200, + response: { type: 'oracleServer/replyResponse', data: { success: true } }, + }) const execute = makeExecute(mockSend) const requests = [ { From e376678fd886debfa88ddab8171c49f6f04b1ae2 Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Tue, 10 Nov 2020 20:56:06 -0600 Subject: [PATCH 13/26] fix: match with actual POST reply from the ag-solo --- agoric/src/adapter.ts | 27 ++++++++++--------------- agoric/test/adapter.test.ts | 40 +++++++------------------------------ 2 files changed, 17 insertions(+), 50 deletions(-) diff --git a/agoric/src/adapter.ts b/agoric/src/adapter.ts index 8486484cfa..3f18d4091e 100644 --- a/agoric/src/adapter.ts +++ b/agoric/src/adapter.ts @@ -40,27 +40,20 @@ export const getRequiredFee = (value: string | number): number => { return Nat(requiredFee) } -export interface ActionResponseData { - success: boolean - error: unknown +export interface PostReply { + ok: boolean + res?: unknown + rej?: unknown } -export const assertGoodReply = (sentType: string, reply: HTTPSenderReply) => { - if (reply.status < 200 || reply.status >= 300) { - throw Error(`${sentType} reply status ${reply.status} is not 2xx`) - } - const obj = reply.response as Action - if (!obj) { - throw Error(`${sentType} no response data`) - } - - if (obj.type !== `${sentType}Response`) { - throw Error(`${sentType} response type ${obj.type} is not ${sentType}Response`) +export const assertGoodReply = (sentType: string, reply: HTTPSenderReply): void => { + if (reply.status < 200 || reply.status >= 300) { + throw Error(`${sentType} status ${reply.status} is not 2xx`) } - const data = obj.data as ActionResponseData - if (!data.success) { - throw Error(`${obj.type} error ${data.error}`) + const pr = reply.response as PostReply + if (!pr.ok) { + throw Error(`${sentType} response failed: ${pr.rej}`) } } diff --git a/agoric/test/adapter.test.ts b/agoric/test/adapter.test.ts index 51ff9cfe58..07c012a03e 100644 --- a/agoric/test/adapter.test.ts +++ b/agoric/test/adapter.test.ts @@ -22,12 +22,7 @@ describe('execute', () => { }, }, ], - receive: { - type: 'oracleServer/errorResponse', - data: { - success: true, - }, - }, + receive: { ok: true, res: true }, }, { name: 'payment not supplied', @@ -42,12 +37,7 @@ describe('execute', () => { }, }, ], - receive: { - type: 'oracleServer/errorResponse', - data: { - success: true, - }, - }, + receive: { ok: true, res: true }, }, { name: 'push request', @@ -63,12 +53,7 @@ describe('execute', () => { }, }, ], - receive: { - type: 'oracleServer/replyResponse', - data: { - success: true, - }, - }, + receive: { ok: true, res: true }, }, { name: 'normal request', @@ -87,12 +72,7 @@ describe('execute', () => { }, }, ], - receive: { - type: 'oracleServer/replyResponse', - data: { - success: true, - }, - }, + receive: { ok: true, res: true }, }, { name: 'bad request_id', @@ -114,17 +94,11 @@ describe('execute', () => { type: 'oracleServer/error', data: { queryId: 'bad', - error: 'oracleServer/reply reply status 500 is not 2xx', + error: 'oracleServer/reply status 500 is not 2xx', }, }, ], - receive: { - type: 'oracleServer/replyResponse', - data: { - success: false, - error: 'unrecognized queryId bad', - }, - }, + receive: { ok: false, err: 'unrecognized queryId bad' }, }, ] @@ -153,7 +127,7 @@ describe('execute', () => { const mockSend: HTTPSender = () => Promise.resolve({ status: 200, - response: { type: 'oracleServer/replyResponse', data: { success: true } }, + response: { ok: true, res: true }, }) const execute = makeExecute(mockSend) const requests = [ From 4b3b92ecf58bdd35952b88104485f58ce6f371b3 Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Tue, 10 Nov 2020 22:36:18 -0600 Subject: [PATCH 14/26] fix: use AdapterErrors to surface errors to the node operator --- agoric/src/adapter.ts | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/agoric/src/adapter.ts b/agoric/src/adapter.ts index 3f18d4091e..2e75778a91 100644 --- a/agoric/src/adapter.ts +++ b/agoric/src/adapter.ts @@ -1,6 +1,6 @@ import { Execute } from '@chainlink/types' -import { Requester, Validator } from '@chainlink/external-adapter' -import { Action, HTTPSender, HTTPSenderReply } from './httpSender' +import { Requester, Validator, AdapterError } from '@chainlink/external-adapter' +import { HTTPSender, HTTPSenderReply } from './httpSender' const customParams = { request_id: ['request_id'], @@ -74,6 +74,7 @@ export const makeExecute: (send: HTTPSender) => Execute = (send) => async (input data: { queryId, reply: result, requiredFee }, } const reply = await send(obj) + assertGoodReply(obj.type, reply) return Requester.success(jobRunID, { @@ -82,10 +83,20 @@ export const makeExecute: (send: HTTPSender) => Execute = (send) => async (input status: 200, }) } catch (e) { + console.error(`Agoric adapter error:`, e) send({ type: 'oracleServer/error', data: { queryId: input.data && input.data.request_id, error: `${(e && e.message) || e}` }, - }).catch((e2) => console.error(`Cannot reflect error`, e, `to caller:`, e2)) - throw e + }).catch((e2) => console.error(`Cannot reflect error to caller:`, e2)) + + if (e instanceof AdapterError) { + throw e + } + throw new AdapterError({ + jobRunID: input.id, + statusCode: 500, + message: `${(e && e.message) || e}`, + cause: e, + }) } } From 90bca323a8dcbf4b1b9cb0fdf8df823431497102 Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Fri, 13 Nov 2020 10:39:07 -0600 Subject: [PATCH 15/26] test: add integration test --- agoric/src/adapter.ts | 10 +- agoric/src/httpSender.ts | 11 +- agoric/test/adapter.test.ts | 232 ++++++++++++++++++++++-------------- 3 files changed, 161 insertions(+), 92 deletions(-) diff --git a/agoric/src/adapter.ts b/agoric/src/adapter.ts index 2e75778a91..79a608309e 100644 --- a/agoric/src/adapter.ts +++ b/agoric/src/adapter.ts @@ -83,10 +83,14 @@ export const makeExecute: (send: HTTPSender) => Execute = (send) => async (input status: 200, }) } catch (e) { - console.error(`Agoric adapter error:`, e) - send({ + const queryId = input.data && input.data.request_id + let rest + if (queryId !== undefined) { + rest = { queryId } + } + await send({ type: 'oracleServer/error', - data: { queryId: input.data && input.data.request_id, error: `${(e && e.message) || e}` }, + data: { error: `${(e && e.message) || e}`, ...rest }, }).catch((e2) => console.error(`Cannot reflect error to caller:`, e2)) if (e instanceof AdapterError) { diff --git a/agoric/src/httpSender.ts b/agoric/src/httpSender.ts index bd5b027921..278ae3f2f6 100644 --- a/agoric/src/httpSender.ts +++ b/agoric/src/httpSender.ts @@ -11,6 +11,15 @@ export interface HTTPSenderReply { export type HTTPSender = (obj: Action) => Promise export const makeHTTPSender: (url: string) => HTTPSender = (url) => async (obj) => { - const response = await axios.post(url, obj) + let response + try { + response = await axios.post(url, obj) + } catch (e) { + if (e.response) { + response = e.response + } else { + throw e + } + } return { status: response.status, response: response.data } } diff --git a/agoric/test/adapter.test.ts b/agoric/test/adapter.test.ts index 07c012a03e..065ad9850d 100644 --- a/agoric/test/adapter.test.ts +++ b/agoric/test/adapter.test.ts @@ -2,109 +2,165 @@ import { assert } from 'chai' import { Requester, assertSuccess, assertError } from '@chainlink/external-adapter' import { AdapterRequest } from '@chainlink/types' import { makeExecute } from '../src/adapter' -import { HTTPSender } from '../src/httpSender' +import { HTTPSender, makeHTTPSender } from '../src/httpSender' + +import express from 'express' +import { Server } from 'http' + +interface Action { + type: string + data: unknown +} describe('execute', () => { const jobID = '1' - context('HTTP calls', () => { - const requests = [ - { - name: 'request_id not supplied', - status: 400, - testData: { data: { payment: '0', result: 'abc' } }, - sends: [ - { - type: 'oracleServer/error', - data: { - queryId: undefined, - error: `Required parameter not supplied: request_id`, - }, + const requests = [ + { + name: 'request_id not supplied', + status: 400, + testData: { data: { payment: '0', result: 'abc' } }, + sends: [ + { + type: 'oracleServer/error', + data: { + error: `Required parameter not supplied: request_id`, }, - ], - receive: { ok: true, res: true }, - }, - { - name: 'payment not supplied', - status: 400, - testData: { data: { request_id: '3939', result: 'abc' } }, - sends: [ - { - type: 'oracleServer/error', - data: { - queryId: '3939', - error: `Required parameter not supplied: payment`, - }, - }, - ], - receive: { ok: true, res: true }, - }, - { - name: 'push request', - status: 200, - testData: { id: jobID, data: { request_id: 'push-3', payment: '12', result: 'abc' } }, - sends: [ - { - type: 'oracleServer/reply', - data: { - queryId: 'push-3', - reply: 'abc', - requiredFee: 0, - }, + }, + ], + receive: { ok: true, res: true }, + }, + { + name: 'payment not supplied', + status: 400, + testData: { data: { request_id: '3939', result: 'abc' } }, + sends: [ + { + type: 'oracleServer/error', + data: { + queryId: '3939', + error: `Required parameter not supplied: payment`, }, - ], - receive: { ok: true, res: true }, - }, - { - name: 'normal request', - status: 200, - testData: { - id: jobID, - data: { request_id: 'push-3', payment: '120000000000000', result: 'abc' }, }, - sends: [ - { - type: 'oracleServer/reply', - data: { - queryId: 'push-3', - reply: 'abc', - requiredFee: 120, - }, + ], + receive: { ok: true, res: true }, + }, + { + name: 'push request', + status: 200, + testData: { id: jobID, data: { request_id: 'push-3', payment: '12', result: 'abc' } }, + sends: [ + { + type: 'oracleServer/reply', + data: { + queryId: 'push-3', + reply: 'abc', + requiredFee: 0, }, - ], - receive: { ok: true, res: true }, + }, + ], + receive: { ok: true, res: true }, + }, + { + name: 'normal request', + status: 200, + testData: { + id: jobID, + data: { request_id: 'push-3', payment: '120000000000000', result: 'abc' }, }, - { - name: 'bad request_id', - status: 500, - testData: { - id: jobID, - data: { request_id: 'bad', payment: '120000000000000', result: 'abc' }, + sends: [ + { + type: 'oracleServer/reply', + data: { + queryId: 'push-3', + reply: 'abc', + requiredFee: 120, + }, }, - sends: [ - { - type: 'oracleServer/reply', - data: { - queryId: 'bad', - reply: 'abc', - requiredFee: 120, - }, + ], + receive: { ok: true, res: true }, + }, + { + name: 'bad request_id', + status: 500, + testData: { + id: jobID, + data: { request_id: 'bad', payment: '120000000000000', result: 'abc' }, + }, + sends: [ + { + type: 'oracleServer/reply', + data: { + queryId: 'bad', + reply: 'abc', + requiredFee: 120, }, - { - type: 'oracleServer/error', - data: { - queryId: 'bad', - error: 'oracleServer/reply status 500 is not 2xx', - }, + }, + { + type: 'oracleServer/error', + data: { + queryId: 'bad', + error: 'oracleServer/reply status 500 is not 2xx', }, - ], - receive: { ok: false, err: 'unrecognized queryId bad' }, - }, - ] + }, + ], + receive: { ok: false, err: 'unrecognized queryId bad' }, + }, + ] + + context('POST to localhost @integration', async () => { + let reqIndex: number + let sends: Action[] = [] + let server: Server + + const port = 18082 + const execute = makeExecute(makeHTTPSender(`http://localhost:${port}/api/oracle`)) + + before( + () => + new Promise((resolve) => { + const app = express() + app.use(express.json()) + app.post('/api/oracle', (req, res) => { + const a = (req.body as unknown) as Action + sends.push(a) + const { queryId } = (a.data as { queryId?: string }) || {} + if (a.type === 'oracleServer/reply' && queryId === 'bad') { + res.status(500).json({ ok: false, rej: `invalid queryId ${queryId}` }) + } else { + res.status(200).json(requests[reqIndex].receive) + } + }) + + server = app.listen(port, resolve) + }), + ) + + after(() => { + server.close() + }) + + requests.forEach((req, i) => { + it(`${req.name}`, async () => { + reqIndex = i + sends = [] + try { + const data = await execute(req.testData as AdapterRequest) + assertSuccess({ expected: req.status, actual: data.statusCode }, data, jobID) + } catch (error) { + const errorResp = Requester.errored(jobID, error) + assertError({ expected: req.status, actual: errorResp.statusCode }, errorResp, jobID) + } + assert.deepEqual(sends, req.sends) + }) + }) + }) + + context('HTTP calls', () => { requests.forEach((req) => { it(`${req.name}`, async () => { - const sends: Array<{ type: string; data: unknown }> = [] + const sends: Action[] = [] const spySend: HTTPSender = async (obj) => { sends.push(obj) return { status: req.status, response: req.receive } From 821c0ad8bc9db29739c6b3057e745ee7309f45f1 Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Fri, 13 Nov 2020 10:46:11 -0600 Subject: [PATCH 16/26] fix: use Requester instead of axios --- agoric/package.json | 1 - agoric/src/httpSender.ts | 15 +++------------ 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/agoric/package.json b/agoric/package.json index a044ac028e..80fb9157ca 100644 --- a/agoric/package.json +++ b/agoric/package.json @@ -30,7 +30,6 @@ "@types/node": "^14.0.13", "@typescript-eslint/eslint-plugin": "^3.9.0", "@typescript-eslint/parser": "^3.9.0", - "axios": "^0.19.2", "ts-node": "^8.10.2", "typescript": "^3.9.7" }, diff --git a/agoric/src/httpSender.ts b/agoric/src/httpSender.ts index 278ae3f2f6..a07006af69 100644 --- a/agoric/src/httpSender.ts +++ b/agoric/src/httpSender.ts @@ -1,4 +1,4 @@ -import axios from 'axios' +import { Requester } from '@chainlink/external-adapter' export interface Action { type: string @@ -10,16 +10,7 @@ export interface HTTPSenderReply { } export type HTTPSender = (obj: Action) => Promise -export const makeHTTPSender: (url: string) => HTTPSender = (url) => async (obj) => { - let response - try { - response = await axios.post(url, obj) - } catch (e) { - if (e.response) { - response = e.response - } else { - throw e - } - } +export const makeHTTPSender: (url: string) => HTTPSender = (url) => async (data) => { + const response = await Requester.request({ method: 'POST', url, data, validateStatus: null }) return { status: response.status, response: response.data } } From f7cd15b3dcd514a4ebb18afb776df0d1b3fdfdac Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Fri, 13 Nov 2020 10:50:07 -0600 Subject: [PATCH 17/26] chore: rename package to @chainlink/agoric --- agoric/README.md | 3 ++- agoric/package.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/agoric/README.md b/agoric/README.md index 8aaaa2ae68..6511b354f3 100644 --- a/agoric/README.md +++ b/agoric/README.md @@ -3,7 +3,8 @@ ## Build Docker Container ```sh -(cd .. && make docker adapter=agoric name=agoric/chainlink) +(cd .. && make docker adapter=agoric tag=agoric/chainlink-adapter) +docker push agoric/chainlink-adapter:latest ``` ## Input Params diff --git a/agoric/package.json b/agoric/package.json index 80fb9157ca..1c179b8c81 100644 --- a/agoric/package.json +++ b/agoric/package.json @@ -1,5 +1,5 @@ { - "name": "@agoric/chainlink-external-adapter", + "name": "@chainlink/agoric", "version": "0.2.0", "license": "MIT", "main": "dist/index.js", From 728b36e504673f1061cfc5f1c488442d182f60c3 Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Mon, 16 Nov 2020 10:44:57 -0600 Subject: [PATCH 18/26] ci: add "agoric" to adapters.json --- .github/strategy/adapters.json | 1 + agoric/README.md | 9 +-------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/.github/strategy/adapters.json b/.github/strategy/adapters.json index add9597763..c919e7b9a3 100644 --- a/.github/strategy/adapters.json +++ b/.github/strategy/adapters.json @@ -7,6 +7,7 @@ "image_name": "__ADAPTER__-adapter", "adapter": [ "1forge", + "agoric", "alphachain", "alphavantage", "amberdata", diff --git a/agoric/README.md b/agoric/README.md index 6511b354f3..543d3420d4 100644 --- a/agoric/README.md +++ b/agoric/README.md @@ -1,11 +1,4 @@ -# Chainlink External Adapter for agoric - -## Build Docker Container - -```sh -(cd .. && make docker adapter=agoric tag=agoric/chainlink-adapter) -docker push agoric/chainlink-adapter:latest -``` +# Chainlink External Adapter for Agoric ## Input Params From 6e3113e8c091c9a243d9263b5037a585e8b27401 Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Tue, 24 Nov 2020 15:27:51 -0600 Subject: [PATCH 19/26] refactor: clarify implementation according to review comments --- CHANGELOG.md | 5 +++ agoric/README.md | 5 +++ agoric/package.json | 5 ++- agoric/src/adapter.ts | 82 +++++++++++++++++----------------------- agoric/src/httpSender.ts | 2 +- agoric/src/index.ts | 5 +-- 6 files changed, 50 insertions(+), 54 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dde4ec4bad..15bbe74215 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Added + +- New adapters: + - `agoric` to push results to the Agoric blockchain + ## [0.2.0-rc.1] - 2021-2-4 ### Added diff --git a/agoric/README.md b/agoric/README.md index 543d3420d4..dbdce3551f 100644 --- a/agoric/README.md +++ b/agoric/README.md @@ -1,5 +1,10 @@ # Chainlink External Adapter for Agoric +This adapter posts a result to the [Agoric blockchain](https://agoric.com). See +the [Agoric Chainlink Oracle +integration](https://github.com/Agoric/dapp-oracle/tree/master/chainlink-agoric) +for details on how to use it with your Chainlink node. + ## Input Params Set the `$AG_SOLO_ORACLE` environment variable to something like: http://localhost:8000/api/oracle diff --git a/agoric/package.json b/agoric/package.json index 1c179b8c81..245f9146c7 100644 --- a/agoric/package.json +++ b/agoric/package.json @@ -1,6 +1,6 @@ { "name": "@chainlink/agoric", - "version": "0.2.0", + "version": "0.0.1", "license": "MIT", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -24,6 +24,7 @@ "start": "yarn server:dist" }, "devDependencies": { + "@types/bn.js": "^4.11.5", "@types/chai": "^4.2.11", "@types/express": "^4.17.6", "@types/mocha": "^7.0.2", @@ -34,6 +35,6 @@ "typescript": "^3.9.7" }, "dependencies": { - + "bn.js": "^4.11.9" } } diff --git a/agoric/src/adapter.ts b/agoric/src/adapter.ts index 79a608309e..372866a57f 100644 --- a/agoric/src/adapter.ts +++ b/agoric/src/adapter.ts @@ -1,3 +1,5 @@ +import BN from 'bn.js' + import { Execute } from '@chainlink/types' import { Requester, Validator, AdapterError } from '@chainlink/external-adapter' import { HTTPSender, HTTPSenderReply } from './httpSender' @@ -8,36 +10,15 @@ const customParams = { payment: ['payment'], } -const Nat = (n: number) => { - if (!Number.isSafeInteger(n)) { - throw Error(`${n} is not a safe integer`) - } - return n -} - // FIXME: Ideally, these would be the same. -const LINK_DECIMALS = 18 -const LINK_AGORIC_DECIMALS = 6 -if (LINK_AGORIC_DECIMALS > LINK_DECIMALS) { - throw Error( - `LINK_AGORIC_DECIMALS ${LINK_AGORIC_DECIMALS} must be less than or equal to ${LINK_DECIMALS}`, - ) -} +const LINK_UNIT = new BN(10).pow(new BN(18)) +const LINK_AGORIC_UNIT = new BN(10).pow(new BN(6)) +// Convert the payment in $LINK into Agoric's pegged $LINK token. export const getRequiredFee = (value: string | number): number => { - const str = String(value || 0) - const digits = str - const significant = digits.substr( - 0, - Math.max(0, digits.length - (LINK_DECIMALS - LINK_AGORIC_DECIMALS)), - ) - - const roundUp = digits[significant.length] && parseInt(digits[significant.length], 10) >= 5 - let requiredFee = Nat(parseInt(significant || '0', 10)) - if (roundUp) { - requiredFee += 1 - } - return Nat(requiredFee) + const paymentCL = new BN(value) + const paymentAgoricLink = paymentCL.mul(LINK_AGORIC_UNIT).div(LINK_UNIT) + return paymentAgoricLink.toNumber() } export interface PostReply { @@ -57,31 +38,35 @@ export const assertGoodReply = (sentType: string, reply: HTTPSenderReply): void } } -export const makeExecute: (send: HTTPSender) => Execute = (send) => async (input) => { - try { - const validator = new Validator(input, customParams) - if (validator.error) { - throw validator.error - } +const makeRawExecute = (send: HTTPSender): Execute => async (input) => { + const validator = new Validator(input, customParams) + if (validator.error) { + throw validator.error + } - const jobRunID = validator.validated.id + const jobRunID = validator.validated.id - const { request_id: queryId, result, payment } = validator.validated.data - const requiredFee = getRequiredFee(payment) + const { request_id: queryId, result, payment } = validator.validated.data + const requiredFee = getRequiredFee(payment) - const obj = { - type: 'oracleServer/reply', - data: { queryId, reply: result, requiredFee }, - } - const reply = await send(obj) + const obj = { + type: 'oracleServer/reply', + data: { queryId, reply: result, requiredFee }, + } + const reply = await send(obj) - assertGoodReply(obj.type, reply) + assertGoodReply(obj.type, reply) - return Requester.success(jobRunID, { - data: { result }, - result, - status: 200, - }) + return Requester.success(jobRunID, { + data: { result }, + result, + status: 200, + }) +} + +const tryExecuteLogError = (send: HTTPSender, execute: Execute): Execute => async (input) => { + try { + return await execute(input) } catch (e) { const queryId = input.data && input.data.request_id let rest @@ -104,3 +89,6 @@ export const makeExecute: (send: HTTPSender) => Execute = (send) => async (input }) } } + +export const makeExecute = (send: HTTPSender): Execute => + tryExecuteLogError(send, makeRawExecute(send)) diff --git a/agoric/src/httpSender.ts b/agoric/src/httpSender.ts index a07006af69..d83089fead 100644 --- a/agoric/src/httpSender.ts +++ b/agoric/src/httpSender.ts @@ -10,7 +10,7 @@ export interface HTTPSenderReply { } export type HTTPSender = (obj: Action) => Promise -export const makeHTTPSender: (url: string) => HTTPSender = (url) => async (data) => { +export const makeHTTPSender = (url: string): HTTPSender => async (data) => { const response = await Requester.request({ method: 'POST', url, data, validateStatus: null }) return { status: response.status, response: response.data } } diff --git a/agoric/src/index.ts b/agoric/src/index.ts index 955e2d7c77..021c807dbb 100644 --- a/agoric/src/index.ts +++ b/agoric/src/index.ts @@ -2,10 +2,7 @@ import { expose, util } from '@chainlink/ea-bootstrap' import { makeExecute } from './adapter' import { makeHTTPSender } from './httpSender' -const oracleAPI = process.env.AG_SOLO_ORACLE_URL -if (!oracleAPI) { - throw Error(`Must supply $AG_SOLO_ORACLE_URL`) -} +const oracleAPI = util.getRequiredEnv('AG_SOLO_ORACLE_URL') const send = makeHTTPSender(oracleAPI) From 8986f8a73612f633d27666660a13b214155edc43 Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Wed, 25 Nov 2020 10:11:06 -0600 Subject: [PATCH 20/26] refactor: use the adapter-test-helpers --- agoric/test/adapter.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/agoric/test/adapter.test.ts b/agoric/test/adapter.test.ts index 065ad9850d..0693412c39 100644 --- a/agoric/test/adapter.test.ts +++ b/agoric/test/adapter.test.ts @@ -1,5 +1,6 @@ import { assert } from 'chai' -import { Requester, assertSuccess, assertError } from '@chainlink/external-adapter' +import { Requester } from '@chainlink/external-adapter' +import { assertSuccess, assertError } from '@chainlink/adapter-test-helpers' import { AdapterRequest } from '@chainlink/types' import { makeExecute } from '../src/adapter' import { HTTPSender, makeHTTPSender } from '../src/httpSender' From ef3cf33c81d9089fcfc927c2b5f7842f01e5590e Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Fri, 4 Dec 2020 15:14:40 -0600 Subject: [PATCH 21/26] ci: fix the Agoric build process --- agoric/package.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/agoric/package.json b/agoric/package.json index 245f9146c7..56bab97e26 100644 --- a/agoric/package.json +++ b/agoric/package.json @@ -1,6 +1,7 @@ { - "name": "@chainlink/agoric", + "name": "@chainlink/agoric-adapter", "version": "0.0.1", + "description": "Chainlink adapter to post to the Agoric blockchain", "license": "MIT", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -13,7 +14,8 @@ }, "scripts": { "prepublishOnly": "yarn build && yarn test:unit", - "build": "tsc", + "setup": "yarn build", + "build": "tsc -b", "lint": "eslint --ignore-path ../.eslintignore . --ext .js,.jsx,.ts,.tsx", "lint:fix": "eslint --ignore-path ../.eslintignore . --ext .js,.jsx,.ts,.tsx --fix", "test": "mocha --exit --timeout 3000 -r ts-node/register 'test/**/*.test.ts'", From 5740690ed658eeece8a498432272ef10b9ae8b38 Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Sun, 14 Feb 2021 20:22:39 -0600 Subject: [PATCH 22/26] fix: address review comments --- agoric/src/adapter.ts | 11 +++++------ agoric/src/index.ts | 2 +- agoric/test/adapter.test.ts | 7 +------ yarn.lock | 2 +- 4 files changed, 8 insertions(+), 14 deletions(-) diff --git a/agoric/src/adapter.ts b/agoric/src/adapter.ts index 372866a57f..39f29dee6e 100644 --- a/agoric/src/adapter.ts +++ b/agoric/src/adapter.ts @@ -68,16 +68,15 @@ const tryExecuteLogError = (send: HTTPSender, execute: Execute): Execute => asyn try { return await execute(input) } catch (e) { - const queryId = input.data && input.data.request_id - let rest - if (queryId !== undefined) { - rest = { queryId } - } + const queryId = input.data?.request_id + const rest = { queryId } await send({ type: 'oracleServer/error', - data: { error: `${(e && e.message) || e}`, ...rest }, + data: { error: `${(e && e.message) || e}`, ...(queryId && rest) }, }).catch((e2) => console.error(`Cannot reflect error to caller:`, e2)) + // See https://github.com/smartcontractkit/external-adapters-js/issues/204 + // for discussion of why this code is necessary. if (e instanceof AdapterError) { throw e } diff --git a/agoric/src/index.ts b/agoric/src/index.ts index 021c807dbb..42ee5a1a48 100644 --- a/agoric/src/index.ts +++ b/agoric/src/index.ts @@ -8,4 +8,4 @@ const send = makeHTTPSender(oracleAPI) const execute = makeExecute(send) -export = { NAME: 'Agoric', execute, ...expose(util.wrapExecute(execute)) } +export = { NAME: 'Agoric', makeExecute, ...expose(util.wrapExecute(execute)) } diff --git a/agoric/test/adapter.test.ts b/agoric/test/adapter.test.ts index 0693412c39..c868138080 100644 --- a/agoric/test/adapter.test.ts +++ b/agoric/test/adapter.test.ts @@ -3,16 +3,11 @@ import { Requester } from '@chainlink/external-adapter' import { assertSuccess, assertError } from '@chainlink/adapter-test-helpers' import { AdapterRequest } from '@chainlink/types' import { makeExecute } from '../src/adapter' -import { HTTPSender, makeHTTPSender } from '../src/httpSender' +import { Action, HTTPSender, makeHTTPSender } from '../src/httpSender' import express from 'express' import { Server } from 'http' -interface Action { - type: string - data: unknown -} - describe('execute', () => { const jobID = '1' diff --git a/yarn.lock b/yarn.lock index eff6a30364..95bad1949e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2313,7 +2313,7 @@ bitcore-lib-cash@^8.20.3: inherits "=2.0.1" lodash "^4.17.20" -"bitcore-lib-zcash@github:zcash-hackworks/bitcore-lib-zcash": +bitcore-lib-zcash@zcash-hackworks/bitcore-lib-zcash: version "0.13.19" resolved "https://codeload.github.com/zcash-hackworks/bitcore-lib-zcash/tar.gz/e97ae1dd2e9f14d6076d5e5429c75d8965afa4ab" dependencies: From 1802067dc0c25ce137db1fe27edf3154b9aca2e4 Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Fri, 26 Feb 2021 13:40:20 -0600 Subject: [PATCH 23/26] refactor: standardize code based on example adapter --- agoric/README.md | 25 ++++++++-- agoric/package.json | 9 +++- agoric/src/adapter.ts | 90 ++++++++++++++++++++++------------- agoric/src/config.ts | 14 ++++++ agoric/src/httpSender.ts | 16 ------- agoric/src/index.ts | 10 ++-- agoric/test/adapter.test.ts | 93 +++---------------------------------- 7 files changed, 107 insertions(+), 150 deletions(-) create mode 100644 agoric/src/config.ts delete mode 100644 agoric/src/httpSender.ts diff --git a/agoric/README.md b/agoric/README.md index dbdce3551f..1246d1470e 100644 --- a/agoric/README.md +++ b/agoric/README.md @@ -5,13 +5,28 @@ the [Agoric Chainlink Oracle integration](https://github.com/Agoric/dapp-oracle/tree/master/chainlink-agoric) for details on how to use it with your Chainlink node. -## Input Params -Set the `$AG_SOLO_ORACLE` environment variable to something like: http://localhost:8000/api/oracle +| Required? | Name | Description | Options | Defaults to | +| :-------: | :------: | :-----------------: | :--------------------------: | :---------: | +| | endpoint | The endpoint to use | [agoric](#Agoric-endpoint) | agoric | -- `request_id`: The Agoric oracle queryId -- `payment`: The user-provided fee in $LINK -- `result`: The result to return to the Agoric oracle contract +--- + +## Agoric endpoint + +This is the endpoint exposed by your local `ag-solo` after installing the +[Agoric Chainlink Oracle +integration](https://github.com/Agoric/dapp-oracle/tree/master/chainlink-agoric). + +The default is http://localhost:8000/api/oracle + +### Input Params + +| Required? | Name | Description | Options | Defaults to | +| :-------: | :------------------------: | :--------------------------------------: | :-----------------: | :---------: | +| ✅ | `request_id` | The Agoric oracle queryId | string | request_id from Agoric External Initiator | +| | `payment` | How much $LINK the Chainlink node would like to collect as a fee | number as a string | the whole fee the user offered | +| ✅ | `result` | The result to return to the Agoric oracle contract | string | | ## Output diff --git a/agoric/package.json b/agoric/package.json index 56bab97e26..5ef4b4d46b 100644 --- a/agoric/package.json +++ b/agoric/package.json @@ -2,7 +2,12 @@ "name": "@chainlink/agoric-adapter", "version": "0.0.1", "description": "Chainlink adapter to post to the Agoric blockchain", - "license": "MIT", + "keywords": [ + "Chainlink", + "LINK", + "blockchain", + "oracle" + ], "main": "dist/index.js", "types": "dist/index.d.ts", "files": [ @@ -12,6 +17,7 @@ "url": "https://github.com/smartcontractkit/external-adapters-js", "type": "git" }, + "license": "MIT", "scripts": { "prepublishOnly": "yarn build && yarn test:unit", "setup": "yarn build", @@ -26,7 +32,6 @@ "start": "yarn server:dist" }, "devDependencies": { - "@types/bn.js": "^4.11.5", "@types/chai": "^4.2.11", "@types/express": "^4.17.6", "@types/mocha": "^7.0.2", diff --git a/agoric/src/adapter.ts b/agoric/src/adapter.ts index 39f29dee6e..258f73a29e 100644 --- a/agoric/src/adapter.ts +++ b/agoric/src/adapter.ts @@ -1,22 +1,31 @@ -import BN from 'bn.js' +import { BigNumber } from 'ethers' -import { Execute } from '@chainlink/types' +import { Config, ExecuteWithConfig, ExecuteFactory } from '@chainlink/types' import { Requester, Validator, AdapterError } from '@chainlink/external-adapter' -import { HTTPSender, HTTPSenderReply } from './httpSender' -const customParams = { +import { makeConfig } from './config' + +// We're on localhost, so retries just confuse the oracle state. +const NUM_RETRIES = 1 + +export interface Action { + type: string + data: unknown +} + +const inputParams = { request_id: ['request_id'], result: ['result'], payment: ['payment'], } // FIXME: Ideally, these would be the same. -const LINK_UNIT = new BN(10).pow(new BN(18)) -const LINK_AGORIC_UNIT = new BN(10).pow(new BN(6)) +const LINK_UNIT = BigNumber.from(10).pow(BigNumber.from(18)) +const LINK_AGORIC_UNIT = BigNumber.from(10).pow(BigNumber.from(6)) // Convert the payment in $LINK into Agoric's pegged $LINK token. export const getRequiredFee = (value: string | number): number => { - const paymentCL = new BN(value) + const paymentCL = BigNumber.from(value) const paymentAgoricLink = paymentCL.mul(LINK_AGORIC_UNIT).div(LINK_UNIT) return paymentAgoricLink.toNumber() } @@ -27,25 +36,15 @@ export interface PostReply { rej?: unknown } -export const assertGoodReply = (sentType: string, reply: HTTPSenderReply): void => { - if (reply.status < 200 || reply.status >= 300) { - throw Error(`${sentType} status ${reply.status} is not 2xx`) - } - - const pr = reply.response as PostReply - if (!pr.ok) { - throw Error(`${sentType} response failed: ${pr.rej}`) - } -} - -const makeRawExecute = (send: HTTPSender): Execute => async (input) => { - const validator = new Validator(input, customParams) +const executeImpl: ExecuteWithConfig = async (request, config) => { + const validator = new Validator(request, inputParams) if (validator.error) { throw validator.error } - const jobRunID = validator.validated.id + Requester.logConfig(config) + const jobRunID = validator.validated.id const { request_id: queryId, result, payment } = validator.validated.data const requiredFee = getRequiredFee(payment) @@ -53,9 +52,21 @@ const makeRawExecute = (send: HTTPSender): Execute => async (input) => { type: 'oracleServer/reply', data: { queryId, reply: result, requiredFee }, } - const reply = await send(obj) - assertGoodReply(obj.type, reply) + const response = await Requester.request( + { + ...config.api, + method: 'POST', + data: obj, + }, + undefined, + NUM_RETRIES, + ) + + const pr = response.data as PostReply + if (!pr.ok) { + throw Error(`${obj.type} response failed: ${pr.rej}`) + } return Requester.success(jobRunID, { data: { result }, @@ -64,16 +75,27 @@ const makeRawExecute = (send: HTTPSender): Execute => async (input) => { }) } -const tryExecuteLogError = (send: HTTPSender, execute: Execute): Execute => async (input) => { +const tryExecuteLogError = ( + execute: ExecuteWithConfig, +): ExecuteWithConfig => async (request, config) => { try { - return await execute(input) + return await execute(request, config) } catch (e) { - const queryId = input.data?.request_id + const queryId = request.data?.request_id const rest = { queryId } - await send({ - type: 'oracleServer/error', - data: { error: `${(e && e.message) || e}`, ...(queryId && rest) }, - }).catch((e2) => console.error(`Cannot reflect error to caller:`, e2)) + + await Requester.request( + { + ...config.api, + method: 'POST', + data: { + type: 'oracleServer/error', + data: { error: `${(e && e.message) || e}`, ...(queryId && rest) }, + }, + }, + undefined, + NUM_RETRIES, + ).catch((e2: Error) => console.error(`Cannot reflect error to caller:`, e2)) // See https://github.com/smartcontractkit/external-adapters-js/issues/204 // for discussion of why this code is necessary. @@ -81,7 +103,7 @@ const tryExecuteLogError = (send: HTTPSender, execute: Execute): Execute => asyn throw e } throw new AdapterError({ - jobRunID: input.id, + jobRunID: request.id, statusCode: 500, message: `${(e && e.message) || e}`, cause: e, @@ -89,5 +111,7 @@ const tryExecuteLogError = (send: HTTPSender, execute: Execute): Execute => asyn } } -export const makeExecute = (send: HTTPSender): Execute => - tryExecuteLogError(send, makeRawExecute(send)) +export const execute = tryExecuteLogError(executeImpl) +export const makeExecute: ExecuteFactory = (config) => { + return async (request) => execute(request, config || makeConfig()) +} diff --git a/agoric/src/config.ts b/agoric/src/config.ts new file mode 100644 index 0000000000..65723e1e36 --- /dev/null +++ b/agoric/src/config.ts @@ -0,0 +1,14 @@ +import { Requester } from '@chainlink/external-adapter' +import { Config } from '@chainlink/types' +import { util } from '@chainlink/ea-bootstrap' + +export const DEFAULT_API_ENDPOINT = 'http://localhost:8000/api/oracle' +const LEGACY_API_ENDPOINT_ENV = 'AG_SOLO_ORACLE' + +export const makeConfig = (prefix?: string): Config => { + const config = Requester.getDefaultConfig(prefix) + config.api.baseURL = + config.api.baseURL || util.getEnv(LEGACY_API_ENDPOINT_ENV) || DEFAULT_API_ENDPOINT + config.apiKey = config.apiKey || 'not required' + return config +} diff --git a/agoric/src/httpSender.ts b/agoric/src/httpSender.ts deleted file mode 100644 index d83089fead..0000000000 --- a/agoric/src/httpSender.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Requester } from '@chainlink/external-adapter' - -export interface Action { - type: string - data: unknown -} -export interface HTTPSenderReply { - status: number - response: unknown -} -export type HTTPSender = (obj: Action) => Promise - -export const makeHTTPSender = (url: string): HTTPSender => async (data) => { - const response = await Requester.request({ method: 'POST', url, data, validateStatus: null }) - return { status: response.status, response: response.data } -} diff --git a/agoric/src/index.ts b/agoric/src/index.ts index 42ee5a1a48..e9fb735460 100644 --- a/agoric/src/index.ts +++ b/agoric/src/index.ts @@ -1,11 +1,7 @@ import { expose, util } from '@chainlink/ea-bootstrap' import { makeExecute } from './adapter' -import { makeHTTPSender } from './httpSender' -const oracleAPI = util.getRequiredEnv('AG_SOLO_ORACLE_URL') +const NAME = 'Agoric' +const endpoint = util.getEnv('AG_SOLO_ORACLE_URL') -const send = makeHTTPSender(oracleAPI) - -const execute = makeExecute(send) - -export = { NAME: 'Agoric', makeExecute, ...expose(util.wrapExecute(execute)) } +export = { NAME, makeExecute, makeConfig, ...expose(util.wrapExecute(makeExecute(endpoint))) } diff --git a/agoric/test/adapter.test.ts b/agoric/test/adapter.test.ts index c868138080..c32b6fcd54 100644 --- a/agoric/test/adapter.test.ts +++ b/agoric/test/adapter.test.ts @@ -2,8 +2,8 @@ import { assert } from 'chai' import { Requester } from '@chainlink/external-adapter' import { assertSuccess, assertError } from '@chainlink/adapter-test-helpers' import { AdapterRequest } from '@chainlink/types' -import { makeExecute } from '../src/adapter' -import { Action, HTTPSender, makeHTTPSender } from '../src/httpSender' +import { Action, makeExecute } from '../src/adapter' +import { makeConfig } from '../src/config' import express from 'express' import { Server } from 'http' @@ -96,7 +96,7 @@ describe('execute', () => { type: 'oracleServer/error', data: { queryId: 'bad', - error: 'oracleServer/reply status 500 is not 2xx', + error: 'Request failed with status code 500', }, }, ], @@ -110,7 +110,9 @@ describe('execute', () => { let server: Server const port = 18082 - const execute = makeExecute(makeHTTPSender(`http://localhost:${port}/api/oracle`)) + process.env.AG_SOLO_ORACLE = `http://localhost:${port}/api/oracle` + const execute = makeExecute(makeConfig('AGORICTEST')) + delete process.env.AG_SOLO_ORACLE before( () => @@ -152,87 +154,4 @@ describe('execute', () => { }) }) }) - - context('HTTP calls', () => { - requests.forEach((req) => { - it(`${req.name}`, async () => { - const sends: Action[] = [] - const spySend: HTTPSender = async (obj) => { - sends.push(obj) - return { status: req.status, response: req.receive } - } - const execute = makeExecute(spySend) - - try { - const data = await execute(req.testData as AdapterRequest) - assertSuccess({ expected: req.status, actual: data.statusCode }, data, jobID) - } catch (error) { - const errorResp = Requester.errored(jobID, error) - assertError({ expected: req.status, actual: errorResp.statusCode }, errorResp, jobID) - } - assert.deepEqual(sends, req.sends) - }) - }) - }) - - context('validation', () => { - const mockSend: HTTPSender = () => - Promise.resolve({ - status: 200, - response: { ok: true, res: true }, - }) - const execute = makeExecute(mockSend) - const requests = [ - { - name: 'normal request_id', - status: 200, - testData: { - id: jobID, - data: { request_id: '4', payment: '10000000000000000', result: 'abc' }, - }, - }, - { - name: 'push request_id', - status: 200, - testData: { - id: jobID, - data: { request_id: 'push-3', payment: '10000000000000000', result: 'def' }, - }, - }, - { - name: 'zero payment', - status: 200, - testData: { id: jobID, data: { request_id: '99', payment: '0', result: 'ghi' } }, - }, - { status: 400, name: 'empty body', testData: {} }, - { status: 400, name: 'empty data', testData: { data: {} } }, - { - status: 400, - name: 'payment not supplied', - testData: { id: jobID, data: { request_id: '3', result: 'abc' } }, - }, - { - status: 400, - name: 'request_id not supplied', - testData: { id: jobID, data: { payment: '0', result: 'def' } }, - }, - { - status: 400, - name: 'result not supplied', - testData: { id: jobID, data: { request_id: '3', payment: '0' } }, - }, - ] - - requests.forEach((req) => { - it(`${req.name}`, async () => { - try { - const data = await execute(req.testData as AdapterRequest) - assertSuccess({ expected: req.status, actual: data.statusCode }, data, jobID) - } catch (error) { - const errorResp = Requester.errored(jobID, error) - assertError({ expected: req.status, actual: errorResp.statusCode }, errorResp, jobID) - } - }) - }) - }) }) From c4c8d8c50db32fe3968238488504e784fc67c0af Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Fri, 26 Feb 2021 14:56:08 -0600 Subject: [PATCH 24/26] fix: import makeConfig --- agoric/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/agoric/src/index.ts b/agoric/src/index.ts index e9fb735460..143b585fe9 100644 --- a/agoric/src/index.ts +++ b/agoric/src/index.ts @@ -1,5 +1,6 @@ import { expose, util } from '@chainlink/ea-bootstrap' import { makeExecute } from './adapter' +import { makeConfig } from './config' const NAME = 'Agoric' const endpoint = util.getEnv('AG_SOLO_ORACLE_URL') From 302eaa6731a44f5f84805c03ff1e10f19d71c727 Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Mon, 1 Mar 2021 12:30:29 -0600 Subject: [PATCH 25/26] fix: correct the default agoric adapter parameters --- agoric/src/config.ts | 5 ++++- agoric/src/index.ts | 5 ++--- agoric/test/adapter.test.ts | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/agoric/src/config.ts b/agoric/src/config.ts index 65723e1e36..2aeeaa5985 100644 --- a/agoric/src/config.ts +++ b/agoric/src/config.ts @@ -3,7 +3,10 @@ import { Config } from '@chainlink/types' import { util } from '@chainlink/ea-bootstrap' export const DEFAULT_API_ENDPOINT = 'http://localhost:8000/api/oracle' -const LEGACY_API_ENDPOINT_ENV = 'AG_SOLO_ORACLE' + +// This environment variable is needed for the Hack the Orb oracle +// instructions to remain correct. +const LEGACY_API_ENDPOINT_ENV = 'AG_SOLO_ORACLE_URL' export const makeConfig = (prefix?: string): Config => { const config = Requester.getDefaultConfig(prefix) diff --git a/agoric/src/index.ts b/agoric/src/index.ts index 143b585fe9..454a553c4a 100644 --- a/agoric/src/index.ts +++ b/agoric/src/index.ts @@ -1,8 +1,7 @@ -import { expose, util } from '@chainlink/ea-bootstrap' +import { expose } from '@chainlink/ea-bootstrap' import { makeExecute } from './adapter' import { makeConfig } from './config' const NAME = 'Agoric' -const endpoint = util.getEnv('AG_SOLO_ORACLE_URL') -export = { NAME, makeExecute, makeConfig, ...expose(util.wrapExecute(makeExecute(endpoint))) } +export = { NAME, makeExecute, makeConfig, ...expose(makeExecute()) } diff --git a/agoric/test/adapter.test.ts b/agoric/test/adapter.test.ts index c32b6fcd54..c32582ec75 100644 --- a/agoric/test/adapter.test.ts +++ b/agoric/test/adapter.test.ts @@ -110,9 +110,9 @@ describe('execute', () => { let server: Server const port = 18082 - process.env.AG_SOLO_ORACLE = `http://localhost:${port}/api/oracle` + process.env.AG_SOLO_ORACLE_URL = `http://localhost:${port}/api/oracle` const execute = makeExecute(makeConfig('AGORICTEST')) - delete process.env.AG_SOLO_ORACLE + delete process.env.AG_SOLO_ORACLE_URL before( () => From 67c9767dc5355c5a9938c177e99b0f3ba54687fa Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Mon, 1 Mar 2021 13:20:14 -0600 Subject: [PATCH 26/26] chore: remove dependency on bn.js --- agoric/package.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/agoric/package.json b/agoric/package.json index 5ef4b4d46b..c49be9d87a 100644 --- a/agoric/package.json +++ b/agoric/package.json @@ -40,8 +40,5 @@ "@typescript-eslint/parser": "^3.9.0", "ts-node": "^8.10.2", "typescript": "^3.9.7" - }, - "dependencies": { - "bn.js": "^4.11.9" } }