Trade Liquidity Volume Market Operator Access
LLGP enables, as one of its use cases, a DEX agnostic limit order system. To facilitate this limit order protocol use case, Market Makers (including Block Builders as well as more traditional MMs) will be able to access this order flow exclusively. This drives more volume and faster transaction settlement by providing incentive—aligned market making operations with users submitted transactions.
LLGP provides the ability to submit transactions without specifying a base fee in the transaction and using an ERC20 to pay for the entire transaction.
For Sushi Guard, the backrunning function in the router can be applied once the Sushi Aggregator is in production and RPC services for those networks are live.
Below is a breakdown of the mechanism audit/review done by 20squares. This provides the supporting information necessary for Market Makers to be able to take on the risk for operating in LLGP.
20Squares Mechanism Audit
Our models are written in a custom DSL compiled to haskell
. Here we give a brief description of how our software works.
The building blocks
The basic building block of our model is called open game, and can be thought of as a gametheoretic lego brick. This may represent a player, a nature draw, a payoff matrix or a complex combination of these elements. It has the following form:
gameName variables = [opengame
inputs : a;
feedback : b;
::
inputs : a';
feedback : b';
operation : content;
outputs : s';
returns : t';
::
outputs : s;
returns : t;
]
We can imagine this block as a box with 4 wires on its outside, on which travels information marked as:

inputs
, data that gets fed into the game (e.g. a player receiving information from a context). 
outputs
, data that the game feeds to the outside world (e.g. a player communicating a choice to another player). 
returns
, the returns of a player actions, which are usually directly fed to a function calculating payoffs.  The
feedback
wire which sends information back in time. If, intuitively,returns
represents the returns on a player action, one could imagine it as ‘information that an agents receive from the future’.feedback
is the dual analog of that: If a given piece of information comes from the future, someone in the future must have been sent it to the past. For additional details about thefeedback
wire please refer to the relevant literature.
The ::
delimiters separate the outside from the inside of the box. As one can see, the interfaces inside are replicated. This is intentional as it allows for a notion of nesting. For instance, the situation depicted in the following picture:
Can be represented by the following code block:
gameName variables = [opengame
inputs : a, a';
feedback : b;
::
inputs : a';
feedback : ;
operation : SubGame1;
outputs : x;
returns : t';
inputs : a, x;
feedback : b;
operation : SubGame2;
outputs : s;
returns : t;
::
outputs : s;
returns : t,t';
]
In turn, Subgame1
and Subgame2
can be other games defined using the same DSL. Notice that the wire x
is internal and totally hidden from the ‘outside world’.
Exogenous parameters
An exogenous parameter is a given assumption that is not part of the model, and is fed to it externally. As such, it is treated by the model as a ‘fact’ that cannot really be modified. An example of exogenous parameter could be the market conditions at the time when a game is played.
Exogenous parameters are just defined as variables, as the field variables
in the previous code blocks testifes. These variables can in turn be fed as exogenous parameters to inside games, as in the following example:
gameName stock1Price stock2Price = [opengame
inputs : a, a';
feedback : b;
::
inputs : a';
feedback : ;
operation : SubGame1 stock1Price;
outputs : x;
returns : t';
inputs : a, x;
feedback : b;
operation : SubGame2 stock2Price;
outputs : s;
returns : t;
::
outputs : s;
returns : t,t';
]
Basic operations
In addition to the DSL defining the ‘piping rules’ between boxes, we provide some basic operations to populate a box, namely:
 A function, which just transforms the input in some output.
 A stochastic distribution, used to implement draws from nature.
 A strategic choice, which can be thought of as a function parametrized over strategies.
