testUtils.js
1 /*global assert, web3 */ 2 const bs58 = require('bs58') 3 4 // This has been tested with the real Ethereum network and Testrpc. 5 // Copied and edited from: https://gist.github.com/xavierlepretre/d5583222fde52ddfbc58b7cfa0d2d0a9 6 7 exports.assertReverts = (contractMethodCall, maxGasAvailable) => { 8 return new Promise((resolve, reject) => { 9 try { 10 resolve(contractMethodCall()) 11 } catch (error) { 12 reject(error) 13 } 14 }) 15 .then(tx => { 16 assert.equal( 17 tx.receipt.gasUsed, 18 maxGasAvailable, 19 'tx successful, the max gas available was not consumed', 20 ) 21 }) 22 .catch(error => { 23 if ( 24 String(error).indexOf('invalid opcode') < 0 && 25 String(error).indexOf('out of gas') < 0 26 ) { 27 // Checks if the error is from TestRpc. If it is then ignore it. 28 // Otherwise relay/throw the error produced by the above assertion. 29 // Note that no error is thrown when using a real Ethereum network AND the assertion above is true. 30 throw error 31 } 32 }) 33 } 34 35 exports.listenForEvent = event => 36 new Promise((resolve, reject) => { 37 event({}, (error, response) => { 38 if (!error) { 39 resolve(response.args) 40 } else { 41 reject(error) 42 } 43 event.stopWatching() 44 }) 45 }) 46 47 exports.eventValues = (receipt, eventName) => { 48 if (receipt.events[eventName]) return receipt.events[eventName].returnValues 49 } 50 51 exports.addressToBytes32 = address => { 52 const stringed = 53 '0000000000000000000000000000000000000000000000000000000000000000' + 54 address.slice(2) 55 return `0x${ stringed.substring(stringed.length - 64, stringed.length)}`; 56 } 57 58 // OpenZeppelin's expectThrow helper - 59 // Source: https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/test/helpers/expectThrow.js 60 exports.expectThrow = async promise => { 61 try { 62 await promise 63 } catch (error) { 64 // TODO: Check jump destination to destinguish between a throw 65 // and an actual invalid jump. 66 const invalidOpcode = error.message.search('invalid opcode') >= 0 67 // TODO: When we contract A calls contract B, and B throws, instead 68 // of an 'invalid jump', we get an 'out of gas' error. How do 69 // we distinguish this from an actual out of gas event? (The 70 // testrpc log actually show an 'invalid jump' event.) 71 const outOfGas = error.message.search('out of gas') >= 0 72 const revert = error.message.search('revert') >= 0 73 assert( 74 invalidOpcode || outOfGas || revert, 75 `Expected throw, got '${ error }' instead`, 76 ) 77 return 78 } 79 assert.fail('Expected throw not received') 80 } 81 82 exports.assertJump = error => { 83 assert( 84 error.message.search('Returned error: VM Exception while processing transaction: revert') > 85 -1, 86 'Revert should happen', 87 ) 88 } 89 90 function callbackToResolve(resolve, reject) { 91 return function(error, value) { 92 if (error) { 93 reject(error) 94 } else { 95 resolve(value) 96 } 97 } 98 } 99 100 exports.promisify = func => (...args) => { 101 return new Promise((resolve, reject) => { 102 const callback = (err, data) => (err ? reject(err) : resolve(data)) 103 func.apply(this, [...args, callback]) 104 }) 105 } 106 107 exports.zeroAddress = '0x0000000000000000000000000000000000000000' 108 exports.zeroBytes32 = 109 '0x0000000000000000000000000000000000000000000000000000000000000000' 110 exports.timeUnits = { 111 seconds: 1, 112 minutes: 60, 113 hours: 60 * 60, 114 days: 24 * 60 * 60, 115 weeks: 7 * 24 * 60 * 60, 116 years: 365 * 24 * 60 * 60, 117 } 118 119 exports.ensureException = function(error) { 120 assert(isException(error), error.toString()) 121 } 122 123 function isException(error) { 124 const strError = error.toString() 125 return ( 126 strError.includes('invalid opcode') || 127 strError.includes('invalid JUMP') || 128 strError.includes('revert') 129 ) 130 } 131 132 const evmMethod = (method, params = []) => { 133 return new Promise(function(resolve, reject) { 134 const sendMethod = web3.currentProvider.sendAsync 135 ? web3.currentProvider.sendAsync.bind(web3.currentProvider) 136 : web3.currentProvider.send.bind(web3.currentProvider) 137 sendMethod( 138 { 139 jsonrpc: '2.0', 140 method, 141 params, 142 id: new Date().getSeconds(), 143 }, 144 (error, res) => { 145 if (error) { 146 return reject(error) 147 } 148 resolve(res.result) 149 }, 150 ) 151 }) 152 } 153 154 exports.evmSnapshot = async () => { 155 const result = await evmMethod('evm_snapshot') 156 return web3.utils.hexToNumber(result) 157 } 158 159 exports.evmRevert = id => { 160 const params = [id] 161 return evmMethod('evm_revert', params) 162 } 163 164 exports.increaseTime = async amount => { 165 await evmMethod('evm_increaseTime', [Number(amount)]) 166 await evmMethod('evm_mine') 167 } 168 169 exports.getBytes32FromIpfsHash = ipfsListing => { 170 const decodedHash = bs58 171 .decode(ipfsListing) 172 .slice(2) 173 .toString('hex') 174 return `0x${decodedHash}` 175 } 176 177 exports.getIpfsHashFromBytes32 = bytes32Hex => { 178 const hashHex = `1220${bytes32Hex.slice(2)}` 179 const hashBytes = Buffer.from(hashHex, 'hex') 180 const hashStr = bs58.encode(hashBytes) 181 return hashStr 182 }