testUtils.js
1 2 // This has been tested with the real Ethereum network and Testrpc. 3 // Copied and edited from: https://gist.github.com/xavierlepretre/d5583222fde52ddfbc58b7cfa0d2d0a9 4 exports.assertReverts = (contractMethodCall, maxGasAvailable) => { 5 return new Promise((resolve, reject) => { 6 try { 7 resolve(contractMethodCall()) 8 } catch (error) { 9 reject(error) 10 } 11 }) 12 .then(tx => { 13 assert.equal(tx.receipt.gasUsed, maxGasAvailable, "tx successful, the max gas available was not consumed") 14 }) 15 .catch(error => { 16 if ((error + "").indexOf("invalid opcode") < 0 && (error + "").indexOf("out of gas") < 0) { 17 // Checks if the error is from TestRpc. If it is then ignore it. 18 // Otherwise relay/throw the error produced by the above assertion. 19 // Note that no error is thrown when using a real Ethereum network AND the assertion above is true. 20 throw error 21 } 22 }) 23 } 24 25 exports.listenForEvent = event => new Promise((resolve, reject) => { 26 event({}, (error, response) => { 27 if (!error) { 28 resolve(response.args) 29 } else { 30 reject(error) 31 } 32 event.stopWatching() 33 }) 34 }); 35 36 exports.eventValues = (receipt, eventName) => { 37 if(receipt.events[eventName]) 38 return receipt.events[eventName].returnValues; 39 } 40 41 exports.addressToBytes32 = (address) => { 42 const stringed = "0000000000000000000000000000000000000000000000000000000000000000" + address.slice(2); 43 return "0x" + stringed.substring(stringed.length - 64, stringed.length); 44 } 45 46 47 // OpenZeppelin's expectThrow helper - 48 // Source: https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/test/helpers/expectThrow.js 49 exports.expectThrow = async promise => { 50 try { 51 await promise; 52 } catch (error) { 53 // TODO: Check jump destination to destinguish between a throw 54 // and an actual invalid jump. 55 const invalidOpcode = error.message.search('invalid opcode') >= 0; 56 // TODO: When we contract A calls contract B, and B throws, instead 57 // of an 'invalid jump', we get an 'out of gas' error. How do 58 // we distinguish this from an actual out of gas event? (The 59 // testrpc log actually show an 'invalid jump' event.) 60 const outOfGas = error.message.search('out of gas') >= 0; 61 const revert = error.message.search('revert') >= 0; 62 assert( 63 invalidOpcode || outOfGas || revert, 64 'Expected throw, got \'' + error + '\' instead', 65 ); 66 return; 67 } 68 assert.fail('Expected throw not received'); 69 }; 70 71 72 73 exports.assertJump = (error) => { 74 assert(error.message.search('revert') > -1, 'Revert should happen'); 75 } 76 77 78 var callbackToResolve = function (resolve, reject) { 79 return function (error, value) { 80 if (error) { 81 reject(error); 82 } else { 83 resolve(value); 84 } 85 }; 86 }; 87 88 exports.promisify = (func) => 89 (...args) => { 90 return new Promise((resolve, reject) => { 91 const callback = (err, data) => err ? reject(err) : resolve(data); 92 func.apply(this, [...args, callback]); 93 }); 94 } 95 96 97 // This has been tested with the real Ethereum network and Testrpc. 98 // Copied and edited from: https://gist.github.com/xavierlepretre/d5583222fde52ddfbc58b7cfa0d2d0a9 99 exports.assertReverts = (contractMethodCall, maxGasAvailable) => { 100 return new Promise((resolve, reject) => { 101 try { 102 resolve(contractMethodCall()) 103 } catch (error) { 104 reject(error) 105 } 106 }) 107 .then(tx => { 108 assert.equal(tx.receipt.gasUsed, maxGasAvailable, "tx successful, the max gas available was not consumed") 109 }) 110 .catch(error => { 111 if ((error + "").indexOf("invalid opcode") < 0 && (error + "").indexOf("out of gas") < 0) { 112 // Checks if the error is from TestRpc. If it is then ignore it. 113 // Otherwise relay/throw the error produced by the above assertion. 114 // Note that no error is thrown when using a real Ethereum network AND the assertion above is true. 115 throw error 116 } 117 }) 118 } 119 120 exports.listenForEvent = event => new Promise((resolve, reject) => { 121 event({}, (error, response) => { 122 if (!error) { 123 resolve(response.args) 124 } else { 125 reject(error) 126 } 127 event.stopWatching() 128 }) 129 }); 130 131 exports.eventValues = (receipt, eventName) => { 132 if(receipt.events[eventName]) 133 return receipt.events[eventName].returnValues; 134 } 135 136 exports.addressToBytes32 = (address) => { 137 const stringed = "0000000000000000000000000000000000000000000000000000000000000000" + address.slice(2); 138 return "0x" + stringed.substring(stringed.length - 64, stringed.length); 139 } 140 141 142 // OpenZeppelin's expectThrow helper - 143 // Source: https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/test/helpers/expectThrow.js 144 exports.expectThrow = async promise => { 145 try { 146 await promise; 147 } catch (error) { 148 // TODO: Check jump destination to destinguish between a throw 149 // and an actual invalid jump. 150 const invalidOpcode = error.message.search('invalid opcode') >= 0; 151 // TODO: When we contract A calls contract B, and B throws, instead 152 // of an 'invalid jump', we get an 'out of gas' error. How do 153 // we distinguish this from an actual out of gas event? (The 154 // testrpc log actually show an 'invalid jump' event.) 155 const outOfGas = error.message.search('out of gas') >= 0; 156 const revert = error.message.search('revert') >= 0; 157 assert( 158 invalidOpcode || outOfGas || revert, 159 'Expected throw, got \'' + error + '\' instead', 160 ); 161 return; 162 } 163 assert.fail('Expected throw not received'); 164 }; 165 166 exports.assertJump = (error) => { 167 assert(error.message.search('revert') > -1, 'Revert should happen'); 168 } 169 170 var callbackToResolve = function (resolve, reject) { 171 return function (error, value) { 172 if (error) { 173 reject(error); 174 } else { 175 resolve(value); 176 } 177 }; 178 }; 179 180 exports.promisify = (func) => 181 (...args) => { 182 return new Promise((resolve, reject) => { 183 const callback = (err, data) => err ? reject(err) : resolve(data); 184 func.apply(this, [...args, callback]); 185 }); 186 } 187 188 exports.zeroAddress = '0x0000000000000000000000000000000000000000'; 189 exports.zeroBytes32 = "0x0000000000000000000000000000000000000000000000000000000000000000"; 190 exports.timeUnits = { 191 seconds: 1, 192 minutes: 60, 193 hours: 60 * 60, 194 days: 24 * 60 * 60, 195 weeks: 7 * 24 * 60 * 60, 196 years: 365 * 24 * 60 * 60 197 } 198 199 exports.ensureException = function(error) { 200 assert(isException(error), error.toString()); 201 }; 202 203 function isException(error) { 204 let strError = error.toString(); 205 return strError.includes('invalid opcode') || strError.includes('invalid JUMP') || strError.includes('revert'); 206 } 207 208 exports.increaseTime = async (amount) => { 209 return new Promise(function(resolve, reject) { 210 web3.currentProvider.sendAsync( 211 { 212 jsonrpc: '2.0', 213 method: 'evm_increaseTime', 214 params: [+amount], 215 id: new Date().getSeconds() 216 }, 217 async (error) => { 218 if (error) { 219 console.log(error); 220 return reject(err); 221 } 222 await web3.currentProvider.sendAsync( 223 { 224 jsonrpc: '2.0', 225 method: 'evm_mine', 226 params: [], 227 id: new Date().getSeconds() 228 }, (error) => { 229 if (error) { 230 console.log(error); 231 return reject(err); 232 } 233 resolve(); 234 } 235 ) 236 } 237 ) 238 }); 239 }