marcoonroad/instrumental

Global Clock (Testing / Mocking / Development Contract)

Opened this issue · 0 comments

Related to issue #28.

Our current time-dependent logic relies on non-standard Ganache instructions such as evm_increaseTime and evm_mine (see the piece of code below). It doesn't scale to multiple contracts, as is the case for my Hold and Loyalty tests breaking due the sole addition of an Auction contract. Such black magic / workaround doesn't allow to go backwards in time, neither to undo the time travels forward in time.

const jsonrpc = '2.0'
const id = 0
const send = (method, params = []) =>
web3.currentProvider.send({ id, jsonrpc, method, params })
const timeTravel = async seconds => {
await send('evm_increaseTime', [seconds])
await send('evm_mine')
}

A good approach would be to deploy a Global Clock Contract. This contract would be a Singleton (in Ganache, it's possible to interact with the migrated / deployed contract on tests). A flag to describe either the development or production mode would be needed as well. The development mode of this contract allows to one owner to either forward or backward the clock for a given sender / caller (in the case, the contract relying on the current timestamp). So, the implementation carries 2 mappings from addresses to uint256 values. The first mapping is the amount to forward in time over block.timestamp, while the latter is the amount to backward (i.e, subtract) from block.timestamp.

Clients / users can rely and trust all of the contracts of this library whenever the Global Clock is deployed with the production mode. Otherwise, they must not interact with the contracts deployed, 'cause a malicious party could break the rules regarding the contracts (only if he has the authority to change the Global Clock).

To help us to hold such time invariant on the JavaScript / integration test side as well, the good library timekeeper could be used too. The possible sketch for the test support API could be:

await timeTravel(seconds, targetContract.address, async () => {
  // internal logic dealing with the contract <targetContract>
})

Where before the asynchronous block call, we forwards the time for the targetContract by seconds, and after the block call, we backwards the time by seconds for this contract, no matter if the asynchronous block call resolved with a value or rejected with a reason (that backward action could be regarded as a cleanup).