How to Model with TokenSPICE EVM Agent Simulation

From a quickstart to deeper understanding of agents and netlists architecture, by example

Trent McConaghy
TokenSPICE
10 min readOct 14, 2021

--

1. Introduction

TokenSPICE is a tool that simulates tokenized ecosystems via an agent-based approach. It can help in Token Engineering flows, to design, tune, and verify tokenized ecosystems.

This article builds on two previous articles: an introduction to TokenSPICE, and flows for design & verification with inspiration from the SPICE simulator of Electrical Engineering.

This article is written for developers and modelers who want to jump right in and start using TokenSPICE, then learn about how to create their own netlists. It’s organized as follows.

  • Sections 2–3 are the quickstart sections from TokenSPICE repo’s README: initial setup (section 2), and doing simulation and making changes (section 3).
  • Section 4 describes the structure of agents and netlists. Recall that a netlist wires up a collection of agents to interact in a given way.
  • Sections 5–6 describe two example netlists. The simplegrant netlist (section 5) is in pure Python with a grant giver agent and grant receiver agent. The simplepool netlist (section 6) is a simple EVM example, where a publisher agent periodically creates a Balancer pool. While the netlist itself is simple, it uses Balancer V1 “BPool” Solidity code for a full-fidelity model.

2. Initial Setup

(This is a snapshot from TokenSPICE README. The most up-to-date version can always be found there.)

2. 1 Prerequisites

  • Linux/MacOS
  • Python 3.8.5+

2.2 Set up environment

Open a new terminal and:

2.3 Get Ganache running

Think of Ganache as local EVM blockchain network, with just one node.

Open a new terminal and:

2.4 Deploy the smart contracts to ganache

Below, you will deploy smart contracts from Ocean Protocol. Those contracts include an ERC20 datatoken factory, ERC20 template, Balancer pool factory, Balancer pool template, and metadata management. Each contract has a corresponding Python wrapper in the web3engine directory. Then, Python agents in assets/agents use these wrappers.

You can add your own smart contracts by deploying them to EVM, then adding corresponding Python wrappers and agents to use them.

Let’s do this. Open a new terminal and:

Then, deploy. In that same terminal:

Finally, open tokenspice/tokenspice.ini and set ARTIFACTS_PATH = contracts/artifacts.

  • Now, TokenSPICE knows where to find each contract on ganache (address.json file)
  • And, it knows what each contract’s interface is (*.json files).

2.4 Test one EVM-based test

Open a new terminal and:

2.5 First usage of tsp

We use tsp for TokenSPICE in the command line.

First, add pwd to bash path. In the terminal:

To see help, call tsp with no args.

2.6 Run simulation

Here’s an example on a supplied netlist simplegrant.

Simulate the netlist, storing results to outdir_csv.

Output plots to outdir_png, and view them.

Here are example plots from wsloop netlist. They track token count, tokens minted, tokens burned, and tokens granted over a 20 year period.

3. Do Simulations, Make Changes

(This is a snapshot from TokenSPICE README. The most up-to-date version can always be found there.)

3.1 Do Once, At Session Start

Start chain. Open a new terminal and:

Deploy contracts. Open a new terminal and:

3.2 Do >=1 Times in a Session

Update simulation code. Open a new terminal. In it:

Run tests. In the same terminal as before:

3.3 Test that everything is working

Commit changes.

4. Agents and Netlists

(This is a snapshot from TokenSPICE README. The most up-to-date version can always be found there.)

4.1 Agents Basics

Agents are defined at assets/agents/. Agents are in a separate directory than netlists, to facilitate reuse across many netlists.

All agents are written in Python. Some may include EVM behavior (more on this later).

Each Agent has an AgentWallet, which holds a Web3Wallet. The Web3Wallet holds a private key and creates transactions (txs).

4.2 Netlist Basics

The netlist defines what you simulate, and how.

Netlists are defined at assets/netlists/. You can reuse existing netlists or create your own.

4.3 What A Netlist Definition Must Hold

