🚨 This repo will be updated regularly for bug fixes, if game balance is tweaked, and to add top agents from seasons. Try to pull every now and then.
Searchers of Nottingham is an MEV-themed multiplayer game built on the Ethereum Virtual Machine. Players submit compiled smart contracts (agents) which represent Merchants at a medieval fair trying to stock their stall with goods (tokens). Merchants face each other in multiple small games. Each round Merchants will try to exchange their gold and goods at the Market (a constant product AMM). But a Merchant can also choose to bid gold to become the "Sheriff" for that round. The Sheriff chooses the order in which all trades are made and can also make their own trades at will. The first Merchant to acquire the maximum amount of any single good will win!
Assuming you already have foundry configured, clone the repo, enter the project folder, then just
# Install dependencies.
forge install
# Build the project.
forge build
ℹ️ You can find the core logic for the game inside Game.sol.
Each match happens in a fresh instance of the Game
contract.
- The
Game
contract will deploy each of the agent bytecodes passed in and assign them player indexes in the same order. Agents are identified by these (0-based) indices, not their address. Agents will not know the address of other agents. - An N-dimensional constant-product AMM will be initialized with
N
assets/tokens, whereN
is equal to the number of agents in the game. Assets are 18-decimal tokens identified by their (0-based) asset index. Asset0
is equivalent to the "gold" asset and1+
are designanted as "goods." This means that there is always one less good than there are players.
ℹ️ Contest tournament matches always consist of 4 agents.
For every round the following happens:
- Each agent is resupplied, being minted
1
units (1e18
) of every goods token and only1
wei of gold. - All agents participate in a blind auction for the privilege of building the block for the round.
- If an agent wins the block auction, that agent builds the block, settling all bundles and getting their bid burned.
- If no agent wins the block auction, no bundles are settled.
- Each reserve of goods token in the market (i.e., not held by players) will have ~6% burned. (🌟 New for season 3)
- If we've played the maximum number of rounds (
32
) or an agent is found to have64
units (64e18
) of any goods token, the game ends.
When the game ends, agents are sorted by their maximum balance of any goods token. This is the final ranking for the match.
Agents are smart contracts that expose two callback functions:
createBundle(uint8 builderIdx) -> PlayerBundle bundle
- Returns a
PlayerBundle
for the round, which consists of a sequence of swaps that must be executed by the block builder identified bybuilderIdx
. - Each player bundle is settled atomically (the swaps cannot be broken up).
- Empty swaps will be ignored.
- If ANY swap in your bundle fails due to insufficient balance, the entire bundle fails, so beware!
- Returns a
buildBlock(PlayerBundle[] bundles) -> uint256 bid
- Receives all bundles created by every agent, ordered by player index.
- Must settle (via
Game.settleBundle()
) ALL OTHER player bundles, in any order. Settiling the builder's own bundle is optional. - Can directly call swap functions on the
Game
instance (sell()
orbuy()
) at any time, including between settling other player bundles. - Returns the agent's bid (in gold) to build this block. This gold will be burned if they win the block auction.
The IGame
interface exposes all functions on the Game
instance available to agents. You should take some time to quickly review it.
The project comes with some example agent contracts. None are particularly sophisticated but will demonstrate typical agent behaviors. You can choose to create new contracts in the agents
folder and extend the examples to get started quickly.
The project comes with a foundry script for running matches locally so you can see how well your bot performs against others. Matches can be held against 2 or more agent contracts. Note that production tournaments are always conducted with 4 agents.
For example, you can run a match against the included example agents GreedyBuyer
, CheapBuyer
, and CheapFrontRunner
with the following command:
forge script Match --sig 'runMatch(string[])' '["GreedyBuyer", "CheapBuyer", "CheapFrontRunner"]'
runMatch()
assigns agents the same player indexes in the same order that agent names are passed in. The performance of some of the example agents can be biased by their order in a match. You can instead use runShuffledMatch()
to assign agents a random index each run:
forge script Match --sig 'runShuffledMatch(string[])' '["GreedyBuyer", "CheapBuyer", "CheapFrontRunner"]'
If successful, the output of either command will:
- For each round:
- Print each agent's bid, ending balances, and change from last round.
- Prefix the agent that built the block with
(B)
. - If the agent's name is in red, one of their callbacks failed.
- Print the swaps that occured in that round, in order.
- At the end of game, print the final score (max non-gold balance) for each agent.
If an agent's name shows up in red it means it reverted during one of its callbacks.
# ...
Round 26:
SimpleBuyer [0] (bid 🪙 0.0):
🪙 0.0 (+0.0)
🍅 64.7681 (+3.6227)
TheCuban [3] (bid 🪙 0.462):
🪙 0.840 (+0.7)
🍅 1.9701 (+0.9801)
🥖 1.9701 (+0.9801)
🐟️ 62.1590 (+1.0)
GreedyBuyer [1] (bid 🪙 0.119):
🪙 0.83 (+0.83)
🍅 22.7657 (+0.7700)
🥖 22.7657 (+0.7700)
🐟️ 26.0 (+1.0)
(B) CheapFrontRunner [2] (bid 🪙 0.685):
🪙 0.1394 (+0.0)
🍅 0.1994 (-3.0)
🥖 2.6864 (-52.8701)
🐟️ 25.6553 (+23.6235)
======ROUND ACTIVITY======
(B) CheapFrontRunner sold:
🍅 0.2099 -> 🪙 0.58
🍅 3.7900 -> 🐟️ 3.4115
🥖 2.8278 -> 🪙 0.626
🥖 51.423 -> 🐟️ 19.2119
SimpleBuyer sold:
🥖 1.0 -> 🍅 0.4492
🐟️ 1.0 -> 🍅 2.1734
GreedyBuyer sold:
🍅 0.2299 -> 🪙 0.58
🥖 0.2299 -> 🪙 0.24
TheCuban sold:
🍅 0.199 -> 🪙 0.5
🥖 0.199 -> 🪙 0.2
🏁 Game ended after 26 rounds:
🏆️ SimpleBuyer [0]: 🍅 64.7681 (1)
🥈 TheCuban [3]: 🐟️ 62.1590 (3)
🥉 GreedyBuyer [1]: 🐟️ 26.0 (3)
🥉 CheapFrontRunner [2]: 🐟️ 25.6553 (3)
If you want to see more detail of what went on during a match, you can run the script with full traces on by passing in the -vvvv
flag. Be warned, this will be a lot of output and can be difficult to read because of how the game logic calls agent callbacks repeatedly to simulate them.
⚠️ Local matches use the foundry scripting engine but the actual tournaments use an isolated, vanilla, Dencun-hardfork environment, so any foundry cheatcodes used by your agent contract will fail in production. You should also not count on theGame
contract being at the same address.