diff --git a/_old_tests/integration/batch.integration.test.js b/_old_tests/integration/batch.integration.test.js deleted file mode 100644 index 59f8d163..00000000 --- a/_old_tests/integration/batch.integration.test.js +++ /dev/null @@ -1,230 +0,0 @@ -const BatchLiquidator = require("../../src/abis/BatchLiquidator.json"); - -const protocolHelper = require("../../test/utils/protocolHelper"); -const expect = require("chai").expect; -const ganache = require("../../test/utils/ganache"); -const App = require("../../src/app"); - -const AGENT_ACCOUNT = "0x868D9F52f84d33261c03C8B77999f83501cF5A99"; - -let app, accounts, snapId, protocolVars, web3, batchContract; - -// eslint-disable-next-line promise/param-names -const delay = ms => new Promise(res => setTimeout(res, ms)); -const exitWithError = (error) => { - console.error(error); - process.exit(1); -}; - -const deployBatchContract = async () => { - if (batchContract === undefined) { - const contract = new web3.eth.Contract(BatchLiquidator.abi); - const res = await contract.deploy({ - data: BatchLiquidator.bytecode, - arguments: [protocolVars.host._address, protocolVars.cfa._address] - }).send({ - from: accounts[0], - gas: 1500000, - gasPrice: "1000" - }); - batchContract = res; - console.log(`BatchLiquidator address: ${res._address}`); - } -}; - -const bootNode = async (config) => { - const sentinelConfig = protocolHelper.getSentinelConfig(config); - app = new App(sentinelConfig); - app.start(); - while (!app.isInitialized()) { - await protocolHelper.timeout(3000); - } -}; - -const closeNode = async (force = false) => { - if (app !== undefined) { - return app.shutdown(force); - } -}; - -describe("Integration scripts tests", () => { - before(async function () { - protocolVars = await protocolHelper.setup(ganache.provider, AGENT_ACCOUNT); - web3 = protocolVars.web3; - accounts = protocolVars.accounts; - await deployBatchContract(); - snapId = await ganache.helper.takeEvmSnapshot(); - }); - - beforeEach(async () => { - }); - - afterEach(async () => { - try { - snapId = await ganache.helper.revertToSnapShot(snapId.result); - } catch (err) { - exitWithError(err); - } - }); - - after(async () => { - if(!app._isShutdown) { - await closeNode(true); - } - await ganache.close(); - }); - - it("Send a batch Liquidation to close multi streams", async () => { - try { - const flowData1 = protocolVars.cfa.methods.createFlow(protocolVars.superToken._address, accounts[0], "1000000000000000", "0x").encodeABI(); - await protocolVars.host.methods.callAgreement(protocolVars.cfa._address, flowData1, "0x").send({ - from: accounts[1], - gas: 1000000 - }); - await protocolVars.host.methods.callAgreement(protocolVars.cfa._address, flowData1, "0x").send({ - from: accounts[2], - gas: 1000000 - }); - await protocolVars.host.methods.callAgreement(protocolVars.cfa._address, flowData1, "0x").send({ - from: accounts[3], - gas: 1000000 - }); - await protocolVars.host.methods.callAgreement(protocolVars.cfa._address, flowData1, "0x").send({ - from: accounts[4], - gas: 1000000 - }); - await protocolVars.host.methods.callAgreement(protocolVars.cfa._address, flowData1, "0x").send({ - from: accounts[5], - gas: 1000000 - }); - const tx = await protocolVars.superToken.methods.transferAll(accounts[9]).send({ - from: accounts[1], - gas: 1000000 - }); - await protocolVars.superToken.methods.transferAll(accounts[9]).send({ - from: accounts[2], - gas: 1000000 - }); - await protocolVars.superToken.methods.transferAll(accounts[9]).send({ - from: accounts[3], - gas: 1000000 - }); - await protocolVars.superToken.methods.transferAll(accounts[9]).send({ - from: accounts[4], - gas: 1000000 - }); - await protocolVars.superToken.methods.transferAll(accounts[9]).send({ - from: accounts[5], - gas: 1000000 - }); - await bootNode({batch_contract: batchContract._address, polling_interval: 1, max_tx_number: 5}); - await ganache.helper.timeTravelOnce(1000, app, true); - const result = await protocolHelper.waitForEventAtSameBlock(protocolVars, app, ganache, "AgreementLiquidatedV2", 5, tx.blockNumber); - await app.shutdown(); - expect(result).gt(tx.blockNumber); - } catch (err) { - exitWithError(err); - } - }); - - it("Don't go over limit of tx per liquidation job", async () => { - try { - const flowData1 = protocolVars.cfa.methods.createFlow(protocolVars.superToken._address, accounts[0], "1000000000000000", "0x").encodeABI(); - await protocolVars.host.methods.callAgreement(protocolVars.cfa._address, flowData1, "0x").send({ - from: accounts[1], - gas: 1000000 - }); - await protocolVars.host.methods.callAgreement(protocolVars.cfa._address, flowData1, "0x").send({ - from: accounts[2], - gas: 1000000 - }); - await protocolVars.host.methods.callAgreement(protocolVars.cfa._address, flowData1, "0x").send({ - from: accounts[3], - gas: 1000000 - }); - await protocolVars.host.methods.callAgreement(protocolVars.cfa._address, flowData1, "0x").send({ - from: accounts[4], - gas: 1000000 - }); - await protocolVars.host.methods.callAgreement(protocolVars.cfa._address, flowData1, "0x").send({ - from: accounts[5], - gas: 1000000 - }); - const tx = await protocolVars.superToken.methods.transferAll(accounts[9]).send({ - from: accounts[1], - gas: 1000000 - }); - await protocolVars.superToken.methods.transferAll(accounts[9]).send({ - from: accounts[2], - gas: 1000000 - }); - await protocolVars.superToken.methods.transferAll(accounts[9]).send({ - from: accounts[3], - gas: 1000000 - }); - await protocolVars.superToken.methods.transferAll(accounts[9]).send({ - from: accounts[4], - gas: 1000000 - }); - await protocolVars.superToken.methods.transferAll(accounts[9]).send({ - from: accounts[5], - gas: 1000000 - }); - await bootNode({batch_contract: batchContract._address, polling_interval: 1, max_tx_number: 3}); - await ganache.helper.timeTravelOnce(1000, app, true); - const result1 = await protocolHelper.waitForEventAtSameBlock(protocolVars, app, ganache, "AgreementLiquidatedV2", 3, tx.blockNumber); - const result2 = await protocolHelper.waitForEventAtSameBlock(protocolVars, app, ganache, "AgreementLiquidatedV2", 2, result1); - await app.shutdown(); - expect(result1).gt(tx.blockNumber); - expect(result2).gt(result1); - } catch (err) { - exitWithError(err); - } - }); - - it("Go over the gasLimit, reduce batch size", async () => { - try { - for (let i = 1; i <= 5; i++) { - for (let j = 6; j <= 7; j++) { - console.log(`Sending from i=${i} , j=${j} , ${accounts[i]} -> ${accounts[j]}`); - const flow = protocolVars.cfa.methods.createFlow(protocolVars.superToken._address, accounts[j], "1000000000000000", "0x").encodeABI(); - await protocolVars.host.methods.callAgreement(protocolVars.cfa._address, flow, "0x").send({ - from: accounts[i], - gas: 1000000 - }); - } - } - const tx = await protocolVars.superToken.methods.transferAll(accounts[9]).send({ - from: accounts[1], - gas: 1000000 - }); - await protocolVars.superToken.methods.transferAll(accounts[9]).send({ - from: accounts[2], - gas: 1000000 - }); - await protocolVars.superToken.methods.transferAll(accounts[9]).send({ - from: accounts[3], - gas: 1000000 - }); - await protocolVars.superToken.methods.transferAll(accounts[9]).send({ - from: accounts[4], - gas: 1000000 - }); - await protocolVars.superToken.methods.transferAll(accounts[9]).send({ - from: accounts[5], - gas: 1000000 - }); - await bootNode({batch_contract: batchContract._address, polling_interval: 1, max_tx_number: 10}); - // blockGasLimit random number picked lower than the gas limit of the tx needed for batch call - app.setTestFlag("REVERT_ON_BLOCK_GAS_LIMIT", { blockGasLimit: 3847206 }); - await ganache.helper.timeTravelOnce(1000, app, true); - const result1 = await protocolHelper.waitForEventAtSameBlock(protocolVars, app, ganache, "AgreementLiquidatedV2", 5, tx.blockNumber); - const result2 = await protocolHelper.waitForEventAtSameBlock(protocolVars, app, ganache, "AgreementLiquidatedV2", 5, result1 + 1); - await app.shutdown(); - expect(result1).gt(tx.blockNumber); - expect(result2).gt(result1); - } catch (err) { - exitWithError(err); - } - }); -}); diff --git a/_old_tests/integration/cfa.integration.test.js b/_old_tests/integration/cfa.integration.test.js deleted file mode 100644 index ccb555e0..00000000 --- a/_old_tests/integration/cfa.integration.test.js +++ /dev/null @@ -1,346 +0,0 @@ -const protocolHelper = require("../../test/utils/protocolHelper"); -const expect = require("chai").expect; -const ganache = require("../../test/utils/ganache"); -const App = require("../../src/app"); - -const AGENT_ACCOUNT = "0x868D9F52f84d33261c03C8B77999f83501cF5A99"; - -let app, accounts, snapId, protocolVars, web3; - -const bootNode = async (config) => { - const sentinelConfig = protocolHelper.getSentinelConfig(config); - app = new App(sentinelConfig); - app.start(); - while (!app.isInitialized()) { - await protocolHelper.timeout(5000); - } -}; - -const closeNode = async (force = false) => { - if (app !== undefined) { - return app.shutdown(force); - } -}; - -describe("Integration scripts tests", () => { - before(async function () { - protocolVars = await protocolHelper.setup(ganache.provider, AGENT_ACCOUNT); - web3 = protocolVars.web3; - accounts = protocolVars.accounts; - snapId = await ganache.helper.takeEvmSnapshot(); - }); - - beforeEach(async () => { - }); - - afterEach(async () => { - try { - snapId = await ganache.helper.revertToSnapShot(snapId.result); - } catch (err) { - protocolHelper.exitWithError(err); - } - }); - - after(async () => { - if(!app._isShutdown) { - await closeNode(true); - } - await ganache.close(); - }); - - it("Create one stream", async () => { - try { - const data = protocolVars.cfa.methods.createFlow( - protocolVars.superToken._address, - accounts[2], - "10000000000000000", - "0x" - ).encodeABI(); - await protocolVars.host.methods.callAgreement(protocolVars.cfa._address, data, "0x").send({ - from: accounts[0], - gas: 1000000 - }); - await ganache.helper.timeTravelOnce(1); - await bootNode({pic: accounts[0]}); - await ganache.helper.timeTravelOnce(60); - const tx = await protocolVars.superToken.methods.transferAll(accounts[2]).send({ - from: accounts[0], - gas: 1000000 - }); - const result = await protocolHelper.waitForEvent(protocolVars, app, ganache, "AgreementLiquidatedV2", tx.blockNumber); - await app.shutdown(); - protocolHelper.expectLiquidationV2(result[0], AGENT_ACCOUNT, accounts[0], "0"); - } catch (err) { - protocolHelper.exitWithError(err); - } - }); - - it("Create small stream then updated to bigger stream", async () => { - try { - const data = protocolVars.cfa.methods.createFlow( - protocolVars.superToken._address, - accounts[2], - "1000", - "0x" - ).encodeABI(); - await protocolVars.host.methods.callAgreement(protocolVars.cfa._address, data, "0x").send({ - from: accounts[0], - gas: 1000000 - }); - await ganache.helper.timeTravelOnce(1); - await bootNode({pic: accounts[0]}); - await ganache.helper.timeTravelOnce(60); - const dataUpdate = protocolVars.cfa.methods.updateFlow( - protocolVars.superToken._address, - accounts[2], - "1000000000", - "0x" - ).encodeABI(); - await protocolVars.host.methods.callAgreement(protocolVars.cfa._address, dataUpdate, "0x").send({ - from: accounts[0], - gas: 1000000 - }); - await ganache.helper.timeTravelOnce(60); - const tx = await protocolVars.superToken.methods.transferAll(accounts[2]).send({ - from: accounts[0], - gas: 1000000 - }); - const result = await protocolHelper.waitForEvent(protocolVars, app, ganache, "AgreementLiquidatedV2", tx.blockNumber); - await app.shutdown(); - protocolHelper.expectLiquidationV2(result[0], AGENT_ACCOUNT, accounts[0], "0"); - } catch (err) { - protocolHelper.exitWithError(err); - } - }); - - it("Create one out going stream and receive a smaller incoming stream", async () => { - try { - const sendingFlowData = protocolVars.cfa.methods.createFlow( - protocolVars.superToken._address, - accounts[2], - "1000000000", - "0x" - ).encodeABI(); - await protocolVars.host.methods.callAgreement( - protocolVars.cfa._address, - sendingFlowData, - "0x").send({ - from: accounts[0], - gas: 1000000 - }); - await ganache.helper.timeTravelOnce(1); - await bootNode({pic: accounts[0]}); - await ganache.helper.timeTravelOnce(60); - const receivingFlowData = protocolVars.cfa.methods.createFlow( - protocolVars.superToken._address, - accounts[0], - "10000", - "0x" - ).encodeABI(); - await protocolVars.host.methods.callAgreement( - protocolVars.cfa._address, - receivingFlowData, - "0x").send({ - from: accounts[2], - gas: 1000000 - }); - await ganache.helper.timeTravelOnce(60); - const tx = await protocolVars.superToken.methods.transferAll(accounts[5]).send({ - from: accounts[0], - gas: 1000000 - }); - const result = await protocolHelper.waitForEvent(protocolVars, app, ganache, "AgreementLiquidatedV2", tx.blockNumber); - await app.shutdown(); - protocolHelper.expectLiquidationV2(result[0], AGENT_ACCOUNT, accounts[0], "0"); - } catch (err) { - protocolHelper.exitWithError(err); - } - }); - - it("Create two outgoing streams, and new total outflow rate should apply to the agent estimation logic", async () => { - try { - const flowData = protocolVars.cfa.methods.createFlow( - protocolVars.superToken._address, - accounts[2], - "1000000000000000", - "0x" - ).encodeABI(); - await protocolVars.host.methods.callAgreement(protocolVars.cfa._address, flowData, "0x").send({ - from: accounts[0], - gas: 1000000 - }); - await ganache.helper.timeTravelOnce(1); - await bootNode({pic: accounts[0]}); - await ganache.helper.timeTravelOnce(3600, app, true); - const flowData2 = protocolVars.cfa.methods.createFlow( - protocolVars.superToken._address, - accounts[3], - "1000000000000000", - "0x" - ).encodeABI(); - await protocolVars.host.methods.callAgreement(protocolVars.cfa._address, flowData2, "0x").send({ - from: accounts[0], - gas: 1000000 - }); - await protocolVars.superToken.methods.transferAll(accounts[9]).send({ - from: accounts[0], - gas: 1000000 - }); - const result = await protocolHelper.waitForEvent(protocolVars, app, ganache, "AgreementLiquidatedV2", 0); - await app.shutdown(); - protocolHelper.expectLiquidationV2(result[0], AGENT_ACCOUNT, accounts[0], "0"); - - } catch (err) { - protocolHelper.exitWithError(err); - } - }); - - it("Create a stream with big flow rate, then update the stream with smaller flow rate", async () => { - try { - const flowData = protocolVars.cfa.methods.createFlow( - protocolVars.superToken._address, - accounts[2], - "100000000000000", - "0x" - ).encodeABI(); - await protocolVars.host.methods.callAgreement(protocolVars.cfa._address, flowData, "0x").send({ - from: accounts[5], - gas: 1000000 - }); - await ganache.helper.timeTravelOnce(1); - await bootNode(); - const firstEstimation = await app.db.queries.getAddressEstimations(accounts[5]); - const updateData = protocolVars.cfa.methods.updateFlow( - protocolVars.superToken._address, - accounts[2], - "1", - "0x" - ).encodeABI(); - await ganache.helper.timeTravelUntil(1, 20); - await protocolVars.host.methods.callAgreement(protocolVars.cfa._address, updateData, "0x").send({ - from: accounts[5], - gas: 1000000 - }); - await ganache.helper.timeTravelUntil(1, 20); - const secondEstimation = await app.db.queries.getAddressEstimations(accounts[5]); - await app.shutdown(); - console.log("Estimation 1: ", firstEstimation[0].estimation); - console.log("Estimation 2: ", secondEstimation[0].estimation); - expect(firstEstimation[0].estimation).to.not.equal(32503593600000); - // the stream is soo small that we mark as not a real estimation - expect(secondEstimation[0].estimation).to.equal(32503593600000); - } catch (err) { - protocolHelper.exitWithError(err); - } - }); - - it("Should make liquidation as Pleb", async() => { - try { - const data = protocolVars.cfa.methods.createFlow( - protocolVars.superToken._address, - accounts[2], - "10000000000000000", - "0x" - ).encodeABI(); - await protocolVars.host.methods.callAgreement(protocolVars.cfa._address, data, "0x").send({ - from: accounts[0], - gas: 1000000 - }); - await ganache.helper.timeTravelOnce(1); - await bootNode({log_level:"debug"}); - await ganache.helper.timeTravelOnce(60); - const tx = await protocolVars.superToken.methods.transferAll(accounts[2]).send({ - from: accounts[0], - gas: 1000000 - }); - await ganache.helper.timeTravelOnce(900); - const result = await protocolHelper.waitForEvent(protocolVars, app, ganache, "AgreementLiquidatedV2", tx.blockNumber); - await app.shutdown(); - protocolHelper.expectLiquidationV2(result[0], AGENT_ACCOUNT, accounts[0], "1"); - } catch (err) { - protocolHelper.exitWithError(err); - } - }); - - it("Should make liquidation wait until Pleb slot", async() => { - try { - const data = protocolVars.cfa.methods.createFlow( - protocolVars.superToken._address, - accounts[2], - "10000000000000000", - "0x" - ).encodeABI(); - await protocolVars.host.methods.callAgreement(protocolVars.cfa._address, data, "0x").send({ - from: accounts[0], - gas: 1000000 - }); - await ganache.helper.timeTravelOnce(1); - await bootNode({log_level:"debug"}); - await ganache.helper.timeTravelOnce(60); - const tx = await protocolVars.superToken.methods.transferAll(accounts[2]).send({ - from: accounts[0], - gas: 1000000 - }); - await ganache.helper.timeTravelOnce(850); - const result = await protocolHelper.waitForEvent(protocolVars, app, ganache, "AgreementLiquidatedV2", tx.blockNumber); - await app.shutdown(); - protocolHelper.expectLiquidationV2(result[0], AGENT_ACCOUNT, accounts[0], "1"); - } catch (err) { - protocolHelper.exitWithError(err); - } - }); - - it("Should make liquidation as Pirate", async() => { - try { - const data = protocolVars.cfa.methods.createFlow( - protocolVars.superToken._address, - accounts[2], - "10000000000000000", - "0x" - ).encodeABI(); - await protocolVars.host.methods.callAgreement(protocolVars.cfa._address, data, "0x").send({ - from: accounts[0], - gas: 1000000 - }); - await ganache.helper.timeTravelOnce(1); - await bootNode({pirate: "true"}); - await ganache.helper.timeTravelOnce(60); - const tx = await protocolVars.superToken.methods.transferAll(accounts[2]).send({ - from: accounts[0], - gas: 1000000 - }); - await ganache.helper.timeTravelOnce(14400); - const result = await protocolHelper.waitForEvent(protocolVars, app, ganache, "AgreementLiquidatedV2", tx.blockNumber); - await app.shutdown(); - protocolHelper.expectLiquidationV2(result[0], AGENT_ACCOUNT, accounts[0], "2"); - } catch (err) { - protocolHelper.exitWithError(err); - } - }); - - it("Subscribe to token runtime", async () => { - try { - await bootNode({pic: accounts[0]}); - const data = protocolVars.cfa.methods.createFlow( - protocolVars.superToken._address, - accounts[2], - "10000000000000000", - "0x" - ).encodeABI(); - await protocolVars.host.methods.callAgreement(protocolVars.cfa._address, data, "0x").send({ - from: accounts[0], - gas: 1000000 - }); - //await ganache.helper.timeTravelOnce(60); - const tx = await protocolVars.superToken.methods.transferAll(accounts[2]).send({ - from: accounts[0], - gas: 1000000 - }); - const result = await protocolHelper.waitForEvent(protocolVars, app, ganache, "AgreementLiquidatedV2", tx.blockNumber); - await app.shutdown(); - protocolHelper.expectLiquidationV2(result[0], AGENT_ACCOUNT, accounts[0], "0"); - } catch (err) { - protocolHelper.exitWithError(err); - } - }); -}); diff --git a/_old_tests/integration/gas.integration.test.js b/_old_tests/integration/gas.integration.test.js deleted file mode 100644 index 635765ff..00000000 --- a/_old_tests/integration/gas.integration.test.js +++ /dev/null @@ -1,76 +0,0 @@ -const protocolHelper = require("../../test/utils/protocolHelper"); -const expect = require("chai").expect; -const ganache = require("../../test/utils/ganache"); -const App = require("../../src/app"); - -const AGENT_ACCOUNT = "0x868D9F52f84d33261c03C8B77999f83501cF5A99"; - -let app, accounts, snapId, protocolVars, web3; - -const bootNode = async (config) => { - const sentinelConfig = protocolHelper.getSentinelConfig(config); - app = new App(sentinelConfig); - app.start(); - while (!app.isInitialized()) { - await protocolHelper.timeout(5000); - } -}; - -const closeNode = async (force = false) => { - if (app !== undefined) { - return app.shutdown(force); - } -}; - -describe("Gas Integration tests", () => { - before(async function () { - protocolVars = await protocolHelper.setup(ganache.provider, AGENT_ACCOUNT); - web3 = protocolVars.web3; - accounts = protocolVars.accounts; - snapId = await ganache.helper.takeEvmSnapshot(); - }); - - beforeEach(async () => { - }); - - afterEach(async () => { - try { - snapId = await ganache.helper.revertToSnapShot(snapId.result); - } catch (err) { - protocolHelper.exitWithError(err); - } - }); - - after(async () => { - if(!app._isShutdown) { - await closeNode(true); - } - await ganache.close(); - }); - - it("Scale gas on timeout", async () => { - try { - const data = protocolVars.cfa.methods.createFlow( - protocolVars.superToken._address, - accounts[2], - "10000000000000000", - "0x" - ).encodeABI(); - await protocolVars.host.methods.callAgreement(protocolVars.cfa._address, data, "0x").send({ - from: accounts[0], - gas: 1000000 - }); - const tx = await protocolVars.superToken.methods.transferAll(accounts[2]).send({ - from: accounts[0], - gas: 1000000 - }); - await bootNode({pic: accounts[0], tx_timeout: 2}); - app.setTestFlag("TIMEOUT_ON_LOW_GAS_PRICE", { minimumGas: 3000000000 }); - const result = await protocolHelper.waitForEvent(protocolVars, app, ganache, "AgreementLiquidatedV2", tx.blockNumber); - await app.shutdown(); - protocolHelper.expectLiquidationV2(result[0], AGENT_ACCOUNT, accounts[0], "0"); - } catch (err) { - protocolHelper.exitWithError(err); - } - }); -}); diff --git a/_old_tests/integration/ida.integration.test.js b/_old_tests/integration/ida.integration.test.js deleted file mode 100644 index aaabcbf5..00000000 --- a/_old_tests/integration/ida.integration.test.js +++ /dev/null @@ -1,106 +0,0 @@ -const protocolHelper = require("../../test/utils/protocolHelper"); -const expect = require("chai").expect; -const ganache = require("../../test/utils/ganache"); -const App = require("../../src/app"); - -const AGENT_ACCOUNT = "0x868D9F52f84d33261c03C8B77999f83501cF5A99"; - -let app, accounts, snapId, protocolVars, web3; - -const bootNode = async (config) => { - const sentinelConfig = protocolHelper.getSentinelConfig(config); - app = new App(sentinelConfig); - app.start(); - while (!app.isInitialized()) { - await protocolHelper.timeout(3000); - } -}; - -const closeNode = async (force = false) => { - if (app !== undefined) { - return app.shutdown(force); - } -}; - -describe("IDA integration tests", () => { - before(async function () { - protocolVars = await protocolHelper.setup(ganache.provider, AGENT_ACCOUNT); - web3 = protocolVars.web3; - accounts = protocolVars.accounts; - snapId = await ganache.helper.takeEvmSnapshot(); - }); - - beforeEach(async () => { - }); - - afterEach(async () => { - try { - snapId = await ganache.helper.revertToSnapShot(snapId.result); - } catch (err) { - exitWithError(err); - } - }); - - after(async () => { - if(!app._isShutdown) { - await closeNode(true); - } - await ganache.close(); - }); - - it("Get critical after IDA distribuiton", async () => { - try { - const cfaData = protocolVars.cfa.methods.createFlow( - protocolVars.superToken._address, - accounts[2], - "1000000000000", - "0x" - ).encodeABI(); - await protocolVars.host.methods.callAgreement(protocolVars.cfa._address, cfaData, "0x").send({ - from: accounts[0], - gas: 1000000 - }); - await ganache.helper.timeTravelOnce(60); - await bootNode({pic: accounts[0]}); - const data = protocolVars.ida.methods.createIndex( - protocolVars.superToken._address, 6, "0x" - ).encodeABI(); - await protocolVars.host.methods.callAgreement(protocolVars.ida._address, data, "0x").send({ - from: accounts[0], - gas: 1000000 - }); - const subscriptionData = protocolVars.ida.methods.updateSubscription( - protocolVars.superToken._address, 6, accounts[1], 100, "0x" - ).encodeABI(); - await protocolVars.host.methods.callAgreement(protocolVars.ida._address, subscriptionData, "0x").send({ - from: accounts[0], - gas: 1000000 - }); - const approveSubData = protocolVars.ida.methods.approveSubscription( - protocolVars.superToken._address, accounts[0], 6, "0x" - ).encodeABI(); - await protocolVars.host.methods.callAgreement(protocolVars.ida._address, approveSubData, "0x").send({ - from: accounts[1], - gas: 1000000 - }); - await ganache.helper.timeTravelOnce(60); - const balance = await protocolVars.superToken.methods.realtimeBalanceOfNow(accounts[0]).call(); - const availableBalance = web3.utils.toBN(balance.availableBalance.toString()); - const distData = protocolVars.ida.methods.distribute( - protocolVars.superToken._address, - 6, - availableBalance.sub(web3.utils.toBN("1000000000000")).toString(), - "0x" - ).encodeABI(); - const tx = await protocolVars.host.methods.callAgreement(protocolVars.ida._address, distData, "0x").send({ - from: accounts[0], - gas: 1000000 - }); - const result = await protocolHelper.waitForEvent(protocolVars, app, ganache, "AgreementLiquidatedV2", tx.blockNumber); - await app.shutdown(); - protocolHelper.expectLiquidationV2(result[0], AGENT_ACCOUNT, accounts[0], "0"); - } catch (err) { - exitWithError(err); - } - }); -}); diff --git a/_old_tests/integration/node.integration.test.js b/_old_tests/integration/node.integration.test.js deleted file mode 100644 index 14ec634b..00000000 --- a/_old_tests/integration/node.integration.test.js +++ /dev/null @@ -1,219 +0,0 @@ -const protocolHelper = require("../../test/utils/protocolHelper"); -const expect = require("chai").expect; -const ganache = require("../../test/utils/ganache"); -const App = require("../../src/app"); - -const AGENT_ACCOUNT = "0x868D9F52f84d33261c03C8B77999f83501cF5A99"; - -let app, accounts, snapId, protocolVars, web3; - -const bootNode = async (config) => { - const sentinelConfig = protocolHelper.getSentinelConfig(config); - app = new App(sentinelConfig); - app.start(); - while (!app.isInitialized()) { - await protocolHelper.timeout(5000); - } -}; - -const closeNode = async (force = false) => { - if (app !== undefined) { - return app.shutdown(force); - } -}; - -describe("Agent configurations tests", () => { - before(async function () { - protocolVars = await protocolHelper.setup(ganache.provider, AGENT_ACCOUNT); - web3 = protocolVars.web3; - accounts = protocolVars.accounts; - snapId = await ganache.helper.takeEvmSnapshot(); - }); - - beforeEach(async () => { - }); - - afterEach(async () => { - try { - snapId = await ganache.helper.revertToSnapShot(snapId.result); - } catch (err) { - protocolHelper.exitWithError(err); - } - }); - - after(async () => { - if(!app._isShutdown) { - await closeNode(true); - } - await ganache.close(); - }); - - it("Should use delay paramater when sending liquidation", async () => { - try { - const data = protocolVars.cfa.methods.createFlow( - protocolVars.superToken._address, - accounts[2], - "100000000000", - "0x" - ).encodeABI(); - await protocolVars.host.methods.callAgreement(protocolVars.cfa._address, data, "0x").send({ - from: accounts[0], - gas: 1000000 - }); - await ganache.helper.timeTravelOnce(1); - await bootNode({additional_liquidation_delay: 2700}); - const tx = await protocolVars.superToken.methods.transferAll(accounts[2]).send({ - from: accounts[0], - gas: 1000000 - }); - await ganache.helper.timeTravelOnce(3580, app, true); - const result = await protocolHelper.waitForEvent(protocolVars, app, ganache, "AgreementLiquidatedV2", tx.blockNumber); - await app.shutdown(); - expect(result[0].returnValues.liquidatorAccount).to.equal(AGENT_ACCOUNT); - } catch (err) { - protocolHelper.exitWithError(err); - } - }); - - it("Change state if not getting new blocks", async () => { - try { - const data = protocolVars.cfa.methods.createFlow( - protocolVars.superToken._address, - accounts[2], - "100000000000", - "0x" - ).encodeABI(); - await protocolVars.host.methods.callAgreement(protocolVars.cfa._address, data, "0x").send({ - from: accounts[0], - gas: 1000000 - }); - await ganache.helper.timeTravelOnce(1); - await bootNode(); - let healthy; - while (true) { - await protocolHelper.timeout(9000); - const report = await app.healthReport.fullReport(); - healthy = report.healthy; - if (!healthy) break; - } - await app.shutdown(); - expect(healthy).eq(false); - } catch (err) { - protocolHelper.exitWithError(err); - } - }); - - it("Get PIC on Boot and change after", async () => { - try { - const data = protocolVars.cfa.methods.createFlow( - protocolVars.superToken._address, - accounts[2], - "100000000000", - "0x" - ).encodeABI(); - await protocolVars.host.methods.callAgreement(protocolVars.cfa._address, data, "0x").send({ - from: accounts[0], - gas: 1000000 - }); - //became pic - await protocolVars.superToken.methods.transfer(protocolVars.toga._address, "100000000000000000").send({ - from: accounts[0], - gas: 1000000 - }); - await ganache.helper.timeTravelOnce(5); - await bootNode({toga_contract: protocolVars.toga._address}); - let picInfo - while (true) { - await protocolHelper.timeout(5000); - picInfo = await app.getPICInfo(protocolVars.superToken._address); - if (picInfo.length > 0) break; - } - - expect(picInfo[0].pic).to.be.equal(accounts[0]); - //PIC changes - await protocolVars.superToken.methods.transfer(protocolVars.toga._address, "100000000000000000").send({ - from: accounts[1], - gas: 1000000 - }); - await ganache.helper.timeTravelOnce(5); - while (true) { - await protocolHelper.timeout(8000); - picInfo = await app.getPICInfo(protocolVars.superToken._address); - if (picInfo.length > 0) break; - } - await app.shutdown(); - expect(picInfo[0].pic).to.be.equal(accounts[1]); - } catch (err) { - protocolHelper.exitWithError(err); - } - }); - - it("When observer, no need for wallet / address", async () => { - try{ - await bootNode({observer: "true", fastsync: "false"}); - expect(app.getConfigurationInfo().OBSERVER).to.be.true; - await app.shutdown(); - } catch(err) { - protocolHelper.exitWithError(err); - } - }); - - // not yet supported - it.skip("Start node, subscribe to new Token and perform estimation", async () => { - try { - await bootNode(); - const data = protocolVars.cfa.methods.createFlow( - protocolVars.superToken._address, - accounts[2], - "10000000000000000", - "0x" - ).encodeABI(); - await protocolVars.host.methods.callAgreement(protocolVars.cfa._address, data, "0x").send({ - from: accounts[0], - gas: 1000000 - }); - while (true) { - const estimation = await app.db.queries.getAddressEstimations(accounts[0]); - if (estimation.length > 0) { - console.log(estimation); - break; - } - await protocolHelper.timeout(1000); - } - await protocolHelper.timeout(1000); - const tx = await protocolVars.superToken.methods.transferAll(accounts[2]).send({ - from: accounts[0], - gas: 1000000 - }); - const result = await protocolHelper.waitForEvent(protocolVars, app, ganache, "AgreementLiquidatedV2", tx.blockNumber); - protocolHelper.expectLiquidationV2(result[0], AGENT_ACCOUNT, accounts[0], "0"); - } catch (err) { - protocolHelper.exitWithError(err); - } - }); - // not yet supported - it.skip("When token is listed afterwards, and there is already existing negative accounts, liquidations should still be performed", async () => { - try { - const data = protocolVars.cfa.methods.createFlow( - protocolVars.superToken._address, - accounts[2], - "1000000000000000000", - "0x" - ).encodeABI(); - await protocolVars.host.methods.callAgreement(protocolVars.cfa._address, data, "0x").send({ - from: accounts[0], - gas: 1000000 - }); - const tx = await protocolVars.superToken.methods.transferAll(accounts[2]).send({ - from: accounts[0], - gas: 1000000 - }); - // const timestamp = await ganache.helper.timeTravelOnce(3600 * 4); - await bootNode(); - const result = await protocolHelper.waitForEvent(protocolVars, app, ganache, "AgreementLiquidatedV2", tx.blockNumber); - protocolHelper.expectLiquidationV2(result[0], AGENT_ACCOUNT, accounts[0], "0"); - } catch (err) { - protocolHelper.exitWithError(err); - } - }); -}); diff --git a/src/app.js b/src/app.js index 5b796194..952ef22c 100644 --- a/src/app.js +++ b/src/app.js @@ -11,8 +11,8 @@ const Time = require("./utils/time"); const Timer = require("./utils/timer"); const EventModel = require("./models/EventModel"); const Bootstrap = require("./boot/bootstrap.js"); - -const Repository = require("./database/repository"); +const SystemRepository = require("./database/systemRepository"); +const BusinessRepository = require("./database/businessRepository"); const utils = require("./utils/utils.js"); const HTTPServer = require("./httpserver/server"); const Report = require("./httpserver/report"); @@ -46,7 +46,10 @@ class App { UserConfig: require("./database/models/userConfiguration")(this.db), ThresholdModel: require("./database/models/thresholdModel")(this.db), } - this.db.queries = new Repository(this); + + this.db.sysQueries = SystemRepository.getInstance(this); + this.db.bizQueries = BusinessRepository.getInstance(this); + this.eventTracker = new EventTracker(this); this.client = new Client(this); this.protocol = new Protocol(this); @@ -109,12 +112,12 @@ class App { // return estimations saved on database async getEstimations() { - return this.db.queries.getEstimations(); + return this.db.bizQueries.getEstimations(); } // return PIC saved on database async getPICInfo(onlyTokens) { - return this.db.queries.getPICInfo(onlyTokens); + return this.db.bizQueries.getPICInfo(onlyTokens); } // return configuration used @@ -201,7 +204,7 @@ class App { this.logger.info("unzipping snapshot..."); this.utils.unzip(this.config.DB + ".gz", this.config.DB); await this.db.sync(); - const userSchemaVersion = Number((await this.db.queries.getUserSchemaVersion())[0].user_version); + const userSchemaVersion = Number((await this.db.sysQueries.getUserSchemaVersion())[0].user_version); if(userSchemaVersion !== this.config.SCHEMA_VERSION) { throw Error(`local data schema ${userSchemaVersion} don't match sentinel version ${this.config.SCHEMA_VERSION}. Update and resync sentinel`); } @@ -209,14 +212,14 @@ class App { // drop existing database to force a full boot this.logger.debug(`resyncing database data`); await this.db.sync({force: true}); - await this.db.queries.setUserSchemaVersion(this.config.SCHEMA_VERSION) + await this.db.sysQueries.setUserSchemaVersion(this.config.SCHEMA_VERSION) } else { await this.db.sync(); // fresh database if(!dbFileExist) { - await this.db.queries.setUserSchemaVersion(this.config.SCHEMA_VERSION) + await this.db.sysQueries.setUserSchemaVersion(this.config.SCHEMA_VERSION) } else { - const userSchemaVersion = Number((await this.db.queries.getUserSchemaVersion())[0].user_version); + const userSchemaVersion = Number((await this.db.sysQueries.getUserSchemaVersion())[0].user_version); if(userSchemaVersion !== this.config.SCHEMA_VERSION) { throw Error(`local data schema ${userSchemaVersion} don't match sentinel version ${this.config.SCHEMA_VERSION}. Update and resync sentinel`); } @@ -232,17 +235,17 @@ class App { await this.timer.timeout(3500); process.exit(1); } - await this.db.queries.saveConfiguration(JSON.stringify(userConfig)); + await this.db.sysQueries.saveConfiguration(JSON.stringify(userConfig)); // get json file with tokens and their thresholds limits. Check if it exists and loaded to json object try { const thresholds = require("../thresholds.json"); const tokensThresholds = thresholds.networks[await this.client.getChainId()]; this.config.SENTINEL_BALANCE_THRESHOLD = tokensThresholds.minSentinelBalanceThreshold; // update thresholds on database - await this.db.queries.updateThresholds(tokensThresholds.thresholds); + await this.db.sysQueries.updateThresholds(tokensThresholds.thresholds); } catch (err) { this.logger.warn(`error loading thresholds.json`); - await this.db.queries.updateThresholds({}); + await this.db.sysQueries.updateThresholds({}); this.config.SENTINEL_BALANCE_THRESHOLD = 0; } @@ -287,7 +290,7 @@ class App { async isResyncNeeded(userConfig) { // check important change of configurations - const res = await this.db.queries.getConfiguration(); + const res = await this.db.sysQueries.getConfiguration(); if (res !== null) { const dbuserConfig = JSON.parse(res.config); // if user was filtering tokens and now is not, then should resync diff --git a/src/boot/bootstrap.js b/src/boot/bootstrap.js index 04673b32..a252bce5 100644 --- a/src/boot/bootstrap.js +++ b/src/boot/bootstrap.js @@ -21,7 +21,7 @@ class Bootstrap { if (blockNumber < currentBlockNumber) { try { const queue = this.app.queues.newEstimationQueue(); - const users = await this.app.db.queries.getAccounts(blockNumber); + const users = await this.app.db.bizQueries.getAccounts(blockNumber); for (const user of users) { queue.push({ self: this, @@ -36,8 +36,8 @@ class Bootstrap { await queue.drain(); } - const cfaFlows = await this.app.db.queries.getLastCFAFlows(blockNumber); - const gdaFlows = await this.app.db.queries.getLastGDAFlows(blockNumber); + const cfaFlows = await this.app.db.bizQueries.getLastCFAFlows(blockNumber); + const gdaFlows = await this.app.db.bizQueries.getLastGDAFlows(blockNumber); const flows = [...cfaFlows, ...gdaFlows]; for (const flow of flows) { try { diff --git a/src/database/SQLRepository.js b/src/database/SQLRepository.js new file mode 100644 index 00000000..b9133e9e --- /dev/null +++ b/src/database/SQLRepository.js @@ -0,0 +1,43 @@ +const { QueryTypes } = require("sequelize"); + +class SQLRepository { + constructor(app) { + if(!app) { + throw new Error("SQLRepository: app is not defined"); + } + if (SQLRepository._instance) { + return SQLRepository._instance; + } + this.app = app; + SQLRepository._instance = this; + } + + static getInstance(app) { + if (!this._instance) { + this._instance = new SQLRepository(app); + } + return this._instance; + } + + async executeSQLSelect(query, replacements) { + if(!query || typeof query !== "string") { + throw new Error("SQLRepository: query must be a string"); + } + + if(!replacements) { + replacements = {}; + } + + if(typeof replacements !== "object") { + throw new Error("SQLRepository: replacements must be an object"); + } + + return this.app.db.query(query, { + replacements: replacements, + type: QueryTypes.SELECT + }); + } +} +SQLRepository._instance = null; + +module.exports = SQLRepository; \ No newline at end of file diff --git a/src/database/repository.js b/src/database/businessRepository.js similarity index 62% rename from src/database/repository.js rename to src/database/businessRepository.js index 594375ea..9d4830a8 100644 --- a/src/database/repository.js +++ b/src/database/businessRepository.js @@ -2,18 +2,41 @@ const { QueryTypes, Op } = require("sequelize"); +const SQLRepository = require("./SQLRepository"); + +class BusinessRepository { -// rename to Queries? -class Repository { constructor(app) { + console.log("BusinessRepository constructor") + if(!app) { + throw new Error("BusinessRepository: app is not defined"); + } + + if (BusinessRepository._instance) { + return BusinessRepository._instance; + } + this.app = app; + if(!this.app.db.SQLRepository) { + this.app.db.SQLRepository = SQLRepository.getInstance(app); + } + BusinessRepository._instance = this; + } + + static getInstance(app) { + console.log("calling getInstance from businessRepository"); + if (!BusinessRepository._instance) { + + BusinessRepository._instance = new BusinessRepository(app); + } + return BusinessRepository._instance; } async getAccounts(fromBlock = 0) { const sqlquery = `SELECT DISTINCT superToken, account FROM ( SELECT * FROM ( SELECT superToken, sender as account, flowRate from flowupdateds - WHERE blockNumber > :bn + WHERE blockNumber >= :bn GROUP BY hashId HAVING MAX(blockNumber) order by blockNumber desc , superToken, hashId @@ -22,7 +45,7 @@ class Repository { UNION ALL SELECT * FROM ( SELECT superToken, receiver as account, flowRate from flowupdateds - WHERE blockNumber > :bn + WHERE blockNumber >= :bn GROUP BY hashId HAVING MAX(blockNumber) order by blockNumber desc , superToken, hashId @@ -31,7 +54,7 @@ class Repository { UNION ALL SELECT * FROM ( SELECT superToken, distributor as account, newDistributorToPoolFlowRate as flowRate from flowdistributionupdateds - where blockNumber > :bn + where blockNumber >= :bn GROUP BY agreementId HAVING MAX(blockNumber) order by blockNumber desc , superToken, agreementId @@ -39,11 +62,7 @@ class Repository { WHERE Y.flowRate <> 0 ) AS Z ORDER BY superToken`; - - return this.app.db.query(sqlquery, { - replacements: {bn: fromBlock}, - type: QueryTypes.SELECT - }); + return this.app.db.SQLRepository.executeSQLSelect(sqlquery, { bn: fromBlock }); } async getLastCFAFlows(fromBlock = 0) { @@ -55,10 +74,7 @@ class Repository { order by blockNumber desc , superToken, hashId ) AS P WHERE P.flowRate <> 0`; - return this.app.db.query(sqlquery, { - replacements: {bn: fromBlock}, - type: QueryTypes.SELECT - }); + return this.app.db.SQLRepository.executeSQLSelect(sqlquery, { bn: fromBlock }); } async getLastGDAFlows(fromBlock = 0) { @@ -70,10 +86,7 @@ class Repository { order by blockNumber desc , superToken, agreementId ) AS P WHERE P.flowRate <> 0`; - return this.app.db.query(sqlquery, { - replacements: {bn: fromBlock}, - type: QueryTypes.SELECT - }); + return this.app.db.SQLRepository.executeSQLSelect(sqlquery, { bn: fromBlock }); } async getAddressEstimations(address) { @@ -93,7 +106,6 @@ class Repository { }); } - // liquidations where flowRate is above a certain threshold async getLiquidations(checkDate, onlyTokens, excludeTokens, limitRows, useThresholds = true) { let inSnipped = ""; let inSnippedLimit = ""; @@ -129,19 +141,12 @@ WHERE ${flowRateCondition} AND out.estimation <= :dt ${inSnipped} ORDER BY out.estimation ASC ${inSnippedLimit}`; if (inSnipped !== "") { - return this.app.db.query(sqlquery, { - replacements: { - dt: checkDate, - tokens: tokenFilter - }, - type: QueryTypes.SELECT + return this.app.db.SQLRepository.executeSQLSelect(sqlquery, { + dt: checkDate, + tokens: tokenFilter }); } - - return this.app.db.query(sqlquery, { - replacements: {dt: checkDate}, - type: QueryTypes.SELECT - }); + return this.app.db.SQLRepository.executeSQLSelect(sqlquery, { dt: checkDate }); } async getNumberOfBatchCalls(checkDate, onlyTokens, excludeTokens, useThresholds = true) { @@ -175,46 +180,13 @@ having count(*) > 1 order by count(*) desc`; if (inSnipped !== "") { - return this.app.db.query(sqlquery, { - replacements: { - dt: checkDate, - tokens: tokenFilter - }, - type: QueryTypes.SELECT + return this.app.db.SQLRepository.executeSQLSelect(sqlquery, { + dt: checkDate, + tokens: tokenFilter }); - } - return this.app.db.query(sqlquery, { - replacements: {dt: checkDate}, - type: QueryTypes.SELECT - }); - } - async healthCheck() { - return this.app.db.query("SELECT 1", { - type: QueryTypes.SELECT - }); - } - - async updateBlockNumber(newBlockNumber) { - const systemInfo = await this.app.db.models.SystemModel.findOne(); - if (systemInfo !== null && systemInfo.blockNumber < newBlockNumber) { - systemInfo.blockNumber = Number(newBlockNumber); - systemInfo.superTokenBlockNumber = Number(newBlockNumber); - } - return systemInfo.save(); - } - - async getConfiguration() { - return this.app.db.models.UserConfig.findOne(); - } - - async saveConfiguration(configString) { - const fromDB = await this.app.db.models.UserConfig.findOne(); - if (fromDB !== null) { - fromDB.config = configString; - return fromDB.save(); } - return this.app.db.models.UserConfig.create({config: configString}); + return this.app.db.SQLRepository.executeSQLSelect(sqlquery, { dt: checkDate }); } async getPICInfo(onlyTokens) { @@ -225,44 +197,12 @@ order by count(*) desc`; const sqlquery = `SELECT address, symbol, name, pic from supertokens ${inSnipped}`; if (inSnipped !== "") { - return this.app.db.query(sqlquery, { - replacements: { - tokens: onlyTokens - }, - type: QueryTypes.SELECT - }); + return this.app.db.SQLRepository.executeSQLSelect(sqlquery, { tokens: onlyTokens }); } - - return this.app.db.query(sqlquery, { - type: QueryTypes.SELECT - }); + return this.app.db.SQLRepository.executeSQLSelect(sqlquery); } - async updateThresholds(thresholds) { - await this.app.db.models.ThresholdModel.destroy({truncate: true}); - // check if thresholds is empty object - if(Object.keys(thresholds).length === 0) { - // create table without table data - return this.app.db.models.ThresholdModel.sync(); - } else { - // from json data save it to table - for (const threshold of thresholds) { - await this.app.db.models.ThresholdModel.create(threshold); - } - } - } - - async getUserSchemaVersion() { - return this.app.db.query("PRAGMA user_version;", { - type: QueryTypes.SELECT - }); - } - - async setUserSchemaVersion(schemaVersion){ - return this.app.db.query(`PRAGMA user_version = ${schemaVersion};`, { - type: QueryTypes.SELECT - }); - } } +BusinessRepository._instance = null; -module.exports = Repository; +module.exports = BusinessRepository; diff --git a/src/database/systemRepository.js b/src/database/systemRepository.js new file mode 100644 index 00000000..35bd050c --- /dev/null +++ b/src/database/systemRepository.js @@ -0,0 +1,88 @@ +const { + QueryTypes, +} = require("sequelize"); +const SQLRepository = require("./SQLRepository"); + +class SystemRepository { + constructor(app) { + console.log("SystemRepository constructor") + if(!app) { + throw new Error("SystemRepository: app is not defined"); + } + if (SystemRepository._instance) { + return SystemRepository._instance; + } + + this.app = app; + if(!this.app.db.SQLRepository) { + this.app.db.SQLRepository = SQLRepository.getInstance(app); + } + SystemRepository._instance = this; + } + + static getInstance(app) { + console.log("calling getInstance from systemRepository"); + if (!SystemRepository._instance) { + SystemRepository._instance = new SystemRepository(app); + } + return SystemRepository._instance; + } + + async healthCheck() { + return this.app.db.query("SELECT 1", { + type: QueryTypes.SELECT + }); + } + + async updateBlockNumber(newBlockNumber) { + const systemInfo = await this.app.db.models.SystemModel.findOne(); + if (systemInfo !== null && systemInfo.blockNumber < newBlockNumber) { + systemInfo.blockNumber = Number(newBlockNumber); + systemInfo.superTokenBlockNumber = Number(newBlockNumber); + } + return systemInfo.save(); + } + + async getConfiguration() { + return this.app.db.models.UserConfig.findOne(); + } + + async saveConfiguration(configString) { + const fromDB = await this.app.db.models.UserConfig.findOne(); + if (fromDB !== null) { + fromDB.config = configString; + return fromDB.save(); + } + return this.app.db.models.UserConfig.create({config: configString}); + } + + + async updateThresholds(thresholds) { + await this.app.db.models.ThresholdModel.destroy({truncate: true}); + // check if thresholds is empty object + if(Object.keys(thresholds).length === 0) { + // create table without table data + return this.app.db.models.ThresholdModel.sync(); + } else { + // from json data save it to table + for (const threshold of thresholds) { + await this.app.db.models.ThresholdModel.create(threshold); + } + } + } + + async getUserSchemaVersion() { + return this.app.db.query("PRAGMA user_version;", { + type: QueryTypes.SELECT + }); + } + + async setUserSchemaVersion(schemaVersion){ + return this.app.db.query(`PRAGMA user_version = ${schemaVersion};`, { + type: QueryTypes.SELECT + }); + } +} +SystemRepository._instance = null; + +module.exports = SystemRepository; diff --git a/src/httpserver/report.js b/src/httpserver/report.js index 006d8718..ebb81af6 100644 --- a/src/httpserver/report.js +++ b/src/httpserver/report.js @@ -6,7 +6,7 @@ class Report { async checkDatabase () { try { - return (await this.app.db.queries.healthCheck()) !== undefined; + return (await this.app.db.sysQueries.healthCheck()) !== undefined; } catch (err) { this.app.logger.error(`Report.checkDatabase(): ${err}`); return false; diff --git a/src/httpserver/server.js b/src/httpserver/server.js index 37912d55..da39648f 100644 --- a/src/httpserver/server.js +++ b/src/httpserver/server.js @@ -61,7 +61,7 @@ class HTTPServer { return; } - const liquidations = await this.app.db.queries.getLiquidations( + const liquidations = await this.app.db.bizQueries.getLiquidations( this.app.time.getTimeWithDelay(-timeframeInSeconds), this.app.config.TOKENS, this.app.config.EXCLUDED_TOKENS diff --git a/src/protocol/queues.js b/src/protocol/queues.js index a227b15d..510189d9 100644 --- a/src/protocol/queues.js +++ b/src/protocol/queues.js @@ -142,7 +142,12 @@ class Queues { throw Error("Queues.addQueuedEstimation(): Need EstimationQueue to be set first"); } if(this._isShutdown) { - throw Error("Queues.addQueuedEstimation(): shutdown"); + throw Error("Queues.addQueuedEstimation(): shutdown"); + } + + if(this.isEstimationTaskInQueue(token, account)) { + this.app.logger.debug(`Queues.addQueuedEstimation(): estimation task already in queue for account: ${account} token: ${token}`); + return; } this.estimationQueue.push({ self: this, @@ -152,6 +157,35 @@ class Queues { }); } + isEstimationTaskInQueue(token, account) { + if (this.estimationQueue === undefined) { + throw Error("Queues.isEstimationTaskInQueue(): Need EstimationQueue to be set first"); + } + let currentTaskNode = this.estimationQueue._tasks.head; + while (currentTaskNode) { + const taskData = currentTaskNode.data + if (taskData.account === account && taskData.token === token) { + return true; + } + + currentTaskNode = currentTaskNode.next; + } + + return false; + } + + // get all tasks in the queue as array + getEstimationTasks(queue = undefined) { + let currentTaskNode = this.estimationQueue._tasks.head; + const tasks = []; + while (currentTaskNode) { + tasks.push(currentTaskNode.data); + currentTaskNode = currentTaskNode.next; + } + + return tasks; + } + getAgreementQueueLength () { return this.agreementUpdateQueue.length(); } @@ -234,9 +268,7 @@ class Queues { if (["CFA", "GDA"].includes(event.source)) { const accounts = event.source === "CFA" ? [event.sender, event.receiver] : [event.distributor, event.pool]; accounts.forEach(account => { - task.self.app.queues.estimationQueue.push( - task.self.app.queues._createAgreementTask(account, event, task) - ); + task.self.app.queues.addQueuedEstimation(event.token, account, "agreementUpdateQueue"); }); } } diff --git a/src/web3client/eventTracker.js b/src/web3client/eventTracker.js index 7b83486b..6ed1ac1d 100644 --- a/src/web3client/eventTracker.js +++ b/src/web3client/eventTracker.js @@ -65,7 +65,7 @@ class EventTracker { if (_oldBlock) { await self.getPastBlockAndParseEvents(_oldBlock + 1, newBlockWithOffset); self.updateBlockNumber(newBlockWithOffset); - self.app.db.queries.updateBlockNumber(newBlockWithOffset); + self.app.db.sysQueries.updateBlockNumber(newBlockWithOffset); } else if (self.oldSeenBlock) { await self.getPastBlockAndParseEvents(_oldBlock, newBlockWithOffset); self.updateBlockNumber(newBlockWithOffset); diff --git a/src/web3client/liquidator.js b/src/web3client/liquidator.js index f45b272e..0539b6eb 100644 --- a/src/web3client/liquidator.js +++ b/src/web3client/liquidator.js @@ -29,7 +29,7 @@ class Liquidator { let haveBatchWork = []; // if we have a batchLiquidator contract, use batch calls if (this.app.config.BATCH_CONTRACT !== undefined) { - haveBatchWork = await this.app.db.queries.getNumberOfBatchCalls(checkDate, this.app.config.TOKENS, this.app.config.EXCLUDED_TOKENS); + haveBatchWork = await this.app.db.bizQueries.getNumberOfBatchCalls(checkDate, this.app.config.TOKENS, this.app.config.EXCLUDED_TOKENS); if(haveBatchWork.length > 0) { this.app.logger.debug(JSON.stringify(haveBatchWork)); } @@ -37,7 +37,7 @@ class Liquidator { if (haveBatchWork.length > 0) { await this.multiTermination(haveBatchWork, checkDate); } else { - const work = await this.app.db.queries.getLiquidations(checkDate, this.app.config.TOKENS, this.app.config.EXCLUDED_TOKENS, this.app.config.MAX_TX_NUMBER); + const work = await this.app.db.bizQueries.getLiquidations(checkDate, this.app.config.TOKENS, this.app.config.EXCLUDED_TOKENS, this.app.config.MAX_TX_NUMBER); await this.singleTerminations(work); } } catch (err) { @@ -133,7 +133,7 @@ class Liquidator { async multiTermination (batchWork, checkDate) { for (const batch of batchWork) { let liquidations = []; - const streams = await this.app.db.queries.getLiquidations( + const streams = await this.app.db.bizQueries.getLiquidations( checkDate, batch.superToken, this.app.config.EXCLUDED_TOKENS, diff --git a/test/integration/cfa.integration.test.js b/test/integration/cfa.integration.test.js index adbbc7b5..c65c8cdf 100644 --- a/test/integration/cfa.integration.test.js +++ b/test/integration/cfa.integration.test.js @@ -140,11 +140,11 @@ describe("CFA tests", () => { await helper.operations.createStream(helper.sf.superToken.options.address, accounts[5], accounts[2], "100000000000000"); await ganache.helper.timeTravelOnce(provider, web3, 1); await bootNode({pic: ZERO_ADDRESS, resolver: helper.sf.resolver.options.address, log_level: "debug", toga_contract: helper.togaAddress}); - const firstEstimation = await app.db.queries.getAddressEstimations(accounts[5]); + const firstEstimation = await app.db.bizQueries.getAddressEstimations(accounts[5]); await ganache.helper.timeTravelUntil(provider, web3, 1, 20); await helper.operations.updateStream(helper.sf.superToken.options.address, accounts[5], accounts[2], "1"); await ganache.helper.timeTravelUntil(provider, web3, 1, 20); - const secondEstimation = await app.db.queries.getAddressEstimations(accounts[5]); + const secondEstimation = await app.db.bizQueries.getAddressEstimations(accounts[5]); await app.shutdown(); console.log("Estimation 1: ", firstEstimation[0].estimation); console.log("Estimation 2: ", secondEstimation[0].estimation); diff --git a/test/unit-tests/database/SQLRepository.test.js b/test/unit-tests/database/SQLRepository.test.js new file mode 100644 index 00000000..15f4293e --- /dev/null +++ b/test/unit-tests/database/SQLRepository.test.js @@ -0,0 +1,64 @@ +const sinon = require('sinon'); +const { expect } = require('chai'); +const SQLRepository = require('../../../src/database/SQLRepository'); +const {QueryTypes} = require("sequelize"); + +describe('SQLRepository', () => { + let appMock; + let baseRepo; + + beforeEach(() => { + appMock = { + db: { + query: sinon.stub() + } + }; + baseRepo = SQLRepository.getInstance(appMock); + }); + + afterEach(() => { + sinon.restore(); + SQLRepository._instance = null; + }); + + it("#1 - should get instance", () => { + const instance = SQLRepository.getInstance(appMock); + expect(instance).to.be.instanceOf(SQLRepository); + }); + + it("#2 - should throw an error if app is not defined", () => { + expect(() => new SQLRepository()).to.throw("SQLRepository: app is not defined"); + }); + + it('#3 - should execute SQL with replacements', async () => { + const query = `SELECT * FROM users WHERE id = ?`; + const replacements = [1]; + + await baseRepo.executeSQLSelect(query, replacements); + + expect(appMock.db.query.calledWith(query, { + replacements: replacements, + type: QueryTypes.SELECT + })).to.be.true; + }); + + it('#4 - should execute SQL without replacements', async () => { + const query = `SELECT * FROM users`; + await baseRepo.executeSQLSelect(query); + + expect(appMock.db.query.calledWith(query, { + replacements: {}, + type: QueryTypes.SELECT + })).to.be.true; + }); + + it("#5 - should throw an error if replacements is not an object", async () => { + const query = `SELECT * FROM users`; + const replacements = "test"; + try { + await baseRepo.executeSQLSelect(query, replacements); + } catch (e) { + expect(e.message).to.equal("SQLRepository: replacements must be an object"); + } + }); +}); diff --git a/test/unit-tests/protocol/queues.test.js b/test/unit-tests/protocol/queues.test.js index 2a8eaf68..b352a34e 100644 --- a/test/unit-tests/protocol/queues.test.js +++ b/test/unit-tests/protocol/queues.test.js @@ -16,12 +16,34 @@ describe("Queues", () => { info: sinon.stub(), error: sinon.stub() }, - + circularBuffer: { + push: sinon.stub() + }, _isShutdown: false, config: { NUM_RETRIES: 3, - CONCURRENCY: 2 + CONCURRENCY: 5 + }, + protocol: { + liquidationData: sinon.stub().resolves({}), + getCFAAgreementEvents: sinon.stub().resolves([]), + getGDAgreementEvents: sinon.stub().resolves([]), + }, + db: { + models: { + AccountEstimationModel: { + upsert: sinon.stub().resolves() + }, + AgreementModel: { + upsert: sinon.stub().resolves() + } + } }, + client: { + superToken: { + isSuperTokenRegistered: sinon.stub().resolves(true) + } + } }; queue = new Queues(appMock); @@ -31,23 +53,90 @@ describe("Queues", () => { sandbox.restore(); }); + it("#1.1 - should initialize queues correctly", () => { + queue.init(); + + expect(queue.estimationQueue).to.exist; + expect(queue.agreementUpdateQueue).to.exist; + }); - it("#1.1 - should not run if app is shutting down", async () => { + it("#1.2 - should not run if app is shutting down", async () => { appMock._isShutdown = true; + await queue.run(sinon.stub(), 5000); + expect(appMock.logger.info.calledOnce).to.be.true; expect(appMock.logger.info.calledWith("app.shutdown() - closing queues")).to.be.true; }); + it("#1.3 - should add new estimation task if not exists", async () => { + queue.init(); + // don't process anything + queue.estimationQueue.concurrency = 0; + await queue.addQueuedEstimation("0xToken", "0xAccount", "caller1"); + const taskExist = queue.isEstimationTaskInQueue("0xToken", "0xAccount"); + expect(taskExist).to.be.true; + }); - it("#1.2 - should not add tasks to the queue if app is shutting down", async () => { + it("#1.3.1 - should add new estimations tasks if not exists", async () => { + queue.init(); + // don't process anything + queue.estimationQueue.concurrency = 0; + await queue.addQueuedEstimation("0xToken", "0xAccount", "caller1"); + await queue.addQueuedEstimation("0xToken", "0xAccount2", "caller1"); + await queue.addQueuedEstimation("0xToken", "0xAccount3", "caller1"); + await queue.addQueuedEstimation("0xToken", "0xAccount4", "caller1"); + const tasks = queue.getEstimationTasks(); + expect(tasks.length).to.equal(4); + // test each task + expect(tasks[0].token).to.equal("0xToken"); + expect(tasks[0].account).to.equal("0xAccount"); + expect(tasks[0].parentCaller).to.equal("caller1"); + + expect(tasks[1].token).to.equal("0xToken"); + expect(tasks[1].account).to.equal("0xAccount2"); + expect(tasks[1].parentCaller).to.equal("caller1"); + + expect(tasks[2].token).to.equal("0xToken"); + expect(tasks[2].account).to.equal("0xAccount3"); + expect(tasks[2].parentCaller).to.equal("caller1"); + + expect(tasks[3].token).to.equal("0xToken"); + expect(tasks[3].account).to.equal("0xAccount4"); + expect(tasks[3].parentCaller).to.equal("caller1"); + }); + + it("#1.4 - should not add duplicate estimation task", async () => { + queue.init(); + // don't process anything + queue.estimationQueue.concurrency = 0; + await queue.addQueuedEstimation("0xToken", "0xAccount", "caller1"); + await queue.addQueuedEstimation("0xToken", "0xAccount", "caller2"); + const tasks = queue.getEstimationTasks(); + expect(tasks.length).to.equal(1); + expect(appMock.logger.debug.calledOnce).to.be.true; + }); + + it("#1.5 - should shut down the queue correctly", async () => { queue.init(); await queue.shutdown(); - try { - await queue.addQueuedEstimation("token", "account", "source"); - expect.fail("Expected addQueuedEstimation to throw"); - } catch (error) { - expect(error.message).to.include("Queues.addQueuedEstimation(): shutdown"); - } + expect(queue.estimationQueue.paused).to.be.true; + expect(queue.agreementUpdateQueue.paused).to.be.true; + expect(appMock.circularBuffer.push.calledOnce).to.be.true; + expect(appMock.circularBuffer.push.calledWith("shutdown", null, "queues shutting down")).to.be.true; + }); + + it.skip("#1.6 - should retry estimation tasks up to NUM_RETRIES times", async () => { + const faultyEstimationFunction = sinon.stub().rejects(new Error("Test Error")); + appMock.protocol.liquidationData = faultyEstimationFunction; + + queue.init(); + // don't process anything + //queue.estimationQueue.concurrency = 0; + await queue.addQueuedEstimation("0xToken", "0xAccount", "caller1"); + //await queue.estimationQueue.drain(); + + // Ensure it was called NUM_RETRIES times + expect(faultyEstimationFunction.callCount).to.equal(appMock.config.NUM_RETRIES); }); });