Modeling Uniswap in cadCAD

We are working on a cadCAD model of Uniswap and using the same data I decided to plot a few charts to illustrate how bonding curves in action. Here’s “Visualizing bonding curves: the Uniswap case” :slight_smile:


Left chart
DAI and ETH balances of the Uniswap smart contract move along the curve. The area of the green rectangle is constant with every move, except fees make the curve move up and to the right a little bit with each trade. The curve moves more - up or down, but always along the same diagonal - when liquidity is added to or removed from the pool (= UNI mints/burns, upper-right chart).

Both of these conservations - the area and the diagonal - are somewhat approximated in the video because we are looking at aggregated daily activity, not individual transactions.

Middle-right chart
How 3 different strategies performed over time since the first day Uniswap was live. It hints at the effects of impermanent loss, eg. until Dec-16 liquidity providers had performed worse than “50/50 hodlers”.

Bottom-right chart
My take on adding another dimension to the excellent analysis done here. It shows how liquidity providers fared compared to 50/50 holders depending on the initial date of the comparison. It confirms the intuition that because ETH/DAI has been going mostly sideways in the past year or so, with sudden spikes and drops here and there, impermanent loss is frequent but doesn’t last long.

Next steps
The cadCAD model is in our GitHub. Some cool next steps would be to model actual user behavior (so far we’ve merely mapped actual events to simplified action models for backtesting the mechanisms implementation), so that we can simulate some interesting “what if” scenarios (what if fees where higher/lower/dynamic? what if ETH skyrocketed or plummeted? …) . If you want to help, reply to this thread and we’ll coordinate.

You can find the data and the code for this analysis on this Kaggle notebook


Hi @markusbkoch,

Im interested in tinkering with this model. As it stands can the Uni/Eth/Dai balances go negative (in the model and/or the protocol)?


Welcome @ipal8181, nice to see you take an interest in the model :slight_smile:

In the protocol, none of the balances can go negative. The model should also not go negative as long as you’re backtesting it using the exact digital twin of the protocol - same swap fee, same liquidity, same trades every time.

There was a work in progress version I was working on late 2019 but never made to master that incurred in that invalid state - negative balances - when exploring different initial conditions or system parameters. The version in this PR by @pkretzschmar and @Stefenon builds on top of that 2019 work and aims to fix those issues. I plan to review it and merge it to the demos repo soon, but in the meantime feel free to fork it, check it out and leave your comments in the PR :rocket:

1 Like

Awesome many thanks for these pointers!

Hi @markusbkoch,

I was wondering if there was any update to the master about fixing the issue of negative balances?

I’m still studying the forks of @pkretzschmar and @Stefenon which I havent got running for me yet ( wonder if its still WIP) - I get a Type Error method’ object is not subscriptable for the lines:
pct_amount = input_[‘UNI_pct’]

which I believe attempts to remove liquidity as a percentage of what is available (so balances do not go negative).

To this I have 2 questions. It seems the uniswap events data removes liquidity in 2 events: one a “Transfer” and then a “Remove Liquidity” event.
Am I right in saying the cadCad model doesnt do anything for the timesteps where the event is a “Remove Liquidity” event (but attempts to removes the liquidity at the transfer event)?

Also should the cadCAD mechanism match the vyper code/protocol as a backbone (which Im still to study in depth) which I presume rejects orders (or does nothing) when there is a request to remove liquidity that is not there?

Thanks again!

I have finally taken the time to review and merge the updates to the model - it works for me on cadCAD 0.4.23. Congrats @pkretzschmar and @Stefenon!


Yes, this is because the relevant information for the model is contained at the records of the historical dataset that refer to the Transfer event (ie what % of the UNI supply was burned)

Because it’s been modeled as a percentage, there should never be a simulated transaction that removes more liquidity than the digital twin holds

1 Like

Awesome many thanks everyone. The new version works for me as well.

Hi, @markusbkoch, I am looking into the repo of uniswap example in the demo repo of cadCAD, however the following files in the “parts” subfolder are missing:

Would you to make them available for education purpose on how to apply cadCAD on uniswap simulations? Thanks.

1 Like

@DennisZ you can download it at

Hi @markusbkoch,

Am I right in saying although the updated model now has a UNI_pct feature ( i.e only removes a percentage of liquidity removed) to avoid negative balances, negative balances can still occur when swapping tokens (if for example simulated agents interact with the model).

