# Zero to Hero in Foundry - Part 6: Time Travel & Events in Tests

> ## Recap

> What’s up, fellow chain-surfers! 🏄‍♂️
> 
> Welcome back to our grand tour of Foundry. So in Part 5 we looked into what Gas is and how it can be optimized to make our contracts user friendly. We wrote a gas-intensive version of an ether batch transfer contract and understood how we can get gas reports and snapshots. Then, we optimized the contract to remove gas intensive parts and used gas saving methods. Finally, we compared the gas used before and after and achieved ~50% reduction.

> Please read it if you haven’t already before continuing

> Foundry: Zero to Hero - Part 5

> **Wanna learn Web3 through live and interactive challenges? You’ll love and find that** [**Web3Compass**](https://www.web3compass.xyz/) ***is the best!!***

## Today’s Outcome

**Topic of focus: Time Travel & Events in Tests**

> ### **Code base -** [**Auction ↗️**](https://github.com/abhiramelf/15-days-of-foundry/tree/main/day-6/Auction)

We'll be dissecting a simple `Auction.sol` contract. But the real star of the show isn't the contract itself—it's how we're going to test it. We’re going to become time travelers, bending the blockchain's clock to our will with `vm.warp()`, and we'll develop advanced hearing to listen for our contract's whispers (aka events) using `vm.expectEmit()`.

Ready to get your hands dirty? Let's dive in!

---

## 🧐 Meet Our Specimen: The `Auction.sol` Contract

First, let's get acquainted with our auction contract. It’s a minimal, no-frills smart contract for a single-item ETH auction.

> ### Here’s our contract - [**Auction.sol 🚀**](https://github.com/abhiramelf/15-days-of-foundry/blob/main/day-6/Auction/src/Auction.sol)

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1755774585305/70b12e67-1155-4b11-8220-09593501e0e0.gif align="center")

### Auction State

The contract has several state variables to keep track of everything happening:

* `highestBidder` (address) & `highestBid` (uint256): These do exactly what they say on the tin—they track who's currently winning and with how much.
    
* `auctionStartTime` & `auctionEndTime` (uint256): These are immutable timestamps that define the auction's lifespan. Once set in the constructor, they can't be changed.
    
* `seller` (address payable): The creator of the auction who will receive the final bid.
    
* `isCanceled` & `isFinalized` (bool): Simple flags to track the auction's status.
    
* `bids` (mapping): This is our refund ledger. When someone gets outbid, their bid amount is stored here, waiting for them to withdraw it. This is a crucial part of the "withdraw pattern".
    

### Events & Errors

Our contract is a chatterbox! It emits events for key actions, making it easy for off-chain applications to track what's happening:

* `BidPlaced`: Fired when a new highest bid is made.
    
* `Withdrawn`: Fired when an outbid user withdraws their funds.
    
* `AuctionFinalized`: Emitted when the seller finalizes the auction and claims the prize.
    
* `AuctionCanceled`: Emitted if the seller cancels the auction.
    

It also has a bunch of custom errors to give clear feedback when someone tries to do something they shouldn't.

### Core Functions

* `constructor(uint256 _endTime)`: When deploying, the seller specifies a duration for the auction. The constructor sets the `auctionStartTime` to the current block's timestamp and the `auctionEndTime` to `block.timestamp + _endTime`.
    
* `bid()`: This is where the magic happens. Anyone can call this payable function to place a bid. It checks if the bid is higher than the current `highestBid`. If it is, it cleverly refunds the *previous* highest bidder by adding their bid to the `bids` mapping, then updates `highestBidder` and `highestBid`.
    
* `withdraw()`: If you've been outbid, you call this function to get your ETH back. It's a pull-based system to prevent nasty re-entrancy bugs.
    
* `finalizeAuction()`: Only the seller can call this, and only after the `auctionEndTime` has passed. It marks the auction as finalized and transfers the `highestBid` to the seller.
    
* `cancelAuction()`: The seller has an escape hatch! They can cancel the auction, but only if no bids have been placed yet.
    

---

## 🧪 Putting It to the Test: `Auction.t.sol`

Alright, we know the contract. Now, let's see how we can test its every nook and corner with Foundry.

> ### Here’s our test contract - [**Auction.t.sol 🚀**](https://github.com/abhiramelf/15-days-of-foundry/blob/main/day-6/Auction/test/Auction.t.sol)

Our test setup (`setUp()` function) uses a handy address `0xBEEF` as the `owner` and gives it 10 ETH using `vm.deal(owner, 10 ether)`. We then use `vm.prank(owner)` to make sure the `Auction` contract is deployed from this address, setting it as the `seller`.