Branching
Another important operation we provide is called branching. This is useful in contexts where, say, a player choice determines which subgame is going to be played next.
Branching is represented using the operator +++
. So, for instance, if SubGame1
is defined as branch1 +++ branch2
, then we are modelling a situation where SubGame1
can actually evolve into two different games depending on input. As the input of a game can be the outcome of a strategic choice in some other game, this allows for flexible modelling of complex situations.
Graphically, branching can be represented by resorting to sheet diagrams, but as they are quite complicated to draw, this depiction is rarely used in practice.
Supplying strategies
As usual in classical game theory, a strategy conditions on the observables and assigns a (possibly randomized) action.
Every player who can make a decision in the game needs to be assigned a strategy. These individual strategies then get aggregated into a list representing the complete strategy for the whole game.
So, for instance, if our model consists of three subgames, a strategy for the whole model will just be a list:
`strGame1 :: strGame2 :: strGame3 :: Nil`.
Evaluating strategies
To evaluate strategies, it is enough to just run the main
function defined in Main.hs
. This is precisely what happens when we give the command stack run
. In turn, main
invokes functions defined in Analytics.hs
which define the right notion of equilibrium to check. If you want to change strategies on the fly, just open a REPL (Cf. Interactive Execution) and give the command main
.
You can make parametric changes or even define new strategies and/or notions of equilibrium by editing the relevant files (cf. File structure). Once you save your edits, giving :r
will recompile the code on the fly. Calling main
again will evaluate the changes.
Stochasticity
Our models are Bayesian by default, meaning that they allow for reasoning in probabilitic terms.
Practically, this is obtained by relying on the Haskell Stochastic Package, which employs monadic techniques.
A consequence of this is that deterministic strategic decisions (e.g. ‘player chooses option A’) must be lifted into the stochastic monad, getting thus transformed into their probabilistic equivalent (e.g. ‘of all the options available, player chooses A with probability 1’)
A practical example of this is the following:
strategyName
:: Kleisli
Stochastic
(Parameter1, Parameter2)
Decision
strategyName = pureAction Decision1
In the example above, the player observes some parameters (Parameter1
and Parameter2
in this particular case), and then must assign an action (in this case Decision1
).
pureAction
lifts the deterministic choice Decision1
to the corresponding concept in the probabilistic realm.
The upside of assuming this little amount of overhead is that switching from pure to mixed strategies can be easily done on the fly, without having to change the model beforehand.
Branching
As a word of caution notice that, in a game with branching, we need to provide a possible strategy for each branch. For example, suppose to have the following game:
 Player 1 can choose between option A and B;
 case A: Player 2 can choose between option A1 or A2;
 case B: Player 2 can choose between option B1 or B2;
Moreover, suppose that the payoffs are as follows:
 If Player1 chooses A, and then Player2 chooses A1, then both players get $100$.
 In any other case, both players get $0$.
In this game the best strategy is clearly (A,A1). Nevertheless, we need to supply a strategy for Player2 also in the ‘B’ branch: Even if Player1 will never rationally choose B, Player2 needs to be endowed with a clear choice between B1 and B2 in case this happens.
File structure
The model is composed of several files:

app/Main.hs
contains all the main ingredients already set up for a run. Executing it will execute equilibrium checking on some of the most interesting strategies we defined. We suggest to start from here to get a feel of how the model analysis works. 
Model.hs
is the file where the main model is defined. 
Components.hs
is where the subgames making up the whole model are defined. 
Payoffs.hs
is where the payoff functions used in every subgame are defined. We decided to keep them all in the same file to make tweaking and finetuning less dispersive. 
Strategies.hs
is where the strategies we want to test are defined. 
Parametrization.hs
defines the concrete parametrizations used for the analysis: e.g. intrinsic utility of Buyer’s transaction, fixed costs for initiating a LedgerHedger contract etc. 
Types.hs
is where we define the types of the decisions to be taken (e.g. $Wait$ or $Initiate$ a LedgerHedger contract) and the types ofHLContract
andTransaction
. Here we also define the types for payoffs, gas etc. These are all aliased toDouble
. 
ActionSpaces.hs
is mainly needed for technical typetransformations. It maps a player’s decision type into the type needed to be fed in the subsequent game. 
Analytics.hs
defines the equilibrium notion for each game we want to test. 
Diagnostics.hs
is the file detailing which and how much information we want to show when strategies are tested.
Relying on the DSL Primer, parsing the code structure should be a manageable task.
Moreover, the code is divided in two different branches:

main
contains the standard model, and runs analytics with the parameters provided in the LedgerHedger paper. 
eqbreaking
instead parametrizes the riskaversity in the utility functions, and runs stress tests to check under which conditions the equilibrium breaks.
Analytics
Now, we switch focus on analytics, which we defined as the set of techniques we employ to verify if and when a supplied results in an equilibrium. The notion of equilibrium we rely upon is the one of Nash equilibrium, which intuitively describes a situation where, for each player, unilaterally deviating from the chosen strategy results in a loss.
Reading the analytics
Analytics in our model are quite straightforward. In case a game is in equilibrium, the terminal will print Strategies are in equilibrium
.
For games with branching, there will also be a NOTHING CASE
. To understand this, consider a game (call it First Game
) that can trigger two different subgames (Subgame branch 1
, Subgame branch 2
, respectively) depending on the player’s choice. Analytics would read like this:
Game name
First Game:
Strategies are in equilibrium
Subgame branch 1:
NOTHING CASE
Subgame branch 2:
Strategies are in equilibrium
Here NOTHING CASE
signifies that the choice provided by the player results in not visiting Subgame branch 1
, which is thus never played in this senario: Evidently, the choice made by the player in First Game
resulting in the play continuing on Subgame branch 2
.
On the contrary, analytics become more expressive when the game is not in equilibrium. In this case, the engine will suggest a more profitable deviation by displaying the following prompt:
Strategies are NOT in equilibrium. Consider the following profitable deviations:
Player:
Optimal Move:
Current Strategy:
Optimal Payoff:
Current Payoff:
Observable State:
other game
No more information
Observable State
contains a dump of all the game parameters that are currenlty observable by all players. This is usually a lot of information, mainly useful for debugging purposes. All the other field names are pretty much selfdescribing.
Strategies employed in the analysis
Our analysis is focused on understanding when the targeted equilibrium  where the LedgerHedger contract gets initiated and later published by Buyer as well as accepted and confirmed by Seller  can be supported. Concretely, this means we employ the following strategies:
  Buyer: initiate contract strategy
initiateStrategyBuyerTarget
:: Kleisli
Stochastic
(Transaction, HLContract, GasPrice)
(InitialDecisionBuyer HLContract)
initiateStrategyBuyerTarget =
Kleisli (\(_,contract,_) > playDeterministically $ Initiate contract)
  Buyer: publish strategy if no LH
noLHPublishStrategyTarget
:: Kleisli
Stochastic
(Transaction, GasPrice)
(PublishDecision Double)
noLHPublishStrategyTarget = Kleisli (\(tx,_ ) > playDeterministically $ Publish (gasAllocTX tx))
  Seller: accept decision
acceptStrategyTarget
:: Kleisli
Stochastic
(Transaction, HLContract, GasPrice)
AcceptDecisionSeller
acceptStrategyTarget = pureAction Accept
  Buyer: publish strategy if recoup
recoupPublishTarget
:: Kleisli
Stochastic
(Transaction, GasPrice)
(PublishDecision Double)
recoupPublishTarget = Kleisli (\(tx,_ ) > playDeterministically $ Publish (gasAllocTX tx))
  Buyer: recoup strategy buyer
recoupStrategyTarget
:: Kleisli
Stochastic
(Transaction, HLContract, GasPrice, GasPrice)
RecoupDecisionBuyer
recoupStrategyTarget = pureAction Refund
  Buyer: publish strategy part 1 if LH
lhPublishStrategyPart1Target
:: Kleisli
Stochastic
GasPrice
(PublishDecision Double)
lhPublishStrategyPart1Target = pureAction $ Publish 0.0
  Buyer: publish strategy part 2 if LH
lhPublishStrategyPart2Target
:: Kleisli
Stochastic
(GasPrice, Transaction, PublishDecision a1)
(PublishDecision Gas)
lhPublishStrategyPart2Target =
Kleisli
(\(pi,tx,publishDecision) >
case publishDecision of
NoOp > playDeterministically NoOp
Publish _ > playDeterministically $ Publish $ gasAllocTX tx)
  Seller: fulfill strategy
fulfillStrategyTarget
:: Kleisli
Stochastic
(Transaction, HLContract, GasPrice,GasPrice, Gas)
FulfillDecisionSeller
fulfillStrategyTarget = pureAction Confirm
  Seller: noFulfill strategy
noFulfillStrategyTarget
:: Kleisli
Stochastic
(Transaction, HLContract, GasPrice, GasPrice)
FulfillDecisionSeller
noFulfillStrategyTarget = pureAction Exhaust
  Buyer: publish strategy if no fulfill
