From 056ed37d8ea59db0a0218c9e37930b6a25950fd7 Mon Sep 17 00:00:00 2001 From: roleengineer Date: Fri, 10 Jul 2020 14:51:42 +0300 Subject: [PATCH 1/2] initial oracle unit tests --- deploy.js | 22 +- src/ExampleClient.sol | 14 +- src/Oracle.sol | 13 + test/test.js | 555 +++++++++++++++++++++++++++++++++++++++++- 4 files changed, 586 insertions(+), 18 deletions(-) diff --git a/deploy.js b/deploy.js index 1bbd5b5..1ccb47b 100644 --- a/deploy.js +++ b/deploy.js @@ -7,6 +7,7 @@ const Machine = require('./build/Machine.json'); const Merkle = require('./build/Merkle.json'); const Oracle = require('./build/Oracle.json'); const Court = require('./build/Court.json'); +const Client = require('./build/EMOClient.json'); const encodeFunctionType = (...args) => { const isBytes24 = @@ -35,7 +36,7 @@ const encodeFunctionType = (...args) => { if (!contract.functions.hasOwnProperty(functionName)) { throw new Error("The given contract does not have the function that was given!"); } - + const funcFragment = contract.interface.fragments.reduce((acc, cv) => { if (cv.type === "function" && cv.name === functionName) return cv; @@ -43,7 +44,7 @@ const encodeFunctionType = (...args) => { return acc; }); const funcSelector = contract.interface.getSighash(funcFragment); - + return (contract.address + funcSelector.replace("0x", "")); } else { throw new Error("Wrong argument types!"); @@ -51,17 +52,17 @@ const encodeFunctionType = (...args) => { } const pimpOracle = (oracle) => { - + oracle.interface._abiCoder._getCoder = function (param) { if (param.type === "function") { return this.__proto__._getCoder({...param, type: 'bytes24'}); } return this.__proto__._getCoder(param); } - + } -const deploy = (wallet, machineFilePath) => async () => { +const deploy = (wallet, machineFilePath, defaultTimeout) => async () => { const machine = await deployContract( wallet, Machine, @@ -76,6 +77,7 @@ const deploy = (wallet, machineFilePath) => async () => { const machineString = machineFilePath + ":Machine"; link(Oracle, machineString, machine.address); link(Court, machineString, machine.address); + link(Client, machineString, machine.address); } catch { throw new Error("Linking the Machine failed. Are you deploying with the same Machine you compiled?"); } @@ -83,7 +85,7 @@ const deploy = (wallet, machineFilePath) => async () => { const oracle = await deployContract( wallet, Oracle, - [] + ["100000000000000000", 1000, wallet.address] ); const court = await deployContract( wallet, @@ -91,6 +93,12 @@ const deploy = (wallet, machineFilePath) => async () => { [] ); + const client = await deployContract( + wallet, + Client, + [oracle.address, defaultTimeout] + ); + pimpOracle(oracle); machine.gen = getStructGeneratorsForCode( fs.readFileSync( @@ -98,7 +106,7 @@ const deploy = (wallet, machineFilePath) => async () => { ) ); - return [machine, merkle, oracle, court]; + return [machine, merkle, oracle, court, client]; } module.exports = { diff --git a/src/ExampleClient.sol b/src/ExampleClient.sol index 2960e0a..76d8753 100644 --- a/src/ExampleClient.sol +++ b/src/ExampleClient.sol @@ -10,7 +10,7 @@ import "./Machine.template.sol"; contract EMOClient { IOracle oracle; - uint defaultTimeout; + uint public defaultTimeout; mapping(bytes32 => Machine.Seed) seeds; // initialStateHash => seed @@ -38,7 +38,7 @@ contract EMOClient { } function failCallback(bytes32 _questionKey) external { - if (timesRetried[_questionKey] > 3) { + if (timesRetried[_questionKey] >= 2) { failed[_questionKey] = true; } else { _retry(_questionKey); @@ -58,8 +58,16 @@ contract EMOClient { return images[imageHash]; } + function showSeedByInitialStateHash(bytes32 _initialStateHash) external view returns(Machine.Seed memory) { + return seeds[_initialStateHash]; + } + + function imageHashForExampleMachine(Machine.Image memory _image) public pure returns(bytes32) { + return keccak256(abi.encodePacked(_image.sum)); + } + // Maybe set visibility to public, so the user can also compute and use initialStateHash instead of seed - function _seedToInitialStateHash(Machine.Seed memory _seed) internal pure returns(bytes32) { + function _seedToInitialStateHash(Machine.Seed memory _seed) public pure returns(bytes32) { return Machine.stateHash(Machine.create(_seed)); } diff --git a/src/Oracle.sol b/src/Oracle.sol index b13c1d3..673c606 100644 --- a/src/Oracle.sol +++ b/src/Oracle.sol @@ -93,6 +93,12 @@ contract Oracle is IOracle { uint public STAKE_SIZE; uint public MAX_ANSWER_NUMBER; + constructor(uint _stake_size, uint _max_answer_number, address _court) public { + court = _court; + STAKE_SIZE = _stake_size; + MAX_ANSWER_NUMBER = _max_answer_number; + } + function getQuestion ( bytes32 questionKey ) override external view returns (Question memory) @@ -100,6 +106,13 @@ contract Oracle is IOracle { return questions[questionKey]; } + function getQuestionTime(bytes32 questionKey) external view returns(uint, uint) { + Question memory question = questions[questionKey]; + uint askTime = question.askTime; + uint timeout = question.timeout; + return (askTime, timeout); + } + function getAnswer ( bytes32 answerKey ) override external view returns (Answer memory) diff --git a/test/test.js b/test/test.js index 84613bc..c018049 100644 --- a/test/test.js +++ b/test/test.js @@ -9,16 +9,34 @@ use(solidity); const machine = process.env.MACHINE || "Machine.template.sol"; const fixture = async (provider, [wallet]) => { - const contracts = await deploy(wallet, "temp/" + machine)(); + const contracts = await deploy(wallet, "temp/" + machine, 6)(); return contracts; } +const stake_size = ethers.BigNumber.from('0x16345785d8a0000'); + +// Helper functions. +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} +// Handle timing in a Oracle contract +async function handleOracleTiming(oracle, questionKey, timeoutCount = 1) { + // Let time run, sleep a bit + let time = await oracle.getQuestionTime(questionKey); + let askTime = Number(time[0] * 1000); + let timeout = Number(time[1] * 1000); + let now = Date.now(); + expect(now < askTime + timeout * timeoutCount, 'Now should be less than askTime + timeout.').to.be.true; + let waitTime = askTime + timeout * timeoutCount - now; + await sleep(waitTime); +} + describe('EMO', function () { - this.timeout(5000); + this.timeout(0); it('Can call ask with bytes24', async () => { - const [machine, merkle, oracle, court] = await loadFixture(fixture); + const [machine, merkle, oracle, court, client] = await loadFixture(fixture); const askTx = await oracle.ask( machine.gen.genSeed(), @@ -32,8 +50,8 @@ describe('EMO', function () { }); it('Can call ask with address and selector', async () => { - const [machine, merkle, oracle, court] = await loadFixture(fixture); - + const [machine, merkle, oracle, court, client] = await loadFixture(fixture); + const askTx = await oracle.ask( machine.gen.genSeed(), 5, @@ -46,8 +64,8 @@ describe('EMO', function () { }); it('Can call ask with contract', async () => { - const [machine, merkle, oracle, court] = await loadFixture(fixture); - + const [machine, merkle, oracle, court, client] = await loadFixture(fixture); + const askTx = await oracle.ask( machine.gen.genSeed(), 5, @@ -58,5 +76,526 @@ describe('EMO', function () { expect('ask').to.be.calledOnContract(oracle); }); - + + it('Ask question, give one answer, resolve with success', async () => { + const [machine, merkle, oracle, court, client] = await loadFixture(fixture); + const wallets = await client.provider.getWallets(); + let _court = await oracle.court(); + expect(_court).to.equal(wallets[0].address); + + // Actors + const asker = wallets[1]; + const answerer = wallets[2]; + const resolver = wallets[3]; + + // Inputs + const seed = { + "nums": [1, 2, 3, 4, 5] + }; + + let sum = 0; + for (let i = 0; i < seed.nums.length; i++) { + sum += seed.nums[i]; + } + + let image = { + "sum": sum + }; + + let successCallback = client.address + client.interface.getSighash(client.interface.getFunction('successCallback')).replace('0x',''); + let failCallback = client.address + client.interface.getSighash(client.interface.getFunction('failCallback')).replace('0x',''); + + let questionKey = await client._seedToInitialStateHash(seed); + + // Ask question + await expect(client.connect(asker).askOracle(seed)) + .to.emit(oracle, 'NewQuestion') + //.withArgs(questionKey, testcase, asker); // for this stuff i need a function that works in ethers js to create an array object from seed js object like this [ + /* + [ + BigNumber { _hex: '0x01', _isBigNumber: true }, + BigNumber { _hex: '0x02', _isBigNumber: true }, + BigNumber { _hex: '0x03', _isBigNumber: true }, + BigNumber { _hex: '0x04', _isBigNumber: true }, + BigNumber { _hex: '0x05', _isBigNumber: true } + ], + nums: [ + BigNumber { _hex: '0x01', _isBigNumber: true }, + BigNumber { _hex: '0x02', _isBigNumber: true }, + BigNumber { _hex: '0x03', _isBigNumber: true }, + BigNumber { _hex: '0x04', _isBigNumber: true }, + BigNumber { _hex: '0x05', _isBigNumber: true } + ] + ] + */ + expect('ask').to.be.calledOnContractWith(oracle, [seed, 6, successCallback, failCallback]); + + /* + let b = await oracle.queryFilter(oracle.filters.NewQuestion()); + console.log(b[0].args[1]); + */ + // Receive imageHash + let imageHash = await client.imageHashForExampleMachine(image); + + // Give one answer + let answererBalanceInitial = await answerer.getBalance(); + await expect(oracle.connect(answerer).answer(questionKey, imageHash, {value: stake_size})) + .to.emit(oracle, 'NewAnswer') + .withArgs(questionKey, imageHash); + + let answererBalanceAfterAnswer = await answerer.getBalance(); + expect(answererBalanceInitial.sub(answererBalanceAfterAnswer) > stake_size, 'Answerer should put stake.').to.be.true; + + // Check oracle has a new answer in a storage + let res = await oracle.getAnswer(imageHash); + expect('getAnswer').to.be.calledOnContractWith(oracle, [imageHash]); + expect(res[0]).to.equal(answerer.address, 'Answerer should match.'); + expect(res[1]).to.equal(0, 'Index in question member answerKeys array should be 0.'); + expect(res[2]).to.equal(questionKey, 'questionKey should match.'); + + // Try to resolve with success + await expect(oracle.connect(resolver).resolveSuccess(imageHash, image)) + .to.be.revertedWith('Fuck.Answering is still in progress.'); + await handleOracleTiming(oracle, questionKey); + + // Resolve with success + await expect(oracle.connect(resolver).resolveSuccess(imageHash, image)) + .to.emit(oracle, 'QuestionResolvedSuccessfully'); + //.withArgs(questionKey, image.sum.toString()); // the same case as previously + /* + let b = await oracle.queryFilter(oracle.filters.QuestionResolvedSuccessfully()); + console.log(b[0].args); + */ + // Check stogare effects (answer was deleted, question was deleted) + res = await oracle.getAnswer(imageHash); + expect('getAnswer').to.be.calledOnContractWith(oracle, [imageHash]); + expect(res[0]).to.equal("0x0000000000000000000000000000000000000000", 'Answerer should be deleted.'); + expect(res[1]).to.equal(0, 'Index in question member answerKeys array should be 0.'); + expect(res[2]).to.equal("0x0000000000000000000000000000000000000000000000000000000000000000", 'questionKey should be deleted.'); + res = await oracle.getQuestionTime(questionKey); + expect('getQuestionTime').to.be.calledOnContractWith(oracle, [questionKey]); + expect(res[0]).to.equal(0, 'askTime should be default value.'); + expect(res[1]).to.equal(0, 'timeout should be default value.'); + + // Check answerer balance changes + let answererBalanceAfterResolve = await answerer.getBalance(); + expect(answererBalanceAfterResolve.sub(answererBalanceAfterAnswer), 'Answerer should receive stake back.').to.equal(stake_size); + + // Check callback + res = await client.connect(asker).cache(questionKey); + expect('cache').to.be.calledOnContractWith(client, [questionKey]); + expect(res, 'imageHash should match in a cache.').to.equal(imageHash); + res = await client.connect(asker).showImageByInitialStateHash(questionKey); + expect('showImageByInitialStateHash').to.be.calledOnContractWith(client, [questionKey]); + expect(res[0], 'Image should match.').to.equal(image.sum.toString()); + + }); + + it('Ask question, do not answer, resolve with fail', async () => { + + const [machine, merkle, oracle, court, client] = await loadFixture(fixture); + const wallets = await client.provider.getWallets(); + + // Actors + const asker = wallets[1]; + const resolver = wallets[2]; + + // Inputs + const seed = { + "nums": [1, 2, 3, 4, 5] + }; + + let successCallback = client.address + client.interface.getSighash(client.interface.getFunction('successCallback')).replace('0x',''); + let failCallback = client.address + client.interface.getSighash(client.interface.getFunction('failCallback')).replace('0x',''); + + let questionKey = await client._seedToInitialStateHash(seed); + + // Ask question + await expect(client.connect(asker).askOracle(seed)) + .to.emit(oracle, 'NewQuestion') + //.withArgs(questionKey, seed, asker); + expect('ask').to.be.calledOnContractWith(oracle, [seed, 6, successCallback, failCallback]); + + //Due to EMOClient contract first and second call failCallback will retry to ask the question again + async function attemptToResolveFailWithClientRetry() { + // Resolve with fail + await expect(oracle.connect(resolver).resolveFail(questionKey)) + .to.emit(oracle, 'QuestionResolvedUnsuccessfully') + .withArgs(questionKey) + .to.emit(oracle, 'NewQuestion'); + //.withArgs(questionKey, seed, asker); // for this stuff i need a function that + expect('failCallback').to.be.calledOnContractWith(client, [questionKey]); + expect('ask').to.be.calledOnContractWith(oracle, [seed, 6, successCallback, failCallback]); + } + + // Try to resolve with fail (first attempt) + await expect(oracle.connect(resolver).resolveFail(questionKey)) + .to.be.revertedWith('It is not the time to give up yet.'); + await handleOracleTiming(oracle, questionKey, 2); + await attemptToResolveFailWithClientRetry(); + // Check sideeffects after first attempt + let res = await client.timesRetried(questionKey); + expect(res, "Should be one time retried.").to.equal(1); + res = await client.failed(questionKey); + expect(res, "Shouldn't be failed at this moment.").to.be.false; + // Try to resolve with fail (second attempt) + await expect(oracle.connect(resolver).resolveFail(questionKey)) + .to.be.revertedWith('It is not the time to give up yet.'); + await handleOracleTiming(oracle, questionKey, 2); + await attemptToResolveFailWithClientRetry(); + // Check sideeffects after second attempt + res = await client.timesRetried(questionKey); + expect(res, "Should be two times retried.").to.equal(2); + res = await client.failed(questionKey); + expect(res, "Shouldn't be failed at this moment.").to.be.false; + + //Finally resolve with fail + await expect(oracle.connect(resolver).resolveFail(questionKey)) + .to.be.revertedWith('It is not the time to give up yet.'); + await handleOracleTiming(oracle, questionKey, 2); + await expect(oracle.connect(resolver).resolveFail(questionKey)) + .to.emit(oracle, 'QuestionResolvedUnsuccessfully') + .withArgs(questionKey); + + // Check sideeffects + res = await client.timesRetried(questionKey); + expect(res, "Should be two times retried.").to.equal(2); + res = await client.failed(questionKey); + expect(res, "initialStateHash should have true flag in a failed mapping on a client side.").to.be.true; + + // Check that question was removed + res = await oracle.getQuestionTime(questionKey); + expect(res[0], 'askTime should be default value').to.equal(0); + expect(res[1], 'timeout should be default value').to.equal(0); + + }); + + it('Ask question, give 2 answers, resolve with fail', async () => { + + const [machine, merkle, oracle, court, client] = await loadFixture(fixture); + const wallets = await client.provider.getWallets(); + + // Actors + const asker = wallets[1]; + const answerer1 = wallets[2]; + const answerer2 = wallets[3]; + const resolver = wallets[4]; + + // Inputs + const seed = { + "nums": [1, 2, 3, 4, 5] + }; + + let sum = 0; + for (let i = 0; i < seed.nums.length; i++) { + sum += seed.nums[i]; + } + + let image = { + "sum": sum + }; + + let successCallback = client.address + client.interface.getSighash(client.interface.getFunction('successCallback')).replace('0x',''); + let failCallback = client.address + client.interface.getSighash(client.interface.getFunction('failCallback')).replace('0x',''); + + let questionKey = await client._seedToInitialStateHash(seed); + + // Ask question + await expect(client.connect(asker).askOracle(seed)) + .to.emit(oracle, 'NewQuestion') + //.withArgs(questionKey, seed, asker); + + expect('ask').to.be.calledOnContractWith(oracle, [seed, 6, successCallback, failCallback]); + + // Receive imageHashes for two answers (one is correct, another one is wrong) + let imageHash1 = await client.imageHashForExampleMachine(image); + image.sum += 234; + let imageHash2 = await client.imageHashForExampleMachine(image); + image.sum -= 234; + + async function giveTwoAnswers() { + // Give first answer (correct) + let answerer1BalanceInitial = await answerer1.getBalance(); + await expect(oracle.connect(answerer1).answer(questionKey, imageHash1, {value: stake_size})) + .to.emit(oracle, 'NewAnswer') + .withArgs(questionKey, imageHash1); + + let answerer1BalanceAfterAnswer = await answerer1.getBalance(); + expect(answerer1BalanceInitial.sub(answerer1BalanceAfterAnswer) > stake_size, 'Answerer1 should put stake.').to.be.true; + + // Check oracle has a new answer in a storage + let res = await oracle.getAnswer(imageHash1); + expect('getAnswer').to.be.calledOnContractWith(oracle, [imageHash1]); + expect(res[0]).to.equal(answerer1.address, 'Answerer1 should match.'); + expect(res[1]).to.equal(0, 'Index in question member answerKeys array should be 0.'); + expect(res[2]).to.equal(questionKey, 'questionKey should match.'); + + // Give second answer (incorrect) + let answerer2BalanceInitial = await answerer2.getBalance(); + await expect(oracle.connect(answerer2).answer(questionKey, imageHash2, {value: stake_size})) + .to.emit(oracle, 'NewAnswer') + .withArgs(questionKey, imageHash2); + + let answerer2BalanceAfterAnswer = await answerer2.getBalance(); + expect(answerer2BalanceInitial.sub(answerer2BalanceAfterAnswer) > stake_size, 'Answerer2 should put stake.').to.be.true; + + // Check oracle has a new answer in a storage + res = await oracle.getAnswer(imageHash2); + expect('getAnswer').to.be.calledOnContractWith(oracle, [imageHash2]); + expect(res[0]).to.equal(answerer2.address, 'Answerer2 should match.'); + expect(res[1]).to.equal(1, 'Index in question member answerKeys array should be 1.'); + expect(res[2]).to.equal(questionKey, 'questionKey should match.'); + + return [answerer1BalanceAfterAnswer, answerer2BalanceAfterAnswer]; + } + + async function checkAnswerDeletedAndStake(attemptValue) { + // Check that answers was deleted and the stakes are still in Oracle + let res = await oracle.getAnswer(imageHash1); + expect(res[0], 'Answerer1 should be deleted.').to.equal("0x0000000000000000000000000000000000000000"); + expect(res[1], 'Index in question member answerKeys array should be 0.').to.equal(0); + expect(res[2], 'questionKey should be deleted.').to.equal("0x0000000000000000000000000000000000000000000000000000000000000000"); + res = await oracle.getAnswer(imageHash2); + expect(res[0], 'Answerer2 should be deleted.').to.equal("0x0000000000000000000000000000000000000000"); + expect(res[1], 'Index in question member answerKeys array should be 0.').to.equal(0); + expect(res[2], 'questionKey should be deleted.').to.equal("0x0000000000000000000000000000000000000000000000000000000000000000"); + res = await oracle.provider.getBalance(oracle.address); + expect(res, "Stakes from two answers two times should be in Oracle contract.").to.equal(stake_size.mul(attemptValue)); + // Check that answerers loose their stakes + res = await answerer1.getBalance(); + expect(res, "Answerer1 should loose his stake.").to.equal(balancesAfterAnswers[0]); + res = await answerer2.getBalance(); + expect(res, "Answerer2 should loose his stake.").to.equal(balancesAfterAnswers[1]); + } + + let balancesAfterAnswers = await giveTwoAnswers(); + + // TODO: REWRITE this with global function + //Due to EMOClient contract first and second call failCallback will retry to ask the question again + async function attemptToResolveFailWithClientRetry() { + // Resolve with fail + await expect(oracle.connect(resolver).resolveFail(questionKey)) + .to.emit(oracle, 'QuestionResolvedUnsuccessfully') + .withArgs(questionKey) + .to.emit(oracle, 'NewQuestion'); + //.withArgs(questionKey, seed, asker); // for this stuff i need a function that + expect('failCallback').to.be.calledOnContractWith(client, [questionKey]); + expect('ask').to.be.calledOnContractWith(oracle, [seed, 6, successCallback, failCallback]); + } + // TODO: do the below in a loop + // Try to resolve with fail (first attempt) + await expect(oracle.connect(resolver).resolveFail(questionKey)) + .to.be.revertedWith('Fuck.It is not the time to give up yet.'); + await handleOracleTiming(oracle, questionKey, 2); + + await attemptToResolveFailWithClientRetry(); + // Check sideeffects + let res = await client.timesRetried(questionKey); + expect(res, "Should be one time retried.").to.equal(1); + res = await client.failed(questionKey); + expect(res, "Shouldn't be failed at this moment.").to.be.false; + await checkAnswerDeletedAndStake(2); + + // failCallback retries to ask the question one more time, give answers one more time + balancesAfterAnswers = await giveTwoAnswers(); + + // Try to resolve with fail (second attempt) + await expect(oracle.connect(resolver).resolveFail(questionKey)) + .to.be.revertedWith('Fuck.It is not the time to give up yet.'); + await handleOracleTiming(oracle, questionKey, 2); + + await attemptToResolveFailWithClientRetry(); + // Check sideeffects + res = await client.timesRetried(questionKey); + expect(res, "Should be one time retried.").to.equal(2); + res = await client.failed(questionKey); + expect(res, "Shouldn't be failed at this moment.").to.be.false; + await checkAnswerDeletedAndStake(4); + + // failCallback retries to ask the question one more time, give answers one more time + balancesAfterAnswers = await giveTwoAnswers(); + + // Finally resolve with fail + await expect(oracle.connect(resolver).resolveFail(questionKey)) + .to.be.revertedWith('Fuck.It is not the time to give up yet.'); + await handleOracleTiming(oracle, questionKey, 2); + await expect(oracle.connect(resolver).resolveFail(questionKey)) + .to.emit(oracle, 'QuestionResolvedUnsuccessfully') + .withArgs(questionKey); + + // Check sideeffects + res = await client.timesRetried(questionKey); + expect(res, "Should be two times retried.").to.equal(2); + res = await client.failed(questionKey); + expect(res, "initialStateHash should have true flag in a failed mapping on a client side.").to.be.true; + + // Check that question was removed + res = await oracle.getQuestionTime(questionKey); + expect(res[0], 'askTime should be default value').to.equal(0); + expect(res[1], 'timeout should be default value').to.equal(0); + // Check the answers deleted and the question deleted and balances doesn't change, Oracle holds all the stakes + await checkAnswerDeletedAndStake(6); + + }); + + it('Ask question, give 3 answers, failsify 2, resolve with success', async () => { + + const [machine, merkle, oracle, court, client] = await loadFixture(fixture); + const wallets = await client.provider.getWallets(); + + // Actors + const asker = wallets[1]; + const answerer1 = wallets[2]; + const answerer2 = wallets[3]; + const answerer3 = wallets[4]; + const resolver = wallets[5]; + const prosecutor = wallets[6]; + + // Inputs + const seed = { + "nums": [1, 2, 3, 4, 5] + }; + + let sum = 0; + for (let i = 0; i < seed.nums.length; i++) { + sum += seed.nums[i]; + } + + let image = { + "sum": sum + }; + + let successCallback = client.address + client.interface.getSighash(client.interface.getFunction('successCallback')).replace('0x',''); + let failCallback = client.address + client.interface.getSighash(client.interface.getFunction('failCallback')).replace('0x',''); + + let questionKey = await client._seedToInitialStateHash(seed); + + // Ask question + await expect(client.connect(asker).askOracle(seed)) + .to.emit(oracle, 'NewQuestion') + //.withArgs(questionKey, seed, asker); + + expect('ask').to.be.calledOnContractWith(oracle, [seed, 6, successCallback, failCallback]); + + // Receive imageHashes for three answers (one is correct, anothers are wrong) + let imageHash1 = await client.imageHashForExampleMachine(image); + image.sum += 234; + let imageHash2 = await client.imageHashForExampleMachine(image); + image.sum += 126; + let imageHash3 = await client.imageHashForExampleMachine(image); + image.sum -= 360; + + // Give first answer + let answerer1BalanceInitial = await answerer1.getBalance(); + await expect(oracle.connect(answerer1).answer(questionKey, imageHash1, {value: stake_size})) + .to.emit(oracle, 'NewAnswer') + .withArgs(questionKey, imageHash1); + + let answerer1BalanceAfterAnswer = await answerer1.getBalance(); + expect(answerer1BalanceInitial.sub(answerer1BalanceAfterAnswer) > stake_size, 'Answerer1 should put stake.').to.be.true; + + // Check oracle has a new answer in a storage + let res = await oracle.getAnswer(imageHash1); + expect('getAnswer').to.be.calledOnContractWith(oracle, [imageHash1]); + expect(res[0]).to.equal(answerer1.address, 'Answerer1 should match.'); + expect(res[1]).to.equal(0, 'Index in question member answerKeys array should be 0.'); + expect(res[2]).to.equal(questionKey, 'questionKey should match.'); + + // Give second answer + let answerer2BalanceInitial = await answerer2.getBalance(); + await expect(oracle.connect(answerer2).answer(questionKey, imageHash2, {value: stake_size})) + .to.emit(oracle, 'NewAnswer') + .withArgs(questionKey, imageHash2); + + let answerer2BalanceAfterAnswer = await answerer2.getBalance(); + expect(answerer2BalanceInitial.sub(answerer2BalanceAfterAnswer) > stake_size, 'Answerer2 should put stake.').to.be.true; + + // Check oracle has a new answer in a storage + res = await oracle.getAnswer(imageHash2); + expect('getAnswer').to.be.calledOnContractWith(oracle, [imageHash2]); + expect(res[0]).to.equal(answerer2.address, 'Answerer2 should match.'); + expect(res[1]).to.equal(1, 'Index in question member answerKeys array should be 1.'); + expect(res[2]).to.equal(questionKey, 'questionKey should match.'); + + // Give third answer + let answerer3BalanceInitial = await answerer3.getBalance(); + await expect(oracle.connect(answerer3).answer(questionKey, imageHash3, {value: stake_size})) + .to.emit(oracle, 'NewAnswer') + .withArgs(questionKey, imageHash3); + + let answerer3BalanceAfterAnswer = await answerer3.getBalance(); + expect(answerer3BalanceInitial.sub(answerer3BalanceAfterAnswer) > stake_size, 'Answerer3 should put stake.').to.be.true; + + // Check oracle has a new answer in a storage + res = await oracle.getAnswer(imageHash3); + expect('getAnswer').to.be.calledOnContractWith(oracle, [imageHash3]); + expect(res[0]).to.equal(answerer3.address, 'Answerer3 should match.'); + expect(res[1]).to.equal(2, 'Index in question member answerKeys array should be 2.'); + expect(res[2]).to.equal(questionKey, 'questionKey should match.'); + + // Falsify 2 answers + let prosecutorBalanceInitial = await prosecutor.getBalance(); + await expect(oracle.falsify(imageHash2, prosecutor.address)) + .to.emit(oracle, 'AnswerFalsified') + .withArgs(questionKey, imageHash2); + + let prosecutorBalanceAfterFalsify1 = await prosecutor.getBalance(); + expect(prosecutorBalanceAfterFalsify1.sub(prosecutorBalanceInitial), "Prosecutor should receive stake.").to.equal(stake_size); + // here we can check question numberOfUnfalsifiedAnswers and question answerKeys + // Check answer was deleted + res = await oracle.getAnswer(imageHash2); + expect('getAnswer').to.be.calledOnContractWith(oracle, [imageHash2]); + expect(res[0]).to.equal("0x0000000000000000000000000000000000000000", 'Answerer2 should be deleted.'); + expect(res[1]).to.equal(0, 'Index in question member answerKeys array should be 0.'); + expect(res[2]).to.equal("0x0000000000000000000000000000000000000000000000000000000000000000", 'questionKey should be deleted.'); + + await expect(oracle.falsify(imageHash3, prosecutor.address)) + .to.emit(oracle, 'AnswerFalsified') + .withArgs(questionKey, imageHash3); + + let prosecutorBalanceAfterFalsify2 = await prosecutor.getBalance(); + expect(prosecutorBalanceAfterFalsify2.sub(prosecutorBalanceAfterFalsify1), "Prosecutor should receive stake.").to.equal(stake_size); + + // Check answer was deleted + res = await oracle.getAnswer(imageHash3); + expect('getAnswer').to.be.calledOnContractWith(oracle, [imageHash3]); + expect(res[0]).to.equal("0x0000000000000000000000000000000000000000", 'Answerer3 should be deleted.'); + expect(res[1]).to.equal(0, 'Index in question member answerKeys array should be 0.'); + expect(res[2]).to.equal("0x0000000000000000000000000000000000000000000000000000000000000000", 'questionKey should be deleted.'); + + await expect(oracle.connect(resolver).resolveSuccess(imageHash1, image)) + .to.be.revertedWith('Fuck.Answering is still in progress.'); + await handleOracleTiming(oracle, questionKey); + + // Resolve with success + await expect(oracle.connect(resolver).resolveSuccess(imageHash1, image)) + .to.emit(oracle, 'QuestionResolvedSuccessfully'); + //.withArgs(questionKey, image.sum.toString()); // the same case as previously + // Check storage effects + // Check answer was deleted + res = await oracle.getAnswer(imageHash1); + expect('getAnswer').to.be.calledOnContractWith(oracle, [imageHash1]); + expect(res[0]).to.equal("0x0000000000000000000000000000000000000000", 'Answerer1 should be deleted.'); + expect(res[1]).to.equal(0, 'Index in question member answerKeys array should be 0.'); + expect(res[2]).to.equal("0x0000000000000000000000000000000000000000000000000000000000000000", 'questionKey should be deleted.'); + + // Check question was deleted + res = await oracle.getQuestionTime(questionKey); + expect('getQuestionTime').to.be.calledOnContractWith(oracle, [questionKey]); + expect(res[0]).to.equal(0, 'askTime should be default value.'); + expect(res[1]).to.equal(0, 'timeout should be default value.'); + + // Check answerer balance changes + let answerer1BalanceAfterResolve = await answerer1.getBalance(); + expect(answerer1BalanceAfterResolve.sub(answerer1BalanceAfterAnswer), 'Answerer1 should receive stake back.').to.equal(stake_size); + + // Check callback + res = await client.cache(questionKey); + expect('cache').to.be.calledOnContractWith(client, [questionKey]); + expect(res, 'imageHash should match in a cache').to.equal(imageHash1); + res = await client.showImageByInitialStateHash(questionKey); + expect('showImageByInitialStateHash').to.be.calledOnContractWith(client, [questionKey]); + expect(res[0], 'Image should match.').to.equal(image.sum.toString()); + }); + }); From d06d8c59c2dce7fa774002688a983484b65bf32d Mon Sep 17 00:00:00 2001 From: roleengineer Date: Fri, 10 Jul 2020 15:30:53 +0300 Subject: [PATCH 2/2] review suggestion about encodeFunctionType --- test/test.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/test.js b/test/test.js index c018049..c8e87be 100644 --- a/test/test.js +++ b/test/test.js @@ -102,8 +102,8 @@ describe('EMO', function () { "sum": sum }; - let successCallback = client.address + client.interface.getSighash(client.interface.getFunction('successCallback')).replace('0x',''); - let failCallback = client.address + client.interface.getSighash(client.interface.getFunction('failCallback')).replace('0x',''); + let successCallback = encodeFunctionType(client, 'successCallback'); + let failCallback = encodeFunctionType(client, 'failCallback'); let questionKey = await client._seedToInitialStateHash(seed); @@ -205,8 +205,8 @@ describe('EMO', function () { "nums": [1, 2, 3, 4, 5] }; - let successCallback = client.address + client.interface.getSighash(client.interface.getFunction('successCallback')).replace('0x',''); - let failCallback = client.address + client.interface.getSighash(client.interface.getFunction('failCallback')).replace('0x',''); + let successCallback = encodeFunctionType(client, 'successCallback'); + let failCallback = encodeFunctionType(client, 'failCallback'); let questionKey = await client._seedToInitialStateHash(seed); @@ -295,8 +295,8 @@ describe('EMO', function () { "sum": sum }; - let successCallback = client.address + client.interface.getSighash(client.interface.getFunction('successCallback')).replace('0x',''); - let failCallback = client.address + client.interface.getSighash(client.interface.getFunction('failCallback')).replace('0x',''); + let successCallback = encodeFunctionType(client, 'successCallback'); + let failCallback = encodeFunctionType(client, 'failCallback'); let questionKey = await client._seedToInitialStateHash(seed); @@ -465,8 +465,8 @@ describe('EMO', function () { "sum": sum }; - let successCallback = client.address + client.interface.getSighash(client.interface.getFunction('successCallback')).replace('0x',''); - let failCallback = client.address + client.interface.getSighash(client.interface.getFunction('failCallback')).replace('0x',''); + let successCallback = encodeFunctionType(client, 'successCallback'); + let failCallback = encodeFunctionType(client, 'failCallback'); let questionKey = await client._seedToInitialStateHash(seed);