TokenSPICE expects a netlist module (in a netlist.py file) that defines these specific classes and functions:

  • SimStrategy class: simulation run parameters
  • KPIs class and netlist_createLogData() function: what metrics to log during the run
  • netlist_plotInstructions() function: how to plot the metrics after the run
  • SimState class: system-level structure & parameters, i.e. how agents are instantiated and connected. It imports agents defined in assets/agents/*Agent.py. Some agents use EVM. You can add and edit Agents to suit your needs.

4.4 How to Implement Netlists

There are two practical ways to specify SimStrategy, KPIs, and so on for netlist.py:

  1. For simple netlists. Have just one file (netlist.py) to hold all the code for each class and method given above. This is appropriate for simple netlists, like simplegrant (just Python) and simplepool (Python+EVM).
  2. For complex netlists. Have one or more separate files for each class and method given above, such as assets/netlists/NETLISTX/SimStrategy.py. Then, import them all into netlist.py file to unify their scope to a single module (netlist). This allows for arbitrary levels of netlist complexity. The wsloop netlist is a good example. It models the Web3 Sustainability Loop, which is inspired by the Amazon flywheel and used by Ocean, Boson and others as their system-level token design.

4.5 Agent.takeStep() method

The class SimState defines which agents are used. Some agents even spawn other agents. Each agent object is stored in the SimState.agents object, a dict with some added querying abilities. Key SimState methods to access this object are addAgent(agent), getAgent(name:str), allAgents(), and numAgents(). SimStateBase has details.

Every iteration of the engine make a call to each agent’s takeStep() method. The implementation of GrantGivingAgent.takeStep() is shown below. Lines 26–33 determine whether it should disburse funds on this tick. Lines 35–37 do the disbursal if appropriate.

There are no real constraints on how an agent’s takeStep() is implemented. This which gives great TokenSPICE flexibility in agent-based simulation. For example, it can loop in EVM, like we show later.

Implementation of GrantGivingAgent.takeStep()

4.6 Netlist Examples

Here are some existing netlists.

  • simplegrant — granter plus receiver, that’s all. No EVM.
  • simplepool — publisher that periodically creates new pools. EVM.
  • wsloop — Web3 Sustainability Loop. No EVM.
  • (WIP) oceanv3 — Ocean Market V3. Initial design. EVM.
  • (WIP) oceanv4 — Ocean Market V4. Solves rug pulls. EVM.

The next two sections will show how TokenSPICE netlists are structured, by elaborating on the simplegrant (pure Python) and simplepool (Python+EVM) netlists.

5. simplegrant Netlist

5.1 Overview

The simplegrant netlist at assets/netlists/simplegrant/netlist.py has two agents (objects):

As one might expect, granter gives grants to taker over time according to a simple schedule. This continues until runs out of money. These two agents are instantiated in the netlist’s SimStrategy class.

Here’s the netlist code, in Python. It’s just one file that defines SimStrategy class, SimState class, KPIs class, netlist_createLogData() function, and netlist_plotInstructions() function.

The following subsections elaborate on each of these, sequentially top-to-bottom in the netlist.py file. They’re worth understanding, because when you create your own netlist, you’ll be making your own versions of these.

5.2 Imports

Imports are at the top of the netlist.

simplegrant: imports

Lines 1–2 import from third-party libraries: enforce_types for dynamic type-checking, and List and Set to specify types for type-checking.

Lines 4–6 import local modules: definitions for grant-giving and grant-taking agents from the agents directory; base classes for KPIs, SimState and SimStrategy (more on this later); and some constants.

5.3 SimStrategy Class

The netlist defines the SimStrategy class by inheriting from a base class, then injecting code as needed. All magic numbers go here, versus scattering them throughout the code.

simplegrant: SimStrategy

Lines 14–15 define values that every SimStrategy needs: a time interval between steps (set to one hour); and a stopping condition (set to 10 days).

Lines 18–20 define values specific to this netlist: how much OCEAN the granter starts with (1.0 OCEAN); the time interval between grants (3 days); and the number of grant actions (4 actions, therefore 1.0/4 = 0.25 OCEAN per grant).

5.4 SimState Class

The netlist defines the SimState class by inheriting from a base class, then injecting code as needed.

simplegrant: SimState

Line 28 instantiates an object of class SimStrategy. We’d just defined that class earlier in the netlist.

Lines 32–37 instantiates the granter object. It’s a GrantGivingAgent which is defined in assets/agents. Like all agent instances, it’s given a name (“granter1”), initial USD funds (0.0) and initial OCEAN funds (specified via SimStrategy). As a GrantGivingAgent, it needs a few more parameters: the name of the agent receiving funds (“taker1”), the time interval between grants (via SimStrategy), and number of actions (via SimStrategy). Note how magic numbers are kept out of here; they stay in SimStrategy.

Lines 38–39 instantiates the taker object. It’s a GrantTakingAgent. It’s given a name (“taker1”); see how this is the same name that the granter has specified where funds go. This is how the netlist gets “wired up”, similar in philosophy to SPICE. Finally, the taker’s initial funds are specified, as 0.0 USD and 0.0 OCEAN.

5.5 KPIs Class

The netlist defines the KPIs by inheriting from a base class, then injecting code as needed. The base class already tracks many metrics out-of-the-box including each agent’s OCEAN balance at each time step. This netlist doesn’t need more, so its KPIs class is simply a pass-through.

simplegrant: KPIs

5.6 netlist_createLogData

The netlist defines the netlist_createLogData() function, which is called by the core simulator engine SimEngine in each takeStep() iteration of a simulation run.

simplegrant: netlist_createLogData()

Lines 58–60 initializes these 3 variables: s, dataheader, and datarow. The rest of the routine fills them in, iteratively.

  • Line 65 grabs the “granter1” agent from the SimState object state. The lines below will use that agent. This function can to grab any data from SimState. Since SimState holds all the agents, this function can grab any any agent. The lines that follow query information from the “granter” agent g.
  • s is a list of strings to be logged to the console’s standard output (stdout), where the final string is a concatenation of all items in the list. Line 66 updates s with another item, for the granter’s OCEAN balance and USD balance. Those values are retrieved by querying the g object.
  • The other two variables are towards constructing a csv file, where the first row has all the header variable names and each remaining row is another datapoint corresponding to a time step.
  • This function constructs dataheader as the list of header names. Line 67 adds the “granter_OCEAN” and “granter_USD” variables to that list.
  • This function constructs datarow as a list of variable values, i.e. a datapoint. Each of these has 1:1 mapping to header names added to dataheader. This function queries the the g object to fill in the values.

While this netlist (simplegrant) only records a single group of values (OCEAN and USD balance), other netlists like wsloop record several groups of values.

5.7 netlist_plotInstructions

The netlist defines the SimState by inheriting from a base class, then injecting code as needed.

simplegrant: netlist_plotInstructions()

This concludes the description of the simplegrant netlist. Further information can be found in its README.

6. simplepool Netlist

6.1 Overview

simplepool layers in EVM. Its netlist is at assets/netlists/simplepool/netlist.py

The netlist is a single file, which defines everything needed: SimStrategy, SimState, etc.

The netlist’s SimState creates a PublisherAgent instance, which during the simulation run creates PoolAgent objects.

Each PoolAgent holds a full-fidelity EVM Balancer pool as follows:

  • At the top level, each PoolAgent Python object (an agent) holds a pool.BPool Python object (a driver to the lower level).
  • One level lower, each BPool Python object (a driver) points to a BPool.sol contract deployment in Ganache EVM (actual contract).

You can view pool.BPool as a middleware driver to the contract deployed to EVM. Like all drivers, is in the web3engine/ directory.

7. Conclusion

This article targeted developers and modelers wanting to jump right in and start using TokenSPICE, then learn about how to create their own netlists. It reviewed how agents and netlists work, and then used worked examples on two simple netlists — a pure Python one and an EVM-based one.

As a next step, we encourage you to go to the TokenSPICE repo, and go through the README to try it for yourself:)

--

--