Now for the main event!

### ⏰ Bending Time with `vm.warp()`

Our contract has time-sensitive logic. The `finalizeAuction()` function should only work *after* `auctionEndTime`. The `bid()` function should only work *before* `auctionEndTime`. How do we test this without sitting around waiting for a day?

We don't! We use `vm.warp()`. This cheat-code is our time machine; it instantly sets the blockchain's `block.timestamp` to any value we want.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1755774977763/f1c4447f-3c78-4c14-a85a-60d462c2d95a.gif align="center")

Let's look at a couple of tests.

First, how do we test that bidding is disabled after the auction ends? Simple:

```solidity
/// @notice bidding after the auction end time should revert
function testAuctionEnded() public {
    vm.warp(auction.auctionEndTime()); // 1. Fast-forward time!
    vm.expectRevert(Auction.AuctionEnded.selector); // 2. Expect an error
    auction.bid{value: 1 ether}(); // 3. Try to bid
}
```

In this test, we instantly jump to the `auctionEndTime` using `vm.warp()`. At this exact moment, any attempt to bid should fail. We tell Forge to expect the `AuctionEnded` error, and then we try to bid. If the bid fails with that specific error, the test passes! 🎉

Similarly, to test the happy path for finalizing the auction, we need to be in the future:

```solidity
function testFinalizeAuction() public {
    // ... users place bids ...

    vm.warp(auction.auctionEndTime()); // Jump to the end

    vm.expectEmit(true, true, true, true);
    emit AuctionFinalized(user2, 2 ether);

    vm.prank(owner);
    auction.finalizeAuction(); // Now, this should work!
}
```

Without `vm.warp()`, testing time-dependent logic would be a nightmare. With it, it's a piece of cake.

---

### 🎧 Listening for Events with `vm.expectEmit()`

State changes are great, but a well-behaved contract should also communicate its actions through events. How do we test that the right events are being emitted with the right data? With `vm.expectEmit()`, of course!

Think of it as putting a stethoscope on your contract. You tell Forge, "I expect to hear *this specific sound*," and then you trigger the action.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1755776062176/d9dd7bfd-ab83-4511-ba9c-b3d8221fed17.gif align="center")

Let's see how we test the `BidPlaced` event:

```solidity
/// @notice ensure BidPlaced event is emitted with correct values
function testBid_EmitsBidPlaced() public {
    vm.expectEmit(true, false, false, true); // 1. Set up our listener
    emit BidPlaced(address(this), 1 ether); // 2. Define the expected event

    auction.bid{value: 1 ether}(); // 3. Place the bid
}
```

Let's break down that `vm.expectEmit` line, as it can look a bit cryptic: `vm.expectEmit(checkTopic1, checkTopic2, checkTopic3, checkData)`

* In Solidity events, parameters marked as `indexed` become **topics**. They are easier to search for. Non-indexed parameters are part of the **data** payload.
    
* Our`BidPlaced` event is `event BidPlaced(address indexed bidder, uint256 amount)`.
    
    * `bidder` is the first topic.
        
    * `amount` is the data.
        
* So, `vm.expectEmit(true, false, false, true)` means:
    
    * `checkTopic1 = true`: Yes, please check the first topic (the bidder's address).
        
    * `checkTopic2 = false`: Ignore the second topic (there isn't one).
        
    * `checkTopic3 = false`: Ignore the third topic (also doesn't exist).
        
    * `checkData = true`: Yes, please check the event's data (the bid amount).
        

The very next line, `emit BidPlaced(address(this), 1 ether);`, tells Foundry *exactly* what to look for: an event where the `bidder` is the test contract (`address(this)`) and the `amount` is `1 ether`. When `auction.bid()` is called, Foundry checks if the emitted event matches these expectations. If it does, we get a green light. ✅

---

## 🚀 Wrapping Up

And that's a wrap! We took a seemingly complex `Auction` contract and wrote powerful, precise tests for it. We saw how to:

* **Become a Time Lord** with `vm.warp()` to test logic that depends on the passage of time.
    
* **Become a Super-Listener** with `vm.expectEmit()` to ensure our contract is communicating correctly with the outside world.
    
* Use other cheat-codes like `vm.expectRevert`, `vm.prank`, and `vm.deal` to create any test scenario we can imagine.
    

Testing doesn't have to be a chore. With tools like Foundry, it can be an incredibly powerful and, dare I say, *fun* part of the development process. Now go ahead, clone that repo, run `forge test` for yourself, and try breaking our contract! Happy forging!
