Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable buidler evm to support exact timestamp testing #508

Merged
merged 2 commits into from
Apr 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/buidler-evm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,9 +227,10 @@ To customise it, take a look at [the configuration section](/config/#buidler-evm
#### Special testing/debugging methods

- `evm_increaseTime` – same as Ganache.
- `evm_mine` – same as Ganache, except it doesn’t accept a timestamp.
- `evm_mine` – same as Ganache
- `evm_revert` – same as Ganache.
- `evm_snapshot` – same as Ganache.
- `evm_setNextBlockTimestamp` - set the timestamp to be used for the next block, if next block is mined with a timestamp, this set will be resetted, on the other hand, it is only effective for only 1 next block.

### Unsupported methods

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,5 @@ export function validateParams(params: any[], ...types: Array<t.Type<any>>) {

decoded.push(result.value);
}

return decoded;
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { BN } from "ethereumjs-util";
import * as t from "io-ts";

import { MethodNotFoundError, MethodNotSupportedError } from "../errors";
import {
InvalidInputError,
MethodNotFoundError,
MethodNotSupportedError
} from "../errors";
import { rpcQuantity, validateParams } from "../input";
import { BuidlerNode } from "../node";
import { numberToRpcQuantity } from "../output";
Expand All @@ -19,6 +23,11 @@ export class EvmModule {
case "evm_increaseTime":
return this._increaseTimeAction(...this._increaseTimeParams(params));

case "evm_setNextBlockTimestamp":
return this._setNextBlockTimestampAction(
...this._setNextBlockTimestampParams(params)
);

case "evm_mine":
return this._mineAction(...this._mineParams(params));

Expand All @@ -32,6 +41,29 @@ export class EvmModule {
throw new MethodNotFoundError(`Method ${method} not found`);
}

// evm_setNextBlockTimestamp

private _setNextBlockTimestampParams(params: any[]): [number] {
return validateParams(params, t.number);
tranvictor marked this conversation as resolved.
Show resolved Hide resolved
}

private async _setNextBlockTimestampAction(
timestamp: number
): Promise<string> {
const latestBlock = await this._node.getLatestBlock();
const increment = new BN(timestamp).sub(
new BN(latestBlock.header.timestamp)
);
if (increment.lte(new BN(0))) {
throw new InvalidInputError(
`Timestamp ${timestamp} is lower than previous block's timestamp` +
`${new BN(latestBlock.header.timestamp).toNumber()}`
);
}
await this._node.setNextBlockTimestamp(new BN(timestamp));
return timestamp.toString();
tranvictor marked this conversation as resolved.
Show resolved Hide resolved
}

// evm_increaseTime

private _increaseTimeParams(params: any[]): [number] {
Expand All @@ -47,12 +79,37 @@ export class EvmModule {

// evm_mine

private _mineParams(params: any[]): [] {
return validateParams(params);
private _mineParams(params: any[]): [number] {
if (params.length === 0) {
params.push(0);
}
return validateParams(params, t.number);
}

private async _mineAction(): Promise<string> {
await this._node.mineEmptyBlock();
private async _advanceTimeOffsetAccordingToTimestamp(timestamp: BN) {
const latestBlock = await this._node.getLatestBlock();
const increment = new BN(timestamp).sub(
new BN(latestBlock.header.timestamp)
);
if (increment.lte(new BN(0))) {
throw new InvalidInputError(
`Timestamp ${timestamp} is lower than previous block's timestamp`
);
}
await this._node.increaseTime(increment);
}

private async _mineAction(timestamp: number): Promise<string> {
if (timestamp !== 0) {
await this._advanceTimeOffsetAccordingToTimestamp(new BN(timestamp));
} else {
const nextBlockTimestamp = await this._node.getNextBlockTimestamp();
if (!nextBlockTimestamp.eq(new BN(0))) {
tranvictor marked this conversation as resolved.
Show resolved Hide resolved
timestamp = nextBlockTimestamp.toNumber();
await this._advanceTimeOffsetAccordingToTimestamp(new BN(timestamp));
}
}
await this._node.mineEmptyBlock(new BN(timestamp));
return numberToRpcQuantity(0);
}

Expand Down
43 changes: 38 additions & 5 deletions packages/buidler-core/src/internal/buidler-evm/provider/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ interface Snapshot {
latestBlock: Block;
stateRoot: Buffer;
blockTimeOffsetSeconds: BN;
nextBlockTimestamp: BN;
transactionByHash: Map<string, Transaction>;
transactionHashToBlockHash: Map<string, string>;
blockHashToTxBlockResults: Map<string, TxBlockResult[]>;
Expand Down Expand Up @@ -239,6 +240,7 @@ export class BuidlerNode extends EventEmitter {
private readonly _accountPrivateKeys: Map<string, Buffer> = new Map();

private _blockTimeOffsetSeconds: BN = new BN(0);
private _nextBlockTimestamp: BN = new BN(0);
private _transactionByHash: Map<string, Transaction> = new Map();
private _transactionHashToBlockHash: Map<string, string> = new Map();
private _blockHashToTxBlockResults: Map<string, TxBlockResult[]> = new Map();
Expand Down Expand Up @@ -358,6 +360,7 @@ export class BuidlerNode extends EventEmitter {
await this._saveTransactionAsReceived(tx);

const block = await this._getNextBlockTemplate();

const needsTimestampIncrease = await this._timestampClashesWithPreviousBlockOne(
block
);
Expand Down Expand Up @@ -400,6 +403,8 @@ export class BuidlerNode extends EventEmitter {
vmTracerError
);

await this._resetNextBlockTimestamp();

return {
trace: vmTrace,
block,
Expand All @@ -409,8 +414,13 @@ export class BuidlerNode extends EventEmitter {
};
}

public async mineEmptyBlock() {
public async mineEmptyBlock(timestamp: BN) {
const block = await this._getNextBlockTemplate();

if (!timestamp.eq(new BN(0))) {
await this._setBlockTimestamp(block, timestamp);
}

const needsTimestampIncrease = await this._timestampClashesWithPreviousBlockOne(
block
);
Expand Down Expand Up @@ -438,6 +448,8 @@ export class BuidlerNode extends EventEmitter {

await this._saveBlockAsSuccessfullyRun(block, result);

await this._resetNextBlockTimestamp();

return result;
} catch (error) {
// We set the state root to the previous one. This is equivalent to a
Expand Down Expand Up @@ -653,6 +665,10 @@ export class BuidlerNode extends EventEmitter {
return this._stateManager.getContractCode(address);
}

public async setNextBlockTimestamp(timestamp: BN) {
this._nextBlockTimestamp = new BN(timestamp);
}

public async increaseTime(increment: BN) {
this._blockTimeOffsetSeconds = this._blockTimeOffsetSeconds.add(increment);
}
Expand All @@ -661,6 +677,10 @@ export class BuidlerNode extends EventEmitter {
return this._blockTimeOffsetSeconds;
}

public async getNextBlockTimestamp(): Promise<BN> {
return this._nextBlockTimestamp;
}

public async getSuccessfulTransactionByHash(
hash: Buffer
): Promise<Transaction | undefined> {
Expand Down Expand Up @@ -714,6 +734,7 @@ export class BuidlerNode extends EventEmitter {
latestBlock: await this.getLatestBlock(),
stateRoot: await this._stateManager.getStateRoot(),
blockTimeOffsetSeconds: new BN(this._blockTimeOffsetSeconds),
nextBlockTimestamp: new BN(this._nextBlockTimestamp),
tranvictor marked this conversation as resolved.
Show resolved Hide resolved
transactionByHash: new Map(this._transactionByHash.entries()),
transactionHashToBlockHash: new Map(
this._transactionHashToBlockHash.entries()
Expand Down Expand Up @@ -757,6 +778,7 @@ export class BuidlerNode extends EventEmitter {
this._blockchain.deleteAllFollowingBlocks(snapshot.latestBlock);
await this._stateManager.setStateRoot(snapshot.stateRoot);
this._blockTimeOffsetSeconds = newOffset;
this._nextBlockTimestamp = snapshot.nextBlockTimestamp;
this._transactionByHash = snapshot.transactionByHash;
this._transactionHashToBlockHash = snapshot.transactionHashToBlockHash;
this._blockHashToTxBlockResults = snapshot.blockHashToTxBlockResults;
Expand Down Expand Up @@ -1044,7 +1066,7 @@ export class BuidlerNode extends EventEmitter {
header: {
gasLimit: this._blockGasLimit,
nonce: "0x42",
timestamp: await this._getNextBlockTimestamp()
timestamp: await this._getNextUsableBlockTimestamp()
tranvictor marked this conversation as resolved.
Show resolved Hide resolved
}
},
{ common: this._common }
Expand All @@ -1062,9 +1084,16 @@ export class BuidlerNode extends EventEmitter {
return block;
}

private async _getNextBlockTimestamp(): Promise<BN> {
const realTimestamp = new BN(getCurrentTimestamp());
return realTimestamp.add(this._blockTimeOffsetSeconds);
private async _resetNextBlockTimestamp() {
this._nextBlockTimestamp = new BN(0);
}

private async _getNextUsableBlockTimestamp(): Promise<BN> {
if (this._nextBlockTimestamp.eq(new BN(0))) {
const realTimestamp = new BN(getCurrentTimestamp());
return realTimestamp.add(this._blockTimeOffsetSeconds);
}
return new BN(this._nextBlockTimestamp);
}

private async _saveTransactionAsReceived(tx: Transaction) {
Expand Down Expand Up @@ -1238,6 +1267,10 @@ export class BuidlerNode extends EventEmitter {
block.header.timestamp = new BN(block.header.timestamp).addn(1);
}

private async _setBlockTimestamp(block: Block, timestamp: BN) {
block.header.timestamp = new BN(timestamp);
}

private async _validateTransaction(tx: Transaction) {
// Geth throws this error if a tx is sent twice
if (await this._transactionWasSuccessful(tx)) {
Expand Down
Loading