diff --git a/.gitignore b/.gitignore index 1db612194..6f947faca 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,6 @@ contracts/flattened .vscode yarn.lock -package-lock.json \ No newline at end of file +package-lock.json + +.old \ No newline at end of file diff --git a/contracts/UsingWitnet.sol b/contracts/UsingWitnet.sol index ec43c349b..99fb8c8d4 100644 --- a/contracts/UsingWitnet.sol +++ b/contracts/UsingWitnet.sol @@ -3,9 +3,8 @@ pragma solidity >=0.6.0 <0.7.0; import "./Request.sol"; -import "./Witnet.sol"; import "./WitnetRequestBoardProxy.sol"; - +import "./libs/Witnet.sol"; /** * @title The UsingWitnet contract diff --git a/contracts/WitnetRequestBoardProxy.sol b/contracts/WitnetRequestBoardProxy.sol index 4665ddf11..c10820e33 100644 --- a/contracts/WitnetRequestBoardProxy.sol +++ b/contracts/WitnetRequestBoardProxy.sol @@ -29,19 +29,23 @@ contract WitnetRequestBoardProxy { // Array with the controllers that have been used in the Proxy ControllerInfo[] internal controllers; + + /// ======================================================================= + /// --- Modifiers --------------------------------------------------------- + modifier notIdentical(address _newAddress) { require(_newAddress != address(currentWitnetRequestBoard), "The provided Witnet Requests Board instance address is already in use"); _; } - /** - * @notice Include an address to specify the Witnet Request Board. - * @param _witnetRequestBoardAddress WitnetRequestBoard address. - */ - constructor(address _witnetRequestBoardAddress) public { + + /// ======================================================================= + /// --- Constructor ------------------------------------------------------- + + /// @notice Constructor: initialize controllers list + constructor() public { // Initialize the first epoch pointing to the first controller - controllers.push(ControllerInfo({controllerAddress: _witnetRequestBoardAddress, lastId: 0})); - currentWitnetRequestBoard = WitnetRequestBoardInterface(_witnetRequestBoardAddress); + controllers.push(ControllerInfo({controllerAddress: address(0), lastId: 0})); } /// @dev Posts a data request into the WRB in expectation that it will be relayed and resolved in Witnet with a total reward that equals to msg.value. @@ -106,11 +110,17 @@ contract WitnetRequestBoardProxy { /// @notice Upgrades the Witnet Requests Board if the current one is upgradeable. /// @param _newAddress address of the new block relay to upgrade. - function upgradeWitnetRequestBoard(address _newAddress) external notIdentical(_newAddress) { - // Require the WRB is upgradable - require(currentWitnetRequestBoard.isUpgradable(msg.sender), "The upgrade has been rejected by the current implementation"); + function upgradeWitnetRequestBoard(address _newAddress) public notIdentical(_newAddress) { + // Require current WRB to be upgradable: + require( + address(currentWitnetRequestBoard) == address(0) + || currentWitnetRequestBoard.isUpgradable(msg.sender) + , "The upgrade has been rejected by the current implementation" + ); + // Map the currentLastId to the corresponding witnetRequestBoardAddress and add it to controllers controllers.push(ControllerInfo({controllerAddress: _newAddress, lastId: currentLastId})); + // Upgrade the WRB currentWitnetRequestBoard = WitnetRequestBoardInterface(_newAddress); } diff --git a/contracts/BufferLib.sol b/contracts/libs/BufferLib.sol similarity index 100% rename from contracts/BufferLib.sol rename to contracts/libs/BufferLib.sol diff --git a/contracts/CBOR.sol b/contracts/libs/CBOR.sol similarity index 100% rename from contracts/CBOR.sol rename to contracts/libs/CBOR.sol diff --git a/contracts/Witnet.sol b/contracts/libs/Witnet.sol similarity index 100% rename from contracts/Witnet.sol rename to contracts/libs/Witnet.sol diff --git a/contracts/Migrations.sol b/contracts/utils/Migrations.sol similarity index 100% rename from contracts/Migrations.sol rename to contracts/utils/Migrations.sol diff --git a/contracts/utils/SingletonFactory.sol b/contracts/utils/SingletonFactory.sol new file mode 100644 index 000000000..58ca8daaf --- /dev/null +++ b/contracts/utils/SingletonFactory.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: CC0-1.0 + +pragma solidity ^0.6.2; + + +/** + * @title Based on Singleton Factory (EIP-2470), authored by Guilherme Schmidt (Status Research & Development GmbH) + * @notice Exposes CREATE2 (EIP-1014) to deploy bytecode on deterministic addresses based on initialization code and salt. + * @dev Exposes also helper method to pre-determine contract address gieve + * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH) + */ +contract SingletonFactory { + /** + * @notice Deploys `_initCode` using `_salt` for defining the deterministic address. + * @param _initCode Initialization code. + * @param _salt Arbitrary value to modify resulting address. + * @return createdContract Created contract address. + */ + function deploy(bytes memory _initCode, bytes32 _salt) + public + returns (address payable createdContract) + { + assembly { + createdContract := create2(0, add(_initCode, 0x20), mload(_initCode), _salt) + } + } + + /** + * @notice Deploys a deterministic address based on `_initCode` and `_salt`. + using `_salt` for defining the deterministic address. + * @param _initCode Initialization code. + * @param _initCall Calldata to be made to created contract. + * @param _salt Arbitrary value to modify resulting address. + * @return createdContract Created contract address. + */ + function deployAndInit(bytes memory _initCode, bytes memory _initCall, bytes32 _salt) + public + returns (address payable createdContract) + { + assembly { + createdContract := create2(0, add(_initCode, 0x20), mload(_initCode), _salt) + } + if (_initCall.length > 0) { + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory reason) = createdContract.call(_initCall); + require(success, string(reason)); + } + } + + + /** + * @notice Determine singleton contract address that might be created from this factory, given its `_initCode` and a `_salt`. + * @param _initCode Initialization code. + * @param _salt Arbitrary value to modify resulting address. + * @return expectedAddr Expected contract address. + */ + function determineAddr(bytes memory _initCode, bytes32 _salt) + public + view + returns (address) + { + return address( + uint160(uint(keccak256( + abi.encodePacked( + bytes1(0xff), + address(this), + _salt, + keccak256(_initCode) + ) + ))) + ); + } +} diff --git a/migrations/01_initial.js b/migrations/01_initial.js new file mode 100644 index 000000000..4cf572299 --- /dev/null +++ b/migrations/01_initial.js @@ -0,0 +1,44 @@ +const fs = require("fs") +const Migrations = artifacts.require("Migrations") + +module.exports = async function (deployer, network) { + if (!fs.existsSync("./migrations/addresses.json")) { + await fs.open("./migrations/addresses.json", "w", function (err, file) { + if (err) throw new Error("Fatal: cannot create ./migrations/addreses.json") + console.log("> Created ./migrations/addresses.json file.") + }) + } + + // Prepare "addresses.json" structure if necessary: + let addresses = await fs.readFileSync("./migrations/addresses.json") + if (addresses.length === 0) addresses = "{}" + addresses = JSON.parse(addresses) + + let changes = false + if (!("networks" in addresses)) { + addresses.networks = {} + changes = true + } + if (!(network in addresses.networks)) { + addresses.networks[network] = {} + changes = true + } + if (!("singletons" in addresses)) { + addresses.singletons = {} + changes = true + } + if (!("libraries" in addresses.singletons)) { + addresses.singletons.libraries = {} + changes = true + } + if (!("contracts" in addresses.singletons)) { + addresses.singletons.contracts = {} + changes = true + } + if (changes) { + await fs.writeFileSync("./migrations/addresses.json", JSON.stringify(addresses, null, 2)) + } + + // Deploy "Migrations" contract: + await deployer.deploy(Migrations) +} diff --git a/migrations/02_singleton_libs.js b/migrations/02_singleton_libs.js new file mode 100644 index 000000000..c46554b33 --- /dev/null +++ b/migrations/02_singleton_libs.js @@ -0,0 +1,115 @@ +const ethUtils = require("ethereumjs-util") +const fs = require("fs") +const utils = require("../utils") + +const SingletonFactory = artifacts.require("SingletonFactory") + +module.exports = async function (deployer, network, accounts) { + const addresses = require("./addresses.json") + const singletons = require("./singletons.json") + + const from = singletons.from || deployer.networks[network].from || accounts[0] + + // Generate SingletonFactory deployment transaction: + const res = utils.generateDeployTx( + SingletonFactory.toJSON(), + singletons.sender.r, + singletons.sender.s, + singletons.sender.gasprice, + singletons.sender.gas + ) + + const deployedCode = await web3.eth.getCode(res.contractAddr) + + if (deployedCode.length <= 3) { + // Deploy SingletonFactory instance, if not yet deployed on this `network`: + utils.traceHeader("Inception of 'SingletonFactory':") + + const balance = await web3.eth.getBalance(from) + const estimatedGas = res.gasLimit + const value = estimatedGas * res.gasPrice + let makerBalance = await web3.eth.getBalance(res.sender) + + if (makerBalance < value) { + // transfer ETH funds to sender address, if currently not enough: + await web3.eth.sendTransaction({ + from: from, + to: res.sender, + value: value - makerBalance, + }) + makerBalance = await web3.eth.getBalance(res.sender) + } + + const tx = await web3.eth.sendSignedTransaction(res.rawTx) + utils.traceTx(tx, web3.utils.fromWei((balance - await web3.eth.getBalance(from)).toString())) + } else { + utils.traceHeader("Singleton factory: 'SingletonFactory") + } + + // Set SingletonFactory address on current network: + SingletonFactory.address = res.contractAddr + const factory = await SingletonFactory.deployed() + + // Trace factory relevant data: + console.log(" ", "> sender's balance:\t", + `${web3.utils.fromWei((await web3.eth.getBalance(res.sender)).toString(), "ether")} ETH` + ) + console.log(" ", "> factory codehash:\t", web3.utils.soliditySha3(await web3.eth.getCode(res.contractAddr))) + console.log(" ", "> factory sender:\t", res.sender) + console.log(" ", "> factory address:\t", factory.address) + console.log(" ", "> factory nonce:\t", await web3.eth.getTransactionCount(factory.address)) + console.log("") + + // Process all singleton libraries referred in config file: + for (const lib in singletons.libs) { + const artifact = artifacts.require(lib) + const salt = singletons.libs[lib].salt + ? "0x" + ethUtils.setLengthLeft(ethUtils.toBuffer(singletons.libs[lib].salt), 32).toString("hex") + : "0x0" + + let bytecode = artifact.toJSON().bytecode + if (singletons.libs[lib].links) { + singletons.libs[lib].links.forEach( + // Join dependent library address(es) into the library bytecode to be deployed: + // Please note: dependent libraries should have been previously deployed, + // so order in which libraries are declared in the config file actually matters. + sublib => { + const sublibArtifact = artifacts.require(sublib) + const sublibAddr = sublibArtifact.address.slice(2).toLowerCase() + const sublibMark = `__${sublibArtifact.contractName}${"_".repeat(38 - sublibArtifact.contractName.length)}` + bytecode = bytecode.split(sublibMark).join(sublibAddr) + } + ) + } + artifact.bytecode = bytecode + + const libAddr = await factory.determineAddr.call(bytecode, salt) + + if ((await web3.eth.getCode(libAddr)).length <= 3) { + // Deploy library instance, if not yet deployed on this `network`: + utils.traceHeader(`Singleton inception of library '${lib}':`) + + const balance = await web3.eth.getBalance(from) + const gas = singletons.libs[lib].gas || 10 ** 6 + const tx = await factory.deploy(bytecode, salt, { from: from, gas: gas }) + utils.traceTx(tx.receipt, web3.utils.fromWei((balance - await web3.eth.getBalance(from)).toString())) + } else { + utils.traceHeader(`Singleton library: '${lib}'`) + } + + artifact.address = libAddr + + const libCodehash = web3.utils.soliditySha3(await web3.eth.getCode(artifact.address)) + if (!libCodehash) { + throw new Error(`Fatal: unable to deploy library '${lib}' as singleton. Try providing more gas.`) + } + + console.log(" ", "> library codehash:\t", libCodehash) + console.log(" ", "> library address:\t", artifact.address) + console.log() + + addresses.singletons.libraries[lib] = libAddr + } + + fs.writeFileSync("./migrations/addresses.json", JSON.stringify(addresses, null, 2)) +} diff --git a/migrations/03_new_wrb.js b/migrations/03_new_wrb.js new file mode 100644 index 000000000..7a22d054f --- /dev/null +++ b/migrations/03_new_wrb.js @@ -0,0 +1,17 @@ +const fs = require("fs") +const WitnetRequestBoard = artifacts.require("WitnetRequestBoard") + +module.exports = async function (deployer, network, accounts) { + network = network.split("-")[0] + + const addresses = require("./addresses.json") + if (network in addresses.networks && addresses.networks[network].WitnetRequestBoard) { + WitnetRequestBoard.address = addresses.networks[network].WitnetRequestBoard + } else { + console.log(`> Deploying new instance of 'WitnetRequestBoard' into '${network}' network...`) + await deployer.deploy(WitnetRequestBoard, [accounts[0]], { from: accounts[0] }) + addresses.networks[network].WitnetRequestBoard = WitnetRequestBoard.address + } + + fs.writeFileSync("./migrations/addresses.json", JSON.stringify(addresses, null, 2)) +} diff --git a/migrations/04_singleton_proxy.js b/migrations/04_singleton_proxy.js new file mode 100644 index 000000000..09069bc21 --- /dev/null +++ b/migrations/04_singleton_proxy.js @@ -0,0 +1,75 @@ +const ethUtils = require("ethereumjs-util") +const fs = require("fs") +const utils = require("../utils") + +const SingletonFactory = artifacts.require("SingletonFactory") +const WitnetRequestBoard = artifacts.require("WitnetRequestBoard") + +module.exports = async function (deployer, network, accounts) { + const addresses = require("./addresses.json") + + const factory = await SingletonFactory.deployed() + const wrb = await WitnetRequestBoard.deployed() + + const singletons = require("./singletons.json") + const artifact = artifacts.require("WitnetRequestBoardProxy") + const contract = artifact.contractName + + if (singletons.contracts[contract]) { + const from = singletons.from || deployer.networks[network].from || accounts[0] + + const salt = singletons.contracts[contract].salt + ? "0x" + ethUtils.setLengthLeft(ethUtils.toBuffer(singletons.contracts[contract].salt), 32).toString("hex") + : "0x0" + + let bytecode = artifact.toJSON().bytecode + if (singletons.contracts[contract].links) { + singletons.contracts[contract].links.forEach( + // Join every dependent library address into the contract bytecode to be deployed: + lib => { + const libArtifact = artifacts.require(lib) + const libAddr = libArtifact.address.slice(2).toLowerCase() + const libMark = `__${libArtifact.contractName}${"_".repeat(38 - libArtifact.contractName.length)}` + bytecode = bytecode.split(libMark).join(libAddr) + } + ) + } + artifact.bytecode = bytecode + + const contractAddr = await factory.determineAddr.call(bytecode, salt) + + if ((await web3.eth.getCode(contractAddr)).length <= 3) { + // Deploy contract instance, if not yet deployed on this `network`: + utils.traceHeader(`Singleton inception of contract '${contract}':`) + + const balance = await web3.eth.getBalance(from) + const gas = singletons.contracts[contract].gas || 10 ** 6 + + // Compose initialization call: 'upgradeWitnetRequestBoard(wrb.address)' + const initCall = "0x47b1e79b000000000000000000000000" + wrb.address.slice(2) + + const tx = await factory.deployAndInit(bytecode, initCall, salt, { from, gas }) + utils.traceEvents(tx.logs) + utils.traceTx(tx.receipt, web3.utils.fromWei((balance - await web3.eth.getBalance(from)).toString())) + } else { + utils.traceHeader(`Singleton contract: '${contract}'`) + } + + artifact.address = contractAddr + const contractCodehash = web3.utils.soliditySha3(await web3.eth.getCode(artifact.address)) + if (!contractCodehash) { + throw new Error(`Fatal: unable to deploy contract '${contract}' as singleton. Try providing more gas.`) + } + + console.log(" ", "> contract codehash: ", web3.utils.soliditySha3(await web3.eth.getCode(artifact.address))) + console.log(" ", "> contract address: ", artifact.address) + console.log(" ", "> current WRB address:", await (await artifact.deployed()).currentWitnetRequestBoard()) + if (singletons.contracts[contract].links && singletons.contracts[contract].links.length > 0) { + console.log(" ", "> linked libraries:\t", JSON.stringify(singletons.contracts[contract].links)) + } + addresses.singletons.contracts[contract] = contractAddr + } + + console.log() + fs.writeFileSync("./migrations/addresses.json", JSON.stringify(addresses, null, 2)) +} diff --git a/migrations/1_initial_migration.js b/migrations/1_initial_migration.js deleted file mode 100644 index 0dd845fe0..000000000 --- a/migrations/1_initial_migration.js +++ /dev/null @@ -1,6 +0,0 @@ -const Migrations = artifacts.require("Migrations") - -module.exports = function (deployer) { - console.log("Migrating your contracts...\n===========================") - deployer.deploy(Migrations) -} diff --git a/migrations/2_deploy_wrb.js b/migrations/2_deploy_wrb.js deleted file mode 100644 index 8e6d573e6..000000000 --- a/migrations/2_deploy_wrb.js +++ /dev/null @@ -1,16 +0,0 @@ -const WitnetRequestBoardProxy = artifacts.require("WitnetRequestBoardProxy") -const addresses = require("./addresses.json") - -module.exports = function (deployer, network, accounts) { - network = network.split("-")[0] - console.log(network) - if (network in addresses && addresses[network].WitnetRequestBoardProxy) { - WitnetRequestBoardProxy.address = addresses[network].WitnetRequestBoardProxy - } else { - const WitnetRequestBoard = artifacts.require("WitnetRequestBoard") - console.log(`> Migrating WitnetRequestBoard and WitnetRequestBoardProxy into ${network} network`) - deployer.deploy(WitnetRequestBoard, [accounts[0]]).then(function () { - return deployer.deploy(WitnetRequestBoardProxy, WitnetRequestBoard.address) - }) - } -} diff --git a/migrations/3_deploy_witnet.js b/migrations/3_deploy_witnet.js deleted file mode 100644 index 46ac0fd43..000000000 --- a/migrations/3_deploy_witnet.js +++ /dev/null @@ -1,10 +0,0 @@ -const CBOR = artifacts.require("CBOR") -const Witnet = artifacts.require("Witnet") - -module.exports = function (deployer, network) { - console.log(`> Migrating CBOR and Witnet into ${network} network`) - deployer.deploy(CBOR).then(function () { - deployer.link(CBOR, Witnet) - return deployer.deploy(Witnet) - }) -} diff --git a/migrations/99_upgrade_proxy.js b/migrations/99_upgrade_proxy.js new file mode 100644 index 000000000..6493791b6 --- /dev/null +++ b/migrations/99_upgrade_proxy.js @@ -0,0 +1,16 @@ +const WitnetRequestBoardProxy = artifacts.require("WitnetRequestBoardProxy") +const WitnetRequestBoard = artifacts.require("WitnetRequestBoard") + +module.exports = async function (deployer, network, accounts) { + const WRB = await WitnetRequestBoard.deployed() + const WRBProxy = await WitnetRequestBoardProxy.deployed() + + const currentWRB = await WRBProxy.currentWitnetRequestBoard.call() + if (currentWRB !== WRB.address) { + console.log(`> Upgrading 'WitnetRequestBoardProxy' singleton on '${network}' network...\n`) + console.log(" ", "WRB owner address:", await WRB.owner.call()) + console.log(" ", "Old WRB address :", currentWRB) + await WRBProxy.upgradeWitnetRequestBoard(WitnetRequestBoard.address, { from: accounts[0] }) + console.log(" ", "New WRB address :", await WRBProxy.currentWitnetRequestBoard.call()) + } +} diff --git a/migrations/addresses.json b/migrations/addresses.json index 2954f8078..93b9606a4 100644 --- a/migrations/addresses.json +++ b/migrations/addresses.json @@ -1,16 +1,13 @@ { - "goerli" : { - "CBOR": "0x4ADd24Cb572b8951808f81b8235ac302D9910c63", - "Migrations": "0xaC43baB88216527560E0A2AC852Ca32130312370", - "Witnet": "0x80Dc9287085ea7C103c3EAe117Ad9A1CcCd02C46", - "WitnetRequestBoard": "0x156a1E0703fA7e9765949d40c6349501a1e9FB02", - "WitnetRequestBoardProxy": "0xd58615B1e77E6eD3a583CB12A7D0016cad7A8438" + "networks": { }, - "rinkeby" : { - "CBOR": "0xFBB631ec0F7b423d23c649bd5733bc450Ab5b366", - "Migrations": "0xa5EBf3DFa0284402Fb640ff6ffaf5102C3AE6959", - "Witnet": "0x47689bc2d27770Ab3a358C1C2C9c998028ea0823", - "WitnetRequestBoard": "0x90d08bF4e89C9D1fFb69cb4F783c38F6493ae74a", - "WitnetRequestBoardProxy": "0xE1EC13d1521B98ee7b826751048806E85DC1F1bA" + "singletons": { + "libraries": { + "CBOR": "0xd75b64FEb11193F9b77BCC3Ec904c89Bd0b9E421", + "Witnet": "0xC72d494a355a58A4FFb31434073118c7CB0651CD" + }, + "contracts": { + "WitnetRequestBoardProxy": "0x1F1a458dAF4162CC07BDaC1D25F459feCAab6C20" + } } -} +} \ No newline at end of file diff --git a/migrations/singletons.json b/migrations/singletons.json new file mode 100644 index 000000000..bb208a226 --- /dev/null +++ b/migrations/singletons.json @@ -0,0 +1,25 @@ +{ + "sender": { + "r": "0x247000", + "s": "0x2470", + "gasprice": 1e11, + "gas": 400000 + }, + "libs": { + "CBOR": { + "gas": 1700000, + "salt": 0 + }, + "Witnet": { + "gas": 2400000, + "salt": 0, + "links": ["CBOR"] + } + }, + "contracts": { + "WitnetRequestBoardProxy": { + "gas": 3000000, + "salt": 0 + } + } +} \ No newline at end of file diff --git a/package.json b/package.json index e2219b32b..8316c7aea 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,8 @@ ], "license": "MIT", "dependencies": { - "@openzeppelin/contracts": "3.2.0" + "@openzeppelin/contracts": "3.2.0", + "ethereumjs-util": "^6.2.1" }, "devDependencies": { "@openzeppelin/test-helpers": "0.5.5", diff --git a/test/TestBuffer.sol b/test/TestBuffer.sol index 5f38b2633..6e2d327ff 100644 --- a/test/TestBuffer.sol +++ b/test/TestBuffer.sol @@ -4,7 +4,7 @@ pragma solidity 0.6.12; pragma experimental ABIEncoderV2; import "truffle/Assert.sol"; -import "../contracts/BufferLib.sol"; +import "../contracts/libs/BufferLib.sol"; contract TestBuffer { diff --git a/test/TestCBOR.sol b/test/TestCBOR.sol index 3847ea61b..6a441cb85 100644 --- a/test/TestCBOR.sol +++ b/test/TestCBOR.sol @@ -4,7 +4,7 @@ pragma solidity 0.6.12; pragma experimental ABIEncoderV2; import "truffle/Assert.sol"; -import "../contracts/CBOR.sol"; +import "../contracts/libs/CBOR.sol"; contract TestCBOR { diff --git a/test/TestWitnet.sol b/test/TestWitnet.sol index 5c1088808..de78de7eb 100644 --- a/test/TestWitnet.sol +++ b/test/TestWitnet.sol @@ -4,7 +4,7 @@ pragma solidity 0.6.12; pragma experimental ABIEncoderV2; import "truffle/Assert.sol"; -import "../contracts/Witnet.sol"; +import "../contracts/libs/Witnet.sol"; contract WitnetTest { diff --git a/test/helpers/UsingWitnetTestHelper.sol b/test/helpers/UsingWitnetTestHelper.sol index ef755e9cc..902170d7b 100644 --- a/test/helpers/UsingWitnetTestHelper.sol +++ b/test/helpers/UsingWitnetTestHelper.sol @@ -4,7 +4,7 @@ pragma solidity 0.6.12; pragma experimental ABIEncoderV2; import "../../contracts/Request.sol"; -import "../../contracts/Witnet.sol"; +import "../../contracts/libs/Witnet.sol"; import "../../contracts/UsingWitnet.sol"; diff --git a/test/helpers/WrbProxyTestHelper.sol b/test/helpers/WrbProxyTestHelper.sol index 955d59dff..53a23c6ed 100644 --- a/test/helpers/WrbProxyTestHelper.sol +++ b/test/helpers/WrbProxyTestHelper.sol @@ -13,7 +13,9 @@ import "../../contracts/WitnetRequestBoardProxy.sol"; */ contract WrbProxyTestHelper is WitnetRequestBoardProxy { - constructor (address _witnetRequestBoardAddress) public WitnetRequestBoardProxy(_witnetRequestBoardAddress) {} + constructor (address _witnetRequestBoardAddress) public WitnetRequestBoardProxy(/*_witnetRequestBoardAddress*/) { + upgradeWitnetRequestBoard(_witnetRequestBoardAddress); + } function checkLastId(uint256 _id) external view returns(bool) { return _id == currentLastId; diff --git a/test/using_witnet.js b/test/using_witnet.js index 2d1ae1500..88b2930d2 100644 --- a/test/using_witnet.js +++ b/test/using_witnet.js @@ -7,8 +7,9 @@ const Witnet = artifacts.require("Witnet") const truffleAssert = require("truffle-assertions") const sha = require("js-sha256") +const BN = web3.utils.BN -contract("UsingWitnet", accounts => { +contract("UsingWitnet", ([owner, requestor]) => { describe("UsingWitnet \"happy path\" test case. " + "This covers pretty much all the life cycle of a Witnet request.", () => { const requestHex = "0x01" @@ -19,15 +20,17 @@ contract("UsingWitnet", accounts => { const reward = web3.utils.toWei("1", "ether") let witnet, clientContract, wrb, wrbProxy, request, requestId, result - let lastAccount0Balance + let ownerBalance, requestorBalance before(async () => { witnet = await Witnet.deployed() - wrb = await WRB.new([accounts[0]]) - wrbProxy = await WRBProxy.new(wrb.address) + wrb = await WRB.new([owner]) + wrbProxy = await WRBProxy.new() + await wrbProxy.upgradeWitnetRequestBoard(wrb.address) await UsingWitnetTestHelper.link(Witnet, witnet.address) clientContract = await UsingWitnetTestHelper.new(wrbProxy.address) - lastAccount0Balance = await web3.eth.getBalance(accounts[0]) + ownerBalance = await web3.eth.getBalance(owner) + requestorBalance = await web3.eth.getBalance(requestor) }) it("should create a Witnet request", async () => { @@ -45,7 +48,7 @@ contract("UsingWitnet", accounts => { requestId = await returnData(clientContract._witnetPostRequest( request.address, { - from: accounts[1], + from: requestor, value: reward, } )) @@ -66,10 +69,10 @@ contract("UsingWitnet", accounts => { assert.equal(drReward, reward) }) - it("requester balance should decrease", async () => { - const afterBalance = await web3.eth.getBalance(accounts[0]) - assert(afterBalance < lastAccount0Balance) - lastAccount0Balance = afterBalance + it("requestor balance should decrease after post", async () => { + const afterBalance = await web3.eth.getBalance(requestor) + assert(new BN(afterBalance).lt(new BN(requestorBalance))) + requestorBalance = afterBalance }) it("client contract balance should remain stable", async () => { @@ -84,7 +87,7 @@ contract("UsingWitnet", accounts => { it("should upgrade the rewards of an existing Witnet request", async () => { await returnData(clientContract._witnetUpgradeRequest(requestId, { - from: accounts[1], + from: requestor, value: reward, })) }) @@ -96,10 +99,10 @@ contract("UsingWitnet", accounts => { assert.equal(drReward, reward * 2) }) - it("requester balance should decrease after rewards upgrade", async () => { - const afterBalance = await web3.eth.getBalance(accounts[1]) - assert(afterBalance < lastAccount0Balance - reward) - lastAccount0Balance = afterBalance + it("requestor balance should decrease after rewards upgrade", async () => { + const afterBalance = await web3.eth.getBalance(requestor) + assert(new BN(afterBalance).lt(new BN(requestorBalance).sub(new BN(reward)))) + requestorBalance = afterBalance }) it("client contract balance should remain stable after rewards upgrade", async () => { @@ -114,18 +117,24 @@ contract("UsingWitnet", accounts => { it("should post the result of the request into the WRB", async () => { await returnData(wrb.reportResult(requestId, drHash, resultHex, { - from: accounts[0], + from: owner, })) const requestInfo = await wrb.requests(requestId) assert.equal(requestInfo.result, resultHex) }) + it("owner balance should increase after report result", async () => { + const afterBalance = await web3.eth.getBalance(owner) + assert(new BN(afterBalance).gt(new BN(ownerBalance))) + ownerBalance = afterBalance + }) + it("should check if the request is resolved", async () => { assert.equal(await clientContract._witnetCheckRequestResolved(requestId), true) }) it("should pull the result from the WRB back into the client contract", async () => { - await clientContract._witnetReadResult(requestId, { from: accounts[0] }) + await clientContract._witnetReadResult(requestId) result = await clientContract.result() assert.equal(result.success, true) assert.equal(result.cborValue.buffer.data, resultHex) @@ -148,7 +157,7 @@ contract("UsingWitnet", accounts => { before(async () => { witnet = await Witnet.deployed() - wrb = await WRB.new([accounts[0]]) + wrb = await WRB.new([owner]) await UsingWitnetTestHelper.link(Witnet, witnet.address) clientContract = await UsingWitnetTestHelper.new(wrb.address) }) @@ -168,7 +177,7 @@ contract("UsingWitnet", accounts => { requestId = await returnData(clientContract._witnetPostRequest( request.address, { - from: accounts[1], + from: requestor, value: reward, } )) @@ -181,20 +190,20 @@ contract("UsingWitnet", accounts => { it("should report the result in the WRB", async () => { await returnData(wrb.reportResult(requestId, drHash, resultHex, { - from: accounts[0], + from: owner, })) const requestinfo = await wrb.requests(requestId) assert.equal(requestinfo.result, resultHex) }) it("should pull the result from the WRB back to the client contract", async () => { - await clientContract._witnetReadResult(requestId, { from: accounts[1] }) + await clientContract._witnetReadResult(requestId) result = await clientContract.result() assert.equal(result.cborValue.buffer.data, resultHex) }) it("should detect the result is false", async () => { - await clientContract._witnetReadResult(requestId, { from: accounts[1] }) + await clientContract._witnetReadResult(requestId) result = await clientContract.result() assert.equal(result.success, false) }) @@ -204,7 +213,7 @@ contract("UsingWitnet", accounts => { const estimatedReward = await clientContract._witnetEstimateGasCost.call(gasPrice) await truffleAssert.passes( clientContract._witnetPostRequest(request.address, { - from: accounts[1], + from: requestor, value: estimatedReward, gasPrice: gasPrice, }), diff --git a/utils/generateDeployTx.js b/utils/generateDeployTx.js new file mode 100644 index 000000000..929196fe3 --- /dev/null +++ b/utils/generateDeployTx.js @@ -0,0 +1,23 @@ +const EthTx = require("ethereumjs-tx").Transaction +const ethUtils = require("ethereumjs-util") +const rawTransaction = require("./rawTransaction") + +module.exports = function (json, r, s, gasprice, gaslimit) { + const rawTx = rawTransaction(json, r, s, gasprice, gaslimit) + const tx = new EthTx(rawTx) + + const res = { + bytecode: rawTx.data, + contractAddr: ethUtils.toChecksumAddress( + "0x" + ethUtils.generateAddress( + tx.getSenderAddress(), + ethUtils.toBuffer(0) + ).toString("hex") + ), + gasLimit: rawTx.gasLimit.toString(), + gasPrice: rawTx.gasPrice.toString(), + rawTx: "0x" + tx.serialize().toString("hex"), + sender: ethUtils.toChecksumAddress("0x" + tx.getSenderAddress().toString("hex")), + } + return res +} diff --git a/utils/index.js b/utils/index.js new file mode 100644 index 000000000..8be7b0013 --- /dev/null +++ b/utils/index.js @@ -0,0 +1,6 @@ +module.exports = { + generateDeployTx: require("./generateDeployTx"), + traceEvents: require("./traceEvents.js"), + traceHeader: require("./traceHeader.js"), + traceTx: require("./traceTx.js"), +} diff --git a/utils/rawTransaction.js b/utils/rawTransaction.js new file mode 100644 index 000000000..35b3acc2e --- /dev/null +++ b/utils/rawTransaction.js @@ -0,0 +1,11 @@ +module.exports = function (json, r, s, gasprice, gaslimit) { + return { + gasPrice: gasprice, + value: 0, + data: json.bytecode, + gasLimit: gaslimit, + v: 27, + r: r, + s: s, + } +} diff --git a/utils/shortenAddr.js b/utils/shortenAddr.js new file mode 100644 index 000000000..622a674b0 --- /dev/null +++ b/utils/shortenAddr.js @@ -0,0 +1,3 @@ +module.exports = function (addr) { + return addr.substring(0, 6) + ".." + addr.slice(-4) +} diff --git a/utils/traceEvents.js b/utils/traceEvents.js new file mode 100644 index 000000000..4e4748263 --- /dev/null +++ b/utils/traceEvents.js @@ -0,0 +1,22 @@ +const shortenAddr = require("./shortenAddr") + +module.exports = async function (logs, web3) { + for (let i = 0; i < logs.length; i++) { + const event = logs[i].event + const args = logs[i].args + let params = "" + + switch (event) { + case "PostedRequest": + case "PostedResult": + params = `from: ${shortenAddr(args[0])}, id: ${args[1].toString(16)}` + break + + default: + continue + } + let tabs = "\t" + if (event.length < 13) tabs = "\t\t" + console.log(" ", `>> ${event}${tabs}(${params})`) + } +} diff --git a/utils/traceHeader.js b/utils/traceHeader.js new file mode 100644 index 000000000..de350909d --- /dev/null +++ b/utils/traceHeader.js @@ -0,0 +1,5 @@ +module.exports = function (header) { + console.log("") + console.log(" ", header) + console.log(" ", `${"-".repeat(header.length)}`) +} diff --git a/utils/traceTx.js b/utils/traceTx.js new file mode 100644 index 000000000..b9053e216 --- /dev/null +++ b/utils/traceTx.js @@ -0,0 +1,9 @@ +module.exports = function (receipt, totalCost) { + console.log(" ", "> transaction hash:\t", receipt.transactionHash) + console.log(" ", "> block number:\t", receipt.blockNumber) + console.log(" ", "> gas used:\t\t", receipt.cumulativeGasUsed) + if (totalCost) { + console.log(" ", "> total cost:\t", totalCost, "ETH") + } + console.log() +}