nofulfillPublishTarget
:: Kleisli
Stochastic
(Transaction, GasPrice)
(PublishDecision Double)
nofulfillPublishTarget = Kleisli (\(tx,_ ) > playDeterministically $ Publish (gasAllocTX tx))
As detailed in File structure, the strategies above reside in Strategies.hs
. For more information about how to supply strategies and/or how to make changes, please refer to the section Supplying Strategies.
Running the analytics
As already stressed in Evaluating strategies, there are two main ways to run strategies. In the Normal execution mode, one just needs to give the command stack run
. This command will execute a predefined battery of strategies using the parameters predefined in the source code. These parameters can be varied as one pleases. Once this is done and the edits are saved, stack run
will automatically recompile the code and run the simulation with the new parameter set.
In the Interactive execution mode, the users accesses the repl via the command stack ghci
. Here one can run single functions by just calling them with the relevant parameters, as in:
functionName parameters
In particular, calling the function main
in interactive mode will result in the same behavior of calling stack run
in normal mode. Again, editing the source code and then hitting :r
will trigger recompilation on the fly.
Replicating the LedgerHedger paper results
These results can be found in the main
branch (see subsection File structure for more information).
To replicate the results highlighted in the LedgerHedger paper, we instantiated the model with the following parameters (the instantiation can be found in Parameters.hs
):
Parameter  Name in the paper  Meaning  Value 

buyerWealth 
$W^{init}_{Buyer}$  Initial wealth of Buyer  $10^9$ 
sellerWealth 
$W^{init}_{Seller}$  Initial wealth of Seller  $10^9$ 
collateral 
$$col$$  The collateral Seller must pay to accept LH contract.  $10^9$ 
piInitial 
$\pi_{initial}$  Initial gas price  $100$ 
piContract 
$\pi_{contract}$  The price at which Buyer buys gasAllocTX from Seller in the LH contract. 
$100$ 
payment 
$payment$  The ampount Buyer pays to Seller in LH  gasAllocTX * piContract 
epsilon 
$\epsilon$  Technical parameter to disincentivize unwanted behavior from Seller.  $1$ 
gasInitiation 
$g_{init}$  Cost of opening a LH contract.  $0.1 \cdot 10^6$ 
gasAccept 
$g_{accept}$  Cost of accepting a LH contract.  $75 \cdot 10^3$ 
gasDone 
$g_{done}$  Cost of closing a LH contract.  $20 \cdot 10^3$ 
gasAllocTX 
$g_{alloc}$  Gas reserved in the LH contract.  $5 \cdot 10^6$ 
gasPub 
$g_{pub}$  Gas size of the TX if issued at current market price.  $5 \cdot 10^6$ 
These parameters were directly pulled from Sec. 6 of the LedgerHedger paper. As for the utility functions, we again followed what the authors did by instantiating the utility functions for both Buyer and Seller to be first $log(x)$ and then $\sqrt(x)$. These function represent riskaversity.
Moreover, as specified in the section Assumptions made explicit, we had to postulate an explicit utility for the transaction that Buyer wants to issue. This is represented by the parameter utilityFromTX
, which has been set to $10^9$.
As for the future price distribution, we defined it in csv format in the file /probability/distribution.csv
(we also provide a constant distribution, /probability/distributionOneElement.csv
for debugging reasons). As the distribution is input through this external file, any other distribution (e.g. based on actual data) can be used. distribution.csv
is a normal distribution centered around the initial gas price. Again, we used the standard deviations suggested in the paper.
Moreover, we defined testActionSpaceGasPub
, representing the range in which $g_{pub}$ can swing. $g_{pub}$ is the gas consumed if Buyer decides to issue the transaction at market price (for more information see Ambiguous Buyer behavior). In practice, we followed the paper and equated $g_{pub}$ and $g_{alloc}$, meaning that Buyer is using LedgerHedger to reserve the precise amount of gas needed to execute the transaction.
As we already stated in Summary  Analytics results, we found that the equilibrium with this parameters is quite brittle, and heavily relying on both the riskaversity of both Buyer and Seller and on the shape of the probability distribution. By this, we mean that the utility gain in using the LedgerHedger is really small, and even a small variation in the given parameters can result in equilibrium breaking.
The reason why the protocol is so sensible is that, Sellerside, the usage fees (gasAccept
, gasDone
) are quite high. This immediately disincentivates Seller to use the protocol, as in terms of raw payoffs doing so results in a loss.
Similarly, Buyerside, the presence of a usage fee (gasInitiation
) and the cost defined bypayment
disincentivate Buyer to use the protocol.
This ‘operating at a loss’ result is counterbalanced uniquely by the accentuated concavity of the utility functions provided. In a nutshell, both players are so riskaverse that they are willing to pay these huge premiums to protect themselves.
Another reason why LedgerHedger is so sensible is also that players are fundamentally unbiased with respect to future gas price: it can go up or down, for both players, with equal probability. We are quite sure the situation would look different if players had private information available about future price distribution. Imagine the current situation:
 Seller believes price will go down in the between blocks $start$ and $end$.
 On the contrary, Buyer believes that price will go up within the same block interval.
In this scenario, both Players will be much more willing to use LedgerHedger, albeit for opposite reasons. Importantly, depending on how skewed these beliefs are, we may dispense of riskaversity all together: Even for a riskloving player hedging would make sense if the player strongly believed that prices would swing towards an unfavorable direction.
Other analyses
We ran also some other analyses these can be found in the eqbreaking
branch (see subsection File structure for more information).
First of all, and unsurprisingly, we found that running the model with the following parameters results in a much bigger utility:
Parameter  Name in the paper  Meaning  Value 

buyerWealth 
$W^{init}_{Buyer}$  Initial wealth of Buyer  $10^9$ 
sellerWealth 
$W^{init}_{Seller}$  Initial wealth of Seller  $10^9$ 
collateral 
$$col$$  The collateral Seller must pay to accept LH contract.  $10^9$ 
piInitial 
$\pi_{initial}$  Initial gas price  $100$ 
piContract 
$\pi_{contract}$  The price at which Buyer buys gasAllocTX from Seller in the LH contract. 
$100$ 
payment 
$payment$  The ampount Buyer pays to Seller in LH  gasAllocTX * piContract 
epsilon 
$\epsilon$  Technical parameter to disincentivize unwanted behavior from Seller.  $1$ 
gasInitiation 
$g_{init}$  Cost of opening a LH contract.  $0$ 
gasAccept 
$g_{accept}$  Cost of accepting a LH contract.  $0$ 
gasDone 
$g_{done}$  Cost of closing a LH contract.  $0$ 
gasAllocTX 
$g_{alloc}$  Gas reserved in the LH contract.  $5 \cdot 10^6$ 
gasPub 
$g_{pub}$  Gas size of the TX if issued at current market price.  $5 \cdot 10^6$ 
This represents the scenario where we dispense of the platform fees altogether, and pay for gasAllocTX
exactly the current price. Most likely, this scenario hadn’t been considered in the original paper as the original work was meant to be part of some onchain infrastructure. As such, what we call ‘platform fees’ would just be unavoidable smart contract execution costs.
Keeping everything fixed, we then turned piContract
into a parameter. In the case when both player’s utility is set to be $\sqrt x$, we verified that in this setting the equilibrium is solid within the bound:
$$98 \leq \mathtt{piContract} \leq 102$$
This represents the maximum and minimum piContract
parameters within which both Buyer and Seller judge using LedgerHedger convenient. This result aligns perfectly with the one highlighted in the LedgerHedger paper, figure 4.
Having verified this, we used both the original paper parameters and the ‘zero fees’ parameters to run a multivariate analysis on player’s risk aversity. In practice, we generalized the $\sqrt x$ function, up to now being used by both players, to the couple of functions
$$\Large x^{\frac{1}{y_{\mathbf{Buyer}}}} \qquad x^{\frac{1}{y_{\mathbf{Seller}}}}$$
The concavity/convexity of these functions is dependent on the value of $y$, as shown in the following chart:
Thus, $y_{\mathbf{Buyer}}$ and $y_{\mathbf{Seller}}$ represent the riskaversity of both players, respectively. Positive values $< 1$ represent risklove, $1$ represents riskneutrality, while values $> 1$ signal riskaversity.
Keeping all parameters fixed, the shaded region in the following graphs represents where the equilibrium holds for different riskaversity ranges.
At $\texttt{piContract} = 98$, we get the following graph:
The blue region represents the ‘zero fees’ scenario, whereas the red region represents the fees as in the LedgerHedger paper. As on can see, the red region is strictly contained in the blue one. We have:
$$ \begin{alignat*}{2} \text{\color{blue} Zero fees scenario:} \qquad 0.68 \leq y_{\mathbf{Buyer}} \qquad 1.92 \leq y_{\mathbf{Seller}}\ \text{\color{red} Paper fees scenario:} \qquad 0.94 \leq y_{\mathbf{Buyer}} \qquad 7.09 \leq y_{\mathbf{Seller}} \end{alignat*} $$
This is compatible with the idea that the lower the fees the less risk averse players must be to judge the use of LedgerHedger convenient.
Setting $\texttt{piContract} = 99$, we get the following graph:
$$ \begin{alignat*}{2} \text{\color{blue} Zero fees scenario:} \qquad 0.79 \leq y_{\mathbf{Buyer}} \qquad 1.39 \leq y_{\mathbf{Seller}}\ \text{\color{red} Paper fees scenario:} \qquad 1.16 \leq y_{\mathbf{Buyer}} \qquad 2.93 \leq y_{\mathbf{Seller}} \end{alignat*} $$
For $\texttt{piContract} = 100$:
$$ \begin{alignat*}{2} \text{\color{blue} Zero fees scenario:} \qquad 0.94 \leq y_{\mathbf{Buyer}} \qquad 1.08 \leq y_{\mathbf{Seller}}\ \text{\color{red} Paper fees scenario:} \qquad 1.50 \leq y_{\mathbf{Buyer}} \qquad 1.85 \leq y_{\mathbf{Seller}} \end{alignat*} $$
For $\texttt{piContract} = 101$:
$$ \begin{alignat*}{2} \text{\color{blue} Zero fees scenario:} \qquad 1.16 \leq y_{\mathbf{Buyer}} \qquad 0.89 \leq y_{\mathbf{Seller}}\ \text{\color{red} Paper fees scenario:} \qquad 2.15 \leq y_{\mathbf{Buyer}} \qquad 1.35 \leq y_{\mathbf{Seller}} \end{alignat*} $$
For $\texttt{piContract} = 102$:
$$ \begin{alignat*}{3} \text{\color{blue} Zero fees scenario:} \qquad 1.50 \leq y_{\mathbf{Buyer}} \qquad 0.75 \leq y_{\mathbf{Seller}}\ \text{\color{red} Paper fees scenario:} \qquad 3.77 \leq y_{\mathbf{Buyer}} \qquad 1.06 \leq y_{\mathbf{Seller}} \end{alignat*} $$
As one can see, as piContract
increases the shaded regions migrate to the lowerright end. Again, this makes sense: As the price goes higher, Buyer is paying more and more for gasAllocTX
with respect to current price. This entails that Buyer should be more riskaverse to deemm this advantageous, and hence the region moves further to the right on the Buyer axis. Specularly, Seller is receiving an increasingly better offer compared to the current price, lowering the necessity for riskaversity. As such, the region grows closer to the Seller axis.
Sanity checks
In addition to this, we performed some sanity checks: Using the constant distribution (see Replicating the LedgerHedger paper results for more information), we verified the following things:
 Setting
epsilon
to 0 results, in the ‘zero fees’ case, in equilibrium for any choice of riskaversity for both players. This makes sense: Using the protocol results in zero extra costs. Moreover, both players know with certainty that the future gas price won’t change. Hence, riskaversity does not matter.  With any other choice of
epsilon
and any other fees, the equilibrium breaks for any choice of riskaversity, for both players. Again, this makes sense: Both players know with certainty that the future gas price won’t change, so payng any extra fee to hedge is disadvantageous.
Interactive perks
Finally, in the eqbreaking
branch there are more things one can do, the most important being that we expose a new function in Main.hs
that allows for better interactive queries. In interactive mode, giving:
interactiveMain piContract utilityParameterBuyer utilityParameterSeller
allows to run the LedgerHedger strategy on both the ‘zero fees’ and ‘paper fees’ scenarios, with customized piContract
and riskaversity parameters for both players. In this way, the user can verify equilibria for particular choices of values without the need to recompile.