diff --git a/.travis.yml b/.travis.yml index dd02a04d8c..12d3ddfd7c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,8 @@ language: node_js node_js: - "node" - - "lts/carbon" - "lts/*" + - "lts/carbon" addons: apt: diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 5fa72ef14b..7453a69560 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -10317,6 +10317,21 @@ "strip-eof": "^1.0.0" } }, + "execa": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", + "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, "find-up": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", diff --git a/test/contracts/ArrayOfStructs.sol b/test/contracts/ArrayOfStructs.sol new file mode 100644 index 0000000000..d4926a77ed --- /dev/null +++ b/test/contracts/ArrayOfStructs.sol @@ -0,0 +1,25 @@ +pragma solidity ^0.4.2; + +contract ArrayOfStructs { + struct PayRecord { + address sender; + uint256 sum; + uint256 blockNumber; + uint256 status; + } + + event PaymentPlaced(address senderAddress, uint256 blockNumber, uint256 payIndex, string guid); + + PayRecord[] public payments; + + function payForSomething(string guid) public payable { + uint256 newLength = payments.push(PayRecord(msg.sender, msg.value, block.number, 0)); + PaymentPlaced(msg.sender, block.number, newLength-1, guid); + } + + function changeSomething(uint256 paymentIndex) public view { + if (payments[paymentIndex].status == 0) { + payments[paymentIndex].status == 1; + } + } +} diff --git a/test/helpers/compile_deploy.js b/test/helpers/compile_deploy.js new file mode 100644 index 0000000000..b961e9e146 --- /dev/null +++ b/test/helpers/compile_deploy.js @@ -0,0 +1,50 @@ +const { readFileSync } = require("fs"); +const { compile } = require("solc"); + +/** + * Compile and deploy the selected contract(s) + * @param {String} contractPath Path to contracts directory + * @param {String} mainContractName Name of the main contract (without .sol extension) + * @param {Array|String} contractFileNames List of imported contracts + * @param {Object} web3 Web3 interface + * @returns {Object} context + */ +async function compileAndDeploy(contractPath, mainContractName, contractFileNames = [], web3) { + // Organize contract(s) for compilation + const selectedContracts = contractFileNames.length === 0 ? [mainContractName] : contractFileNames; + const contractSources = selectedContracts.map((contractName) => { + return { [`${contractName}.sol`]: readFileSync(`${contractPath}${contractName}.sol`, "utf8") }; + }); + + const sources = Object.assign({}, ...contractSources); + + const { contracts } = compile({ sources }, 1); + const compiledMainContract = contracts[`${mainContractName}.sol:${mainContractName}`]; + const bytecode = `0x${compiledMainContract.bytecode}`; + const abi = JSON.parse(compiledMainContract.interface); + const contract = new web3.eth.Contract(abi); + + const accounts = await web3.eth.getAccounts(); + + // Retrieve block gas limit + const { gasLimit } = await web3.eth.getBlock("latest"); + const instance = await contract.deploy({ data: bytecode }).send({ from: accounts[0], gas: gasLimit }); + + // TODO: ugly workaround - not sure why this is necessary. + if (!instance._requestManager.provider) { + instance._requestManager.setProvider(web3.eth._provider); + } + + return { + abi, + accounts, + bytecode, + contract, + instance, + sources + }; +} + +exports = module.exports = { + compileAndDeploy +}; diff --git a/test/helpers/pretest_setup.js b/test/helpers/pretest_setup.js new file mode 100644 index 0000000000..4ec547959e --- /dev/null +++ b/test/helpers/pretest_setup.js @@ -0,0 +1,39 @@ +const Web3 = require("web3"); +const Ganache = require("../../index"); +const { join } = require("path"); +const { compileAndDeploy } = require("./compile_deploy"); + +const preloadContracts = (mainContractName = "", subContractNames = [], contractPath = "../contracts/", mnemonics) => { + const context = {}; + + before("Setting up web3 and contract", async function() { + this.timeout(10000); + + const options = typeof mnemonics === "string" ? {} : { mnemonics }; + const provider = Ganache.provider(options); + const web3 = new Web3(provider); + + const { abi, accounts, bytecode, contract, instance, sources } = await compileAndDeploy( + join(__dirname, contractPath), + mainContractName, + subContractNames, + web3 + ); + + context.abi = abi; + context.accounts = accounts; + context.bytecode = bytecode; + context.contract = contract; + context.instance = instance; + context.provider = provider; + context.options = options; + context.sources = sources; + context.web3 = web3; + }); + + return context; +}; + +module.exports = { + preloadContracts +}; diff --git a/test/solidity_array_structs.js b/test/solidity_array_structs.js new file mode 100644 index 0000000000..ffbb210d4e --- /dev/null +++ b/test/solidity_array_structs.js @@ -0,0 +1,66 @@ +const { preloadContracts } = require("./helpers/pretest_setup"); +const assert = require("assert"); + +describe("Solidity Data Types", function() { + describe("Array of Structures", function() { + // Main contract + const mainContract = "ArrayOfStructs"; // Name of the parent contract + + // List of all contracts to compile and deploy + const subContractNames = ["ArrayOfStructs"]; + + const services = preloadContracts(mainContract, subContractNames); + + it("can add structs to an array", async function() { + /** + * Enable access to: + * accounts - randomly generated test accounts + * instance - contract instance + * provider - Ganache, Geth or Parity + * web3 - web3 interface + */ + + const { accounts, instance } = services; + + this.timeout(6000); + const myGuid = "Payment1"; + const paymentIndex = 0; + const value = 10; + const gas = 5000000; + const iterations = 100; + + // Add and validate a struct to the array + const response = await instance.methods.payForSomething(myGuid).send({ + from: accounts[0], + value, + gas + }); + + const { blockNumber, guid, payIndex, senderAddress } = response.events.PaymentPlaced.returnValues; + + assert.strictEqual(guid, myGuid); + assert.strictEqual(senderAddress, accounts[0]); + assert.strictEqual(parseInt(blockNumber), 2); + assert.strictEqual(parseInt(payIndex), 0); + + // Update the status of a struct in the array + await instance.methods.changeSomething(paymentIndex).call(); + + // Add and validate 100 more struct to the array + for (let i = 0; i < iterations; i++) { + const response = await instance.methods.payForSomething(myGuid).send({ + from: accounts[0], + value, + gas + }); + + const { blockNumber, guid, payIndex, senderAddress } = response.events.PaymentPlaced.returnValues; + + assert.strictEqual(guid, myGuid); + assert.strictEqual(senderAddress, accounts[0]); + assert.strictEqual(parseInt(blockNumber), i + 3); + assert.strictEqual(parseInt(payIndex), i + 1); + } + }); + }); +});