/ utils / testUtils.js
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  }