For example if the pool has x ETH and y tokens and you want to you want to buy z ETH ( where z>x) this can occur and wipe out the pool? I presume a ETH_pct or DAI_pct feature or order reject feature could cater for this, but I was hoping to clarify am I right negative balances can still occur in the master model in this situation?

Many thanks again!

I’d have to dig into the code a bit to be sure, but I don’t think that invalid state could be reached because the model always handles swaps based on the amount of tokens the trader wants to sell to the pool, not buy. So barring a bug in the model implementation, z<x for all w, where w is the number of tokens the trader is selling into the pool. Please share your findings if you decide to stress test this! :slight_smile:

Hi Markus,

Thanks for this. My mistake, the model does indeed handle swaps based on the amount of tokens traders is selling to the pool so the pricing function should always ensure a pool doesn’t go negative.

I was getting negative balances due to liquidity removal percentage function (below) didn’t cater to my interacting agents (seems to work fine for the digital twin representation model as intended), Thus anyone programming interacting agents probably needs to ensure their liquidity removal is conditioned on the liquidity token available.

elif event == ‘Transfer’:
UNI_delta = uniswap_events[‘uni_delta’][t]
UNI_supply = uniswap_events[‘UNI_supply’][t-1]
if UNI_delta < 0:
action[‘UNI_burn’] = -UNI_delta
action[‘UNI_pct’] = -UNI_delta / UNI_supply
del uniswap_events
return action

Makes sense. We should probably improve the model to account for attempts at burning more UNI than the supply, though. I’ve quickly put something together that might work, but I unfortunately don’t have the time to test it. It’s basically emulating a revert at the state update function level in case an invalid amount of UNI is passed as a parameter.

If you could review it and propose any changes, it’d be much appreciated :pray: Thank you!

Burn constraint looks good. I’ve incorporated in my simulations and will revert if it works or are issues.

Hi @markusbkoch,

Was looking at the following Uniswap model notebook:

In the ExpMovingAverage function (block #9), there are the following lines:
plot_data[‘ETH_price_DAI’] = plot_data[‘DAI_balance’] / plot_data[‘ETH_balance’]
plot_data[‘UNI_price_DAI’] = 2 * plot_data[‘DAI_balance’] / plot_data[‘UNI_supply’]

Can I ask why the UNI price ( in DAI) needs to be multiplied by 2?


Sorry this slipped for so long. First thing to keep in mind is that this notebook predates the launch of the Uniswap governance token, so UNI here refers to the token that liquidity providers receive when they add liquidity to the pool - it represents pool ownership.

So in this case we’re keeping track of the value of one share of the pool. The shares (UNI) are a claim on the balance of the pool. Assuming the pool is in 50/50 balance, the value of the pool, measured in DAI, is equal to DAI_balance * 2. The value of a single share is the value of the pool divided by the total number of shares `UNI_supply``

I hope this helps, let me know if you have any questions.

1 Like

I have another question about the Uniswap digital twin. Looking at the original real dataset “uniswap_events.pickle” when calculating slippage for both EthPurchase and TokenPurchase I sometimes see negative slippage i.e. the effective price ( ratio of ETH/DAI deltas) is more the spot price ( ratio of ETH/DAI previous step balances). This is even before taking into account 0.003 fees (for spot). Below shows an example of the very last entry of the pickle dataset which is an EthPurchase.

You will see the effective price (129.068) is less than the spot price ( 132.552). One thing I notice the invariant goes down with the ETHpurchase - I would have thought it would go up with a swap due to fees. I was wondering whether this negative slippage is right or maybe its an issue with the way the data is originally sourced ( off the blockchain?) which may be including gas fees that Im now seeing as negative slippage? It also seems to occur on the newer pickle file (that runs till 2020-03-20 23:57) albeit to a lesser extent.

I’m sorry I don’t recall the specifics to be able to answer off the top of my head. It’s definitely nothing related to gas fees.

My hunch is it could be related to the ordering of the swaps, as I see some swaps with the same timestamp in the sample dataset from your screenshot and you’d need an additional column to properly sort them (it seems we had transaction index in the original dataset, but would still have the same issue if multiple swaps were to occur within the same transaction).

I would suggest you try to invert the position of swaps in your sample and see if the numbers make sense then. If that doesn’t work, please share some of your data in a google spreadsheet or in a CSV file along with the equations for each one of the derived columns and we can all look into it together.

Many thanks for this. It does seem all the instances of negative slippage are events which share the same timestamp with other events so most likely an event ordering issue. Thanks again!