From b4b60b64aac51c7210705fb12461f30829d1bfe4 Mon Sep 17 00:00:00 2001 From: ricmoo Date: Wed, 1 Mar 2017 02:26:50 -0500 Subject: [PATCH] Added provider test cases. --- tests/index.js | 12 +- tests/make-tests/make-contract-interface.js | 5 +- tests/make-tests/make-providers.js | 138 ++++++ .../test-contracts/TestContract.sol | 22 + tests/package.json | 7 +- tests/run-contract-interface.js | 10 +- tests/run-hdnode.js | 4 +- tests/run-providers.js | 395 ++++++++++++++++++ 8 files changed, 570 insertions(+), 23 deletions(-) create mode 100644 tests/make-tests/make-providers.js create mode 100644 tests/run-providers.js diff --git a/tests/index.js b/tests/index.js index d57a1f9e4..5be54a9ef 100644 --- a/tests/index.js +++ b/tests/index.js @@ -2,20 +2,10 @@ var reporter = require('nodeunit').reporters.default; -/* -module.exports = {}; - -['contract-iterface', 'hdnode', 'utils', 'wallet'].forEach(function(runName) { - var runs = require('./run-' + runName + '.js'); - for (var testcaseName in runs) { - module.exports[testcaseName] = runs[testcaseName]; - } -}); -*/ - reporter.run([ 'run-contract-interface.js', 'run-hdnode.js', + 'run-providers.js', 'run-utils.js', 'run-wallet.js', ]); diff --git a/tests/make-tests/make-contract-interface.js b/tests/make-tests/make-contract-interface.js index c6aec120a..26750f242 100644 --- a/tests/make-tests/make-contract-interface.js +++ b/tests/make-tests/make-contract-interface.js @@ -11,6 +11,7 @@ var contracts = require('../../contracts/index.js'); var bigNumber = require('../../utils/bignumber.js'); var convert = require('../../utils/convert.js'); +var getAddress = require('../../utils/address.js').getAddress; var utils = require('./utils.js'); @@ -305,7 +306,7 @@ function makeTests() { check('sol-28', ['address', 'string', 'bytes6[4]', 'int'], [ - "0x97916ef549947a3e0d321485a31dd2715a97d455", + getAddress("0x97916ef549947a3e0d321485a31dd2715a97d455"), "foobar2", [ new Buffer("a165ab0173c6", 'hex'), @@ -409,7 +410,7 @@ function makeTests() { return { type: 'address', value: function(extraSeed) { - var value = utils.randomHexString(seed + '-' + extraSeed + '-type-2', 20); + var value = getAddress(utils.randomHexString(seed + '-' + extraSeed + '-type-2', 20)); return { value: value, normalized: value diff --git a/tests/make-tests/make-providers.js b/tests/make-tests/make-providers.js new file mode 100644 index 000000000..8641f2e96 --- /dev/null +++ b/tests/make-tests/make-providers.js @@ -0,0 +1,138 @@ +'use strict'; + +var fs = require('fs'); +var path = require('path'); + +var solc = require('solc'); + +var convert = require('../../utils/convert.js'); +var ethers = require('../../index.js'); +var utils = require('./utils.js'); + +var privateKey = null; +try { + privateKey = fs.readFileSync('.make-providers-account').toString(); + console.log('Found privateKey: ' + privateKey); +} catch (error) { + privateKey = convert.hexlify(ethers.utils.randomBytes(32)); + console.log('Created new private key: ' + privateKey); + fs.writeFileSync('.make-providers-account', privateKey); +} + +var sourcePath = path.join(__dirname, 'test-contracts/TestContract.sol'); +var source = fs.readFileSync(sourcePath).toString(); + +var provider = ethers.providers.getDefaultProvider(true); +var wallet = new ethers.Wallet(privateKey, provider); + +console.log('Address: ' + wallet.address); + +var TestContract = { + dateCreated: (new Date()).getTime(), + source: source, + owner: wallet.address, +}; + +var TestContractDeploy = { + dateCreated: (new Date()).getTime(), + source: source, +} + + +function serialize(object) { + if (object == null) { + return {type: "null"}; + } + + if (Array.isArray(object)) { + var result = []; + object.forEach(function(object) { + result.push(serialize(object)); + }); + return result; + + } else if (object.toHexString) { + return {type: 'bigNumber', value: object.toHexString()}; + + } + + switch (typeof(object)) { + case 'string': + case 'number': + case 'boolean': + return {type: typeof(object), value: object}; + default: + break; + } + + var result = {}; + for (var key in object) { + result[key] = serialize(object[key]); + } + return result; +} + + +wallet.getBalance().then(function(balance) { + if (balance.isZero()) { + console.log('Plese send some testnet ether to: ' + wallet.address); + throw new Error('insufficient funds'); + } + + var compiled = solc.compile(source, 0); + if (compiled.errors) { + console.log('Solidity Compile Error(s):'); + compiled.errors.forEach(function(line) { + console.log(' ' + line); + }); + throw new Error('invalid solidity contract source'); + } + + (function() { + var contract = compiled.contracts.TestContractDeploy; + TestContractDeploy.bytecode = '0x' + contract.bytecode; + TestContractDeploy.functions = contract.functionHashes; + TestContractDeploy.interface = contract.interface; + TestContractDeploy.runtimeBytecode = '0x' + contract.runtimeBytecode; + })(); + + (function() { + var contract = compiled.contracts.TestContract; + TestContract.bytecode = '0x' + contract.bytecode; + TestContract.functions = contract.functionHashes; + TestContract.interface = contract.interface; + TestContract.runtimeBytecode = '0x' + contract.runtimeBytecode; + })(); + TestContract.value = 123456789; + + var transaction = { + data: TestContract.bytecode, + gasLimit: 2000000, + value: TestContract.value, + } + console.log(transaction); + + return wallet.sendTransaction(transaction); + +}).then(function(hash) { + TestContract.transactionHash = hash; + return provider.waitForTransaction(hash); + +}).then(function(transaction) { + + TestContract.address = ethers.utils.getContractAddress(transaction); + TestContract.transaction = JSON.stringify(serialize(transaction)); + TestContract.blockNumber = transaction.blockNumber; + TestContract.blockHash = transaction.blockHash; + + return Promise.all([ + provider.getTransactionReceipt(transaction.hash), + provider.getBlock(transaction.blockHash) + ]); + +}).then(function(results) { + TestContract.transactionReceipt = JSON.stringify(serialize(results[0])); + TestContract.block = JSON.stringify(serialize(results[1])); + + utils.saveTestcase('test-contract', {deploy: TestContractDeploy, test: TestContract}); +}); diff --git a/tests/make-tests/test-contracts/TestContract.sol b/tests/make-tests/test-contracts/TestContract.sol index 7814284cb..09bc9603f 100644 --- a/tests/make-tests/test-contracts/TestContract.sol +++ b/tests/make-tests/test-contracts/TestContract.sol @@ -82,3 +82,25 @@ contract TestContract { callFallback(msg.sender, msg.value); } } + +contract TestContractDeploy { + address _owner; + + uint _uintValue; + string _stringValue; + + function TestContractDeploy(uint uintValue, string stringValue) { + _owner = msg.sender; + _uintValue = uintValue; + _stringValue = stringValue; + } + + function getValues() constant returns (uint, string) { + return (_uintValue, _stringValue); + } + + function cleanup() { + if (msg.sender != _owner) { return; } + suicide(_owner); + } +} diff --git a/tests/package.json b/tests/package.json index ff3100a06..1a5bb711e 100644 --- a/tests/package.json +++ b/tests/package.json @@ -6,19 +6,22 @@ "dependencies": { "nodeunit": "0.9.1" }, - "devDendencies": { + "devDependencies": { "bip39": "2.2.0", "bitcoinjs-lib": "2.3.0", "ethereumjs-abi": "0.6.2", "ethereumjs-tx": "1.1.1", "ethereumjs-util": "4.3.0", + "grunt": "^0.4.5", + "grunt-browserify": "^5.0.0", + "grunt-contrib-uglify": "^1.0.1", "promise-rationing": "0.0.1", "rlp": "2.0.0", "solc": "0.3.5", "web3": "0.18.2" }, "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "node index.js" }, "keywords": [ "Ethereum", diff --git a/tests/run-contract-interface.js b/tests/run-contract-interface.js index bfa79d235..6600958a4 100644 --- a/tests/run-contract-interface.js +++ b/tests/run-contract-interface.js @@ -1,8 +1,8 @@ 'use strict'; var utils = (function() { - var bigNumber = require('../utils/bignumber.js'); - var convert = require('../utils/convert.js'); + var bigNumber = require('../utils/bignumber'); + var convert = require('../utils/convert'); return { arrayify: convert.arrayify, @@ -10,6 +10,7 @@ var utils = (function() { }; })(); + function equals(a, b) { // Array (treat recursively) @@ -88,10 +89,7 @@ function testContractInterface(test) { try { var decoded = Interface.decodeParams(types, result); - var decodedArray = []; - for (var i = 0; decoded[i] != null; i++) { - decodedArray.push(decoded[i]); - } + var decodedArray = Array.prototype.slice.call(decoded);; test.ok(equals(values, decodedArray), 'failed to decode parameters - ' + testcase.name); diff --git a/tests/run-hdnode.js b/tests/run-hdnode.js index 6934768ab..6ffe892e8 100644 --- a/tests/run-hdnode.js +++ b/tests/run-hdnode.js @@ -4,7 +4,7 @@ var HDNode = require('../hdnode/index.js'); function testHDNode(test) { var Wallet = require('../wallet/index.js'); -var c = 0; + var c = 0; var testcases = require('./tests/hdnode.json'); testcases.forEach(function(testcase) { //if (c++ > 10) { return; } @@ -25,7 +25,7 @@ var c = 0; } function testMnemonic(test) { -var c = 0; + var c = 0; var testcases = require('./tests/hdnode.json'); testcases.forEach(function(testcase) { //if (c++ > 10) { return; } diff --git a/tests/run-providers.js b/tests/run-providers.js new file mode 100644 index 000000000..989ef61b9 --- /dev/null +++ b/tests/run-providers.js @@ -0,0 +1,395 @@ +'use strict'; + +var fs = require('fs'); + +var utils = require('../utils/index.js'); + +var Wallet = require('../wallet/index.js'); + +var providers = require('../providers/index.js'); + +var contracts = require('../contracts'); + +var TestContracts = require('./tests/test-contract.json'); + +var TestContract = TestContracts.test; +var TestContractDeploy = TestContracts.deploy; + + +var callFallback = (function() { + var contractInterface = new contracts.Interface(TestContract.interface); + + return contractInterface.events.callFallback(); +})(); + +var privateKey = null; +try { + privateKey = fs.readFileSync('.run-providers-account').toString(); + console.log('Found privateKey!'); +} catch (error) { + console.log('Creating new private key!'); + privateKey = utils.hexlify(utils.randomBytes(32)); + fs.writeFileSync('.run-providers-account', privateKey); +} + +var provider = providers.getDefaultProvider(true); +var wallet = new Wallet(privateKey, provider); + +console.log('Address: ' + wallet.address); + +function FailProvider(testnet) { + if (!(this instanceof FailProvider)) { throw new Error('missing new'); } + providers.Provider.call(this, testnet); +} +providers.Provider.inherits(FailProvider); + +utils.defineProperty(FailProvider.prototype, 'perform', function (method, params) { + return new Promise(function(resolve, reject) { + setTimeout(function() { + reject(new Error('out of order')); + }, 1000); + }); +}); + + +function equal(serialized, object) { + if (Array.isArray(serialized)) { + if (!Array.isArray(object) || serialized.length != object.length) { return false; } + for (var i = 0; i < serialized.length; i++) { + if (!equal(serialized[i], object[i])) { return false; } + } + return true; + } else if (serialized.type) { + var result = null; + switch (serialized.type) { + case 'null': + result = (null === object); + break; + case 'string': + case 'number': + case 'boolean': + result = (serialized.value === object); + break; + case 'bigNumber': + result = utils.bigNumberify(serialized.value).eq(object); + break; + default: + throw new Error('unknown type - ' + serialized.type); + } + if (!result) { + console.log('Not Equal: ', serialized, object); + } + return result; + } + + for (var key in serialized) { + if (!equal(serialized[key], object[key])) { return false; } + } + + return true; +} + + +function testReadOnlyProvider(test, provider) { + return Promise.all([ + provider.getBalance(TestContract.address), + provider.getCode(TestContract.address), + provider.getStorageAt(TestContract.address, 0), + provider.getBlock(TestContract.blockNumber), + provider.getBlock((provider instanceof providers.EtherscanProvider) ? TestContract.blockNumber: TestContract.blockHash), + provider.getTransaction(TestContract.transactionHash), + provider.getTransactionReceipt(TestContract.transactionHash), + provider.call({to: TestContract.address, data: '0x' + TestContract.functions['getOwner()']}), + provider.call({to: TestContract.address, data: '0x' + TestContract.functions['getStringValue()']}), + + provider.getBlockNumber(), + provider.getGasPrice(), + //provider.estimeGas() + ]).then(function(result) { + + // getBalance + test.equal(result[0].toString(), '123456789', 'getBalance(contractAddress)'); + + // getCode + test.equal(result[1], TestContract.runtimeBytecode, 'getCode(contractAddress)'); + + // getStorageAt + test.ok(utils.bigNumberify(result[2]).eq(42), 'getStorageAt(contractAddress, 0)'); + + // getBlock + var block = JSON.parse(TestContract.block); + test.ok(equal(block, result[3]), 'getBlock(blockNumber)'); + test.ok(equal(block, result[4]), 'getBlock(blockHash)'); + + // getTransaction + var transaction = JSON.parse(TestContract.transaction); + test.ok(equal(transaction, result[5]), 'getTransaction(transactionHash)'); + + // getTransactionReceipt + var transactionReceipt = JSON.parse(TestContract.transactionReceipt); + test.ok(equal(transactionReceipt, result[6]), 'getTransaction(transactionHash)'); + + // call + test.equal(TestContract.owner, utils.getAddress('0x' + result[7].substring(26)), 'call(getOwner())'); + var thisIsNotAString = '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001554686973206973206e6f74206120737472696e672e0000000000000000000000'; + test.equal(thisIsNotAString, result[8], 'call(getStringValue())'); + + return {blockNumber: result[9], gasPrice: result[9]} + }); +} + +function testWriteProvider(test, wallet) { + + var testTransaction = { + to: TestContract.address, + gasLimit: 0x793e + 42, + value: 100, + }; + + return Promise.all([ + wallet.estimateGas(testTransaction), + wallet.sendTransaction(testTransaction), + + ]).then(function(results) { + test.ok(results[0].eq(0x793e), 'estimateGas()'); + return wallet.provider.waitForTransaction(results[1]); + + }).then(function(transaction) { + test.equal(transaction.to, testTransaction.to, 'check toAddress'); + test.equal(transaction.from, wallet.address, 'check fromAddress'); + test.ok(transaction.gasLimit.eq(testTransaction.gasLimit), 'check gasLimit'); + test.ok(transaction.value.eq(testTransaction.value), 'check value'); + }); +} + + +function getProviders() { + return [ + new providers.InfuraProvider(true), + //new providers.EtherscanProvider(true), + new providers.FallbackProvider([ + new FailProvider(true), + new providers.InfuraProvider(true), + ]), + ]; +} + +function testEventsProvider(test, provider) { + var firstBlockNumber = null; + var lastBlockNumber = null; + return Promise.all([ + new Promise(function(resolve, reject) { + function callback(blockNumber) { + if (lastBlockNumber === null) { + firstBlockNumber = blockNumber; + lastBlockNumber = blockNumber - 1; + } + + test.equal(lastBlockNumber + 1, blockNumber, 'increasing block number'); + + lastBlockNumber = blockNumber; + if (blockNumber > firstBlockNumber + 4) { + provider.removeListener('block', callback); + resolve(blockNumber); + } + } + provider.on('block', callback); + }), + new Promise(function(resolve, reject) { + function callback(log) { + var result = callFallback.parse(log.data); + if (result.sender !== wallet.address || !result.amount.eq(123)) { + //console.log('someone else is running the test cases'); + return; + } + + test.ok(true, 'callFallback triggered'); + + provider.removeListener(callFallback.topics, callback); + + resolve(result); + } + provider.on(callFallback.topics, callback); + }), + ]); +} + +function testReadOnly(test) { + var promises = []; + getProviders().forEach(function(provider) { + promises.push(testReadOnlyProvider(test, provider)); + }); + + Promise.all(promises).then(function(results) { + results.forEach(function(result, i) { + if (i === 0) { return; } + test.equal(results[0].blockNumber, result.blockNumber, 'blockNumber'); + test.equal(results[0].gasPrice, result.gasPrice, 'blockNumber'); + }); + + test.done(); + }, function(error) { + console.log(error); + test.ok(false, 'error occurred'); + }); +} + +function testWrite(test) { + + var promise = wallet.getBalance().then(function(balance) { + if (balance.isZero()) { + console.log('Plese send some testnet ether to: ' + wallet.address); + throw new Error('insufficient balance'); + } + }); + + getProviders().forEach(function(provider) { + promise = promise.then(function() { + var wallet = new Wallet(privateKey, provider); + return testWriteProvider(test, wallet); + }); + }); + + promise.then(function(result) { + test.done(); + + }, function(error) { + console.log(error); + test.ok(false, 'error occurred'); + test.done(); + }); +} + +function testEvents(test) { + var promises = []; + getProviders().forEach(function(provider) { + promises.push(testEventsProvider(test, provider)); + }); + + Promise.all(promises).then(function(result) { + test.done() + }, function (error) { + console.log(error); + test.ok(false, 'error occurred'); + test.done() + }); + + // Send 123 wei to the contrat to trigger its callFallback event + wallet.send(TestContract.address, 123).then(function(hash) { + console.log('Trigger Transaction: ' + hash); + }); +} + +function testContracts(test) { + var contract = new contracts.Contract(TestContract.address, TestContract.interface, wallet); + + var newValue = 'Chicken-' + parseInt((new Date()).getTime()); + + Promise.all([ + contract.getUintValue(), + contract.getArrayValue(0), + contract.getArrayValue(1), + contract.getArrayValue(2), + contract.getBytes32Value(), + contract.getStringValue(), + contract.getOwner(), + contract.getMappingValue('A'), + contract.getMappingValue('B'), + contract.getMappingValue('C'), + contract.getMappingValue('Nothing'), + contract.getValue(), + (new Promise(function(resolve, reject) { + contract.onvaluechanged = function(author, oldValue, newValue) { + + test.ok(true, 'contract event oncallfa'); + + contract.onvaluechanged = null; + + resolve({author: author, oldValue: oldValue, newValue: newValue}); + }; + })), + contract.setValue(newValue), + ]).then(function(results) { + test.ok(results[0][0].eq(42), 'getUintValue()'); + test.equal(results[1][0], 'One', 'getArrayValue(1)'); + test.equal(results[2][0], 'Two', 'getArrayValue(2)'); + test.equal(results[3][0], 'Three', 'getArrayValue(3)'); + test.equal( + utils.hexlify(results[4][0]), + utils.keccak256(utils.toUtf8Bytes('TheEmptyString')), + 'getBytes32Value()' + ); + test.equal(results[5][0], 'This is not a string.', 'getStringValue()'); + test.equal(results[6][0], TestContract.owner, 'getOwner()'); + test.equal(results[7][0], 'Apple', 'getMapping(A)'); + test.equal(results[8][0], 'Banana', 'getMapping(B)'); + test.equal(results[9][0], 'Cherry', 'getMapping(C)'); + test.equal(results[10][0], '', 'getMapping(Nothing)'); + + var getValue = results[11][0]; + var onvaluechanged = results[12]; + + test.equal(onvaluechanged.oldValue, getValue, 'getValue()'); + test.equal(onvaluechanged.author, wallet.address, 'onvaluechanged.author'); + test.equal(onvaluechanged.newValue, newValue, 'onvaluechanged.newValue'); + test.done(); + + }, function(error) { + console.log(error); + test.ok(false, 'an error occurred'); + }); +} + +function testDeploy(test) { + var valueUint = parseInt((new Date()).getTime()); + var valueString = 'HelloWorld-' + valueUint; + + var contractInterface = new contracts.Interface(TestContractDeploy.interface); + var deployInfo = contractInterface.deployFunction( + TestContractDeploy.bytecode, + valueUint, + valueString + ); + + var transaction = { + data: deployInfo.bytecode + }; + + var contract = null; + + wallet.sendTransaction(transaction).then(function(hash) { + return provider.waitForTransaction(hash); + + }).then(function(transaction) { + contract = new contracts.Contract(transaction.creates, TestContractDeploy.interface, wallet); + return contract.getValues(); + + }).then(function(result) { + test.ok(result[0].eq(valueUint), 'deployed contract - uint equal'); + test.equal(result[1], valueString, 'deployed contract - string equal'); + return provider.getCode(contract.address); + + }).then(function(code) { + test.equal(code, TestContractDeploy.runtimeBytecode, 'getCode() == runtimeBytecode (after deploy)'); + return contract.cleanup(); + + }).then(function(hash) { + return provider.waitForTransaction(hash); + + }).then(function(transaction) { + return provider.getCode(contract.address); + + }).then(function(code) { + test.equal(code, '0x', 'getCode() == null (after suicide)'); + test.done(); + }); +} + +module.exports = { + 'read-only': testReadOnly, + 'write': testWrite, + 'events': testEvents, + 'contracts': testContracts, + 'deploy': testDeploy, +}; +