13

Any tips on how to simulate real network conditions on the testrpc testing client using truffle? I have some functions that look at blocknumber and try to deal with passing time. Has anyone come up with a good way to test these kind of things?

I'm wondering if just calling some null transactions in a loop with increase the block number accordingly. Any info on the inner workings of these things would be appreciated.

Austin Fatheree
  • 409
  • 6
  • 11

4 Answers4

12

By default, every transaction called in testrpc is immediately mined and the block height will increase. The time it takes to mine each block can be set as a command line option when starting testrpc using -b or --blocktime

In addition, testrpc provides a custom rpc method you can use to manipulate the block height:

evm_mine : Force a block to be mined. Takes no parameters. Mines a block independent of whether or not mining is started or stopped.

Testrpc's custom methods can be called from web3 using web3.currentProvider.sendAsync() like so:

web3.currentProvider.sendAsync({
  jsonrpc: "2.0",
  method: "evm_mine",
  id: 12345
}, function(err, result) {
  // this is your callback
});
arseniy
  • 376
  • 2
  • 5
4

I found a way to write time precise test by using own wrapper:

const { time } = require('openzeppelin-test-helpers');    

async function timeIncreaseTo (seconds) {
    const delay = 1000 - new Date().getMilliseconds();
    await new Promise(resolve => setTimeout(resolve, delay));
    await time.increaseTo(seconds);
}

I noticed that Ganache-cli switches EVM seconds exactly when host switches seconds. So I added waiting for new second and then increase time on EVM. So, I have 1 exact second to perform all necessary transactions and calls until EVM time will be switched.

Usage:

const { time } = require('openzeppelin-test-helpers');    

// Skip 10 sec to have exact 1 second
await timeIncreaseTo((await time.latest()).addn(10));

await this.contract.deposit(1000000);
const timeDeposited = await time.latest();

// Skip 1 year
await timeIncreaseTo(timeDeposited.add(time.duration.years(1)).subn(1));

expect(await this.contract.balanceOf(wallet)).to.be.bignumber.equal('1060000'); // +6%
await this.contract.withdrawAll();
k06a
  • 3,016
  • 2
  • 21
  • 35
  • 2
    This was the only solution that worked consistently for me. Everything else had issues with sometimes being off by 1 second (depending how long my test took to execute) – JasoonS Sep 26 '19 at 03:03
3

I wrote an entire article about this: Writing Accurate Time-Dependent Truffle Tests.

TLDR;

While I strongly advise to check it out (there's some subtleties involved), here's the short story.

Either do npm install ganache-time-traveler or copy-paste the following in your project:

const advanceBlockAtTime = (time) => {
  return new Promise((resolve, reject) => {
    web3.currentProvider.send(
      {
        jsonrpc: "2.0",
        method: "evm_mine",
        params: [time],
        id: new Date().getTime(),
      },
      (err, _) => {
        if (err) {
          return reject(err);
        }
        const newBlockHash = web3.eth.getBlock("latest").hash;

        return resolve(newBlockHash);
      },
    );
  });
};
Paul Razvan Berg
  • 17,902
  • 6
  • 73
  • 143
1
  1. 1.evm_increaseTime: increases the time for the next block.
  2. 2.evm_mine: mines a new block.

You don't even need a Tardis or a DeLorean for this kind of time travel.

Let me explain how these functions work:

Every time a new block gets mined, the miner adds a timestamp to it. Let's say the transactions that created our zombies got mined in block 5.

Next, we call evm_increaseTime but, since the blockchain is immutable, there is no way of modifying an existing block. So, when the contract checks the time, it will not be increased.

If we run evm_mine, block number 6 gets mined (and timestamped) which means that, when we put the zombies to fight, the smart contract will "see" that a day has passed.

Putting it together, we can fix our test by traveling through time as follows:

await web3.currentProvider.sendAsync({
  jsonrpc: "2.0",
  method: "evm_increaseTime",
  params: [86400],  // there are 86400 seconds in a day
  id: new Date().getTime()
}, () => { });

web3.currentProvider.send({ jsonrpc: '2.0', method: 'evm_mine', params: [], id: new Date().getTime() });