diff --git a/contracts/oracle/Oracle.sol b/contracts/oracle/Oracle.sol new file mode 100644 index 00000000000..5ac252aa397 --- /dev/null +++ b/contracts/oracle/Oracle.sol @@ -0,0 +1,11 @@ +pragma solidity ^0.4.24; + +/** + * @dev implements EIP 1154 (draft) https://github.com/ethereum/EIPs/issues/1161 + */ +interface Oracle { + /** + * Returns stored result + */ + function resultFor(bytes32 id) external view returns (bytes result); +} diff --git a/contracts/oracle/OracleBasic.sol b/contracts/oracle/OracleBasic.sol new file mode 100644 index 00000000000..1a2fc2a6e63 --- /dev/null +++ b/contracts/oracle/OracleBasic.sol @@ -0,0 +1,52 @@ +pragma solidity ^0.4.24; + +import "./Oracle.sol"; +import "./OracleConsumer.sol"; + + +contract OracleBasic is Oracle, OracleConsumer { + + /** + * Result to update by an oracle + * result - actual holder of the data + * exists - flag to indicate if data already exists + */ + struct Result { + bytes result; // data to store + bool exists; // is data already exist + } + + mapping (bytes32 => Result) public results; + + address public oracle; + + /** + * @dev Constructor + * @param _oracle the one who could update the results + */ + constructor(address _oracle) public { + oracle = _oracle; + } + + function receiveResult(bytes32 _id, bytes _result) external { + require(msg.sender == oracle, "Could be called only by an oracle"); + require(!resultExist(_id), "Id is not unique"); + results[_id].exists = true; + results[_id].result = _result; + } + + function resultFor(bytes32 _id) external view returns (bytes) { + require(resultExist(_id), "Data does not exists"); + return results[_id].result; + } + + /** + * @dev Checks if data already stored + * @param _id id of the result + * @return bool + */ + function resultExist(bytes32 _id) public view returns(bool exists) { + return results[_id].exists; + } + +} diff --git a/contracts/oracle/OracleConsumer.sol b/contracts/oracle/OracleConsumer.sol new file mode 100644 index 00000000000..558e85abb0f --- /dev/null +++ b/contracts/oracle/OracleConsumer.sol @@ -0,0 +1,11 @@ +pragma solidity ^0.4.24; + +/** + * @dev implements EIP 1154 (draft) https://github.com/ethereum/EIPs/issues/1161 + */ +interface OracleConsumer { + /** + * Receives data from an oracle + */ + function receiveResult(bytes32 id, bytes result) external; +} diff --git a/test/oracle/Oracle.test.js b/test/oracle/Oracle.test.js new file mode 100644 index 00000000000..f59fd7773b7 --- /dev/null +++ b/test/oracle/Oracle.test.js @@ -0,0 +1,52 @@ +const { EVMRevert } = require('../helpers/EVMRevert'); +const { expectThrow } = require('../helpers/expectThrow'); + +require('chai') + .should(); + +const OracleBasic = artifacts.require('OracleBasic'); + +contract('OracleBasic', function ([owner, oracle, other]) { + const id = 'unique question'; + const result = 'just some answer'; + + beforeEach(async function () { + this.contract = await OracleBasic.new(oracle, { from: owner }); + }); + + it('MUST store the result', async function () { + const resultExits = await this.contract.resultExist(id); + resultExits.should.be.equal(false); + + await this.contract.receiveResult(id, result, { from: oracle }); + + const resultExitsAfterUpdate = await this.contract.resultExist(id); + resultExitsAfterUpdate.should.be.equal(true); + + const receivedResult = await this.contract.resultFor(id); + web3.toUtf8(receivedResult).should.be.equal(result); + }); + + it('MUST revert if the caller is not an oracle authorized to provide the result for that id', async function () { + await expectThrow(this.contract.receiveResult(id, result, { from: other }), EVMRevert); + }); + + it('MUST return the same result for an id after that result is available', async function () { + await this.contract.receiveResult(id, result, { from: oracle }); + const receivedResult = await this.contract.resultFor(id); + web3.toUtf8(receivedResult).should.be.equal(result); + + const receivedResultAgain = await this.contract.resultFor(id); + web3.toUtf8(receivedResultAgain).should.be.equal(result); + }); + + it('MUST revert if receiveResult has been called with the same id before', async function () { + await this.contract.receiveResult(id, result, { from: oracle }); + await expectThrow(this.contract.receiveResult(id, result, { from: oracle }), EVMRevert); + }); + + it('MUST revert if the result for an id is not available yet', async function () { + const nonExistingId = 'non existing id'; + await expectThrow(this.contract.resultFor(nonExistingId), EVMRevert); + }); +});