diff --git a/SUPPORTED_APIS.md b/SUPPORTED_APIS.md index 7110dcdd..a76d569d 100644 --- a/SUPPORTED_APIS.md +++ b/SUPPORTED_APIS.md @@ -14,6 +14,8 @@ The `status` options are: | Namespace | API |
Status
| Description | | --- | --- | --- | --- | +| `ANVIL` | `anvil_snapshot` | `SUPPORTED` | Snapshot the state of the blockchain at the current block | +| `ANVIL` | `anvil_revert` | `SUPPORTED` | Revert the state of the blockchain to a previous snapshot | | `ANVIL` | `anvil_setTime` | `SUPPORTED` | Sets the internal clock time to the given timestamp | | `ANVIL` | `anvil_increaseTime` | `SUPPORTED` | Jump forward in time by the given amount of time, in seconds | | `ANVIL` | `anvil_setNextBlockTimestamp` | `SUPPORTED` | Works like `anvil_increaseTime`, but takes the exact timestamp that you want in the next block, and increases the time accordingly | diff --git a/e2e-tests/test/anvil-apis.test.ts b/e2e-tests/test/anvil-apis.test.ts index 72d24148..648ff80b 100644 --- a/e2e-tests/test/anvil-apis.test.ts +++ b/e2e-tests/test/anvil-apis.test.ts @@ -10,6 +10,43 @@ import { BigNumber } from "ethers"; const provider = getTestProvider(); +describe("anvil_snapshot", function () { + it("Should return incrementing snapshot ids", async function () { + const wallet = new Wallet(RichAccounts[6].PrivateKey); + const deployer = new Deployer(hre, wallet); + const greeter = await deployContract(deployer, "Greeter", ["Hi"]); + expect(await greeter.greet()).to.eq("Hi"); + + // Act + const snapshotId1: string = await provider.send("anvil_snapshot", []); + const snapshotId2: string = await provider.send("anvil_snapshot", []); + + // Assert + expect(await greeter.greet()).to.eq("Hi"); + expect(BigNumber.from(snapshotId2).toString()).to.eq(BigNumber.from(snapshotId1).add(1).toString()); + }); +}); + +describe("anvil_revert", function () { + it("Should revert with correct snapshot id", async function () { + const wallet = new Wallet(RichAccounts[6].PrivateKey); + const deployer = new Deployer(hre, wallet); + const greeter = await deployContract(deployer, "Greeter", ["Hi"]); + expect(await greeter.greet()).to.eq("Hi"); + const snapshotId = await provider.send("anvil_snapshot", []); + const setGreetingTx = await greeter.setGreeting("Hola, mundo!"); + await setGreetingTx.wait(); + expect(await greeter.greet()).to.equal("Hola, mundo!"); + + // Act + const reverted: boolean = await provider.send("anvil_revert", [snapshotId]); + + // Assert + expect(await greeter.greet()).to.eq("Hi"); + expect(reverted).to.be.true; + }); +}); + describe("anvil_increaseTime", function () { it("Should increase current timestamp of the node", async function () { // Arrange diff --git a/src/namespaces/anvil.rs b/src/namespaces/anvil.rs index 4cf69ee9..39c802a0 100644 --- a/src/namespaces/anvil.rs +++ b/src/namespaces/anvil.rs @@ -6,6 +6,29 @@ use crate::utils::Numeric; #[rpc] pub trait AnvilNamespaceT { + /// Snapshot the state of the blockchain at the current block. Takes no parameters. Returns the id of the snapshot + /// that was created. A snapshot can only be reverted once. After a successful `anvil_revert`, the same snapshot id cannot + /// be used again. Consider creating a new snapshot after each `anvil_revert` if you need to revert to the same + /// point multiple times. + /// + /// # Returns + /// The `U64` identifier for this snapshot. + #[rpc(name = "anvil_snapshot")] + fn snapshot(&self) -> RpcResult; + + /// Revert the state of the blockchain to a previous snapshot. Takes a single parameter, + /// which is the snapshot id to revert to. This deletes the given snapshot, as well as any snapshots + /// taken after (e.g.: reverting to id 0x1 will delete snapshots with ids 0x1, 0x2, etc.) + /// + /// # Arguments + /// + /// * `id` - The snapshot id to revert + /// + /// # Returns + /// `true` if a snapshot was reverted, otherwise `false`. + #[rpc(name = "anvil_revert")] + fn revert(&self, id: U64) -> RpcResult; + /// Set the current timestamp for the node. /// Warning: This will allow you to move backwards in time, which may cause new blocks to appear to be /// mined before old blocks. This will result in an invalid state. diff --git a/src/node/anvil.rs b/src/node/anvil.rs index c8f473bb..a3251f5a 100644 --- a/src/node/anvil.rs +++ b/src/node/anvil.rs @@ -12,6 +12,24 @@ use crate::{ impl AnvilNamespaceT for InMemoryNode { + fn snapshot(&self) -> RpcResult { + self.snapshot() + .map_err(|err| { + tracing::error!("failed creating snapshot: {:?}", err); + into_jsrpc_error(Web3Error::InternalError(err)) + }) + .into_boxed_future() + } + + fn revert(&self, id: U64) -> RpcResult { + self.revert_snapshot(id) + .map_err(|err| { + tracing::error!("failed reverting snapshot: {:?}", err); + into_jsrpc_error(Web3Error::InternalError(err)) + }) + .into_boxed_future() + } + fn set_time(&self, timestamp: Numeric) -> RpcResult { self.set_time(timestamp) .map_err(|err| {