Returns the address to which fees are minted.
behaviour feeTo of UniswapV2Factory
interface feeTo()
for all
To : address
storage
feeTo |-> To
iff
VCallValue == 0
returns To
Returns the address that is allowed to update feeTo
.
behaviour feeToSetter of UniswapV2Factory
interface feeToSetter()
for all
Setter : address
storage
feeToSetter |-> Setter
iff
VCallValue == 0
returns Setter
Returns the exchange address for a given pair of tokens. In practice, token
arguments can be provided in any order (A, B)
or (B, A)
because
createPair
stores two records - one for each pairing.
behaviour getPair of UniswapV2Factory
interface getPair(address tokenA, address tokenB)
for all
Pair : address
storage
getPair[tokenA][tokenB] |-> Pair
iff
VCallValue == 0
returns Pair
allPairs
is an array which stores the address of every exchange created
using the Factory
.
pair0
is a concrete value, representing the storage key of the first
array element (the keccak hash of slot 3). Subsequent array elements are stored
at an offset from this value: pair0 + index
.
The maximum number of addresses that can be stored by the allPairs
array
before overflow is (maxUInt256 - pair0) + 1
.
We do not currently specify the behaviour of the array after overflow has occured.
behaviour allPairs of UniswapV2Factory
interface allPairs(uint256 index)
for all
Pair : address
Length : uint256
storage
allPairs.length |-> Length
allPairs[index] |-> Pair
iff
Length > index
VCallValue == 0
if
// Ignore array overflow
Length <= maxPairs
returns Pair
Returns the total number of exchange pairs created by this factory by querying the
length of the allPairs
array.
behaviour allPairsLength of UniswapV2Factory
interface allPairsLength()
for all
Length : uint256
storage
allPairs.length |-> Length
iff
VCallValue == 0
returns Length
The current fee setter can appoint a new fee setter.
behaviour setFeeToSetter of UniswapV2Factory
interface setFeeToSetter(address usr)
for all
Setter : address
storage
feeToSetter |-> Setter => usr
iff
VCallValue == 0
CALLER_ID == Setter
The current fee setter can appoint a new fee recipient.
behaviour setFeeTo of UniswapV2Factory
interface setFeeTo(address usr)
for all
FeeTo : address
Setter : address
storage
feeToSetter |-> Setter
feeTo |-> FeeTo => usr
iff
VCallValue == 0
CALLER_ID == Setter
createPair
allows anyone to create a new instance of the UniswsapV2Pair
exchange contract by providing a pair of token addresses.
Token address arguments are not valid if equal to zero, or if they are equal to each other.
The address of the new exchange will be constant for any given token pair, regardless of the order in which they are provided.
The address of every new exchange pair is added to the allPairs
array, which is
primarily used by the contract to provide a count of all created pairs. The
maximum number of exchange pairs which can be created before overflowing the array
is:
87,903,029,871,075,914,254,377,627,908,054,574,944,891,091,886,930,582,284,385,770,809,450,030,037,083
We don't specify the behaviour of createPair
after array overflow.
behaviour createPair-lt of UniswapV2Factory
interface createPair(address tokenA, address tokenB)
for all
Pair : address UniswapV2Pair
Length : uint256
Domain_separator : uint256
Empty : address
Any : address
creates storage Pair
// Set during contract creation
#UniswapV2Pair.lockState |-> 1
#UniswapV2Pair.DOMAIN_SEPARATOR |-> Domain_separator
// Set via initialize
#UniswapV2Pair.factory |-> ACCT_ID
#UniswapV2Pair.token0 |-> tokenA
#UniswapV2Pair.token1 |-> tokenB
storage
#UniswapV2Factory.getPair[tokenA][tokenB] |-> Empty => Pair
#UniswapV2Factory.getPair[tokenB][tokenA] |-> 0 => Pair
#UniswapV2Factory.allPairs[Length] |-> Any => Pair
#UniswapV2Factory.allPairs.length |-> Length => Length + 1
gas
( (#if (notBool ( Junk_5 ==K 0 ) ) #then 0 #else 4200 #fi) +Int \
( (#if ( ( Junk_5 ==K 0 ) andBool ( Junk_5 ==K 0 ) ) #then 15000 #else 0 #fi) +Int \
( (#if (notBool ( Junk_6 ==K 0 ) ) #then 0 #else 4200 #fi) +Int \
( (#if ( ( Junk_6 ==K 0 ) andBool ( Junk_6 ==K 0 ) ) #then 15000 #else 0 #fi) +Int \
( (#if ( ( Length ==K ( Length +Int 1 ) ) orBool (notBool ( Junk_8 ==K Length ) ) ) #then 0 #else 4200 #fi) +Int \
( (#if ( ( Junk_8 ==K 0 ) andBool (notBool ( ( Length ==K ( Length +Int 1 ) ) orBool (notBool ( Junk_8 ==K Length ) ) ) ) ) #then 15000 #else 0 #fi) +Int \
( (#if ( ( Any ==K Pair ) orBool (notBool ( Junk_7 ==K Any ) ) ) #then 0 #else 4200 #fi) +Int \
( (#if ( ( Junk_7 ==K 0 ) andBool (notBool ( ( Any ==K Pair ) orBool (notBool ( Junk_7 ==K Any ) ) ) ) ) #then 15000 #else 0 #fi) +Int 2414776 ) ) ) ) ) ) ) )
iff
tokenA =/= 0
tokenA =/= tokenB
Empty == 0
VCallValue == 0
if
// The new exchange address
Pair == #newAddr( \
ACCT_ID, \
keccak( \
#take(20, #asByteStack(tokenA <<Int 96)) ++ \
#take(20, #asByteStack(tokenB <<Int 96)) \
), \
UniswapV2Pair_bin \
)
// The domain separator is set when the new
// exchange is created
Domain_separator == keccakIntList( \
Constants.EIP712Domain \
keccak(#parseByteStackRaw("Uniswap V2")) \
keccak(#parseByteStackRaw("1")) \
VChainId \
Pair \
)
// Ignore array overflow
Length <= maxPairs
tokenA < tokenB
VCallDepth < 1024
calls
UniswapV2Pair.initialize
returns Pair
behaviour createPair-gt of UniswapV2Factory
interface createPair(address tokenA, address tokenB)
for all
Pair : address UniswapV2Pair
Length : uint256
Domain_separator : uint256
Empty : address
Any : address
creates storage Pair
// Set during contract creation
#UniswapV2Pair.lockState |-> 1
#UniswapV2Pair.DOMAIN_SEPARATOR |-> Domain_separator
// Set via initialize
#UniswapV2Pair.factory |-> ACCT_ID
#UniswapV2Pair.token0 |-> tokenB
#UniswapV2Pair.token1 |-> tokenA
storage
#UniswapV2Factory.getPair[tokenB][tokenA] |-> Empty => Pair
#UniswapV2Factory.getPair[tokenA][tokenB] |-> 0 => Pair
#UniswapV2Factory.allPairs[Length] |-> Any => Pair
#UniswapV2Factory.allPairs.length |-> Length => Length + 1
gas
( (#if (notBool ( Junk_5 ==K 0 ) ) #then 0 #else 4200 #fi) +Int \
( (#if ( ( Junk_5 ==K 0 ) andBool ( Junk_5 ==K 0 ) ) #then 15000 #else 0 #fi) +Int \
( (#if (notBool ( Junk_6 ==K 0 ) ) #then 0 #else 4200 #fi) +Int \
( (#if ( ( Junk_6 ==K 0 ) andBool ( Junk_6 ==K 0 ) ) #then 15000 #else 0 #fi) +Int \
( (#if ( ( Length ==K ( Length +Int 1 ) ) orBool (notBool ( Junk_8 ==K Length ) ) ) #then 0 #else 4200 #fi) +Int \
( (#if ( ( Junk_8 ==K 0 ) andBool (notBool ( ( Length ==K ( Length +Int 1 ) ) orBool (notBool ( Junk_8 ==K Length ) ) ) ) ) #then 15000 #else 0 #fi) +Int \
( (#if ( ( Any ==K Pair ) orBool (notBool ( Junk_7 ==K Any ) ) ) #then 0 #else 4200 #fi) +Int \
( (#if ( ( Junk_7 ==K 0 ) andBool (notBool ( ( Any ==K Pair ) orBool (notBool ( Junk_7 ==K Any ) ) ) ) ) #then 15000 #else 0 #fi) +Int 2414786 ) ) ) ) ) ) ) )
iff
tokenB =/= 0
tokenA =/= tokenB
Empty == 0
VCallValue == 0
if
// The new exchange address
Pair == #newAddr( \
ACCT_ID, \
keccak( \
#take(20, #asByteStack(tokenB <<Int 96)) ++ \
#take(20, #asByteStack(tokenA <<Int 96)) \
), \
UniswapV2Pair_bin \
)
// The domain separator is set when the new
// exchange is created
Domain_separator == keccakIntList( \
Constants.EIP712Domain \
keccak(#parseByteStackRaw("Uniswap V2")) \
keccak(#parseByteStackRaw("1")) \
VChainId \
Pair \
)
// Ignore array overflow
Length <= maxPairs
tokenB < tokenA
VCallDepth < 1024
calls
UniswapV2Pair.initialize
returns Pair
behaviour MINIMUM_LIQUIDITY of UniswapV2Pair
interface MINIMUM_LIQUIDITY()
iff
VCallValue == 0
returns 1000
behaviour factory of UniswapV2Pair
interface factory()
for all
Factory : address
storage
factory |-> Factory
iff
VCallValue == 0
returns Factory
behaviour token0 of UniswapV2Pair
interface token0()
for all
Token0 : address
storage
token0 |-> Token0
iff
VCallValue == 0
returns Token0
behaviour token1 of UniswapV2Pair
interface token1()
for all
Token1 : address
storage
token1 |-> Token1
iff
VCallValue == 0
returns Token1
behaviour price0CumulativeLast of UniswapV2Pair
interface price0CumulativeLast()
for all
Price0 : uint256
storage
price0CumulativeLast |-> Price0
iff
VCallValue == 0
returns Price0
behaviour price1CumulativeLast of UniswapV2Pair
interface price1CumulativeLast()
for all
Price1 : uint256
storage
price1CumulativeLast |-> Price1
iff
VCallValue == 0
returns Price1
behaviour kLast of UniswapV2Pair
interface kLast()
for all
KLast : uint256
storage
kLast |-> KLast
iff
VCallValue == 0
returns KLast
behaviour getReserves of UniswapV2Pair
interface getReserves()
for all
Reserve0 : uint112
Reserve1 : uint112
BlockTimestampLast : uint32
storage
reserve0_reserve1_blockTimestampLast |-> #WordPackUInt112UInt112UInt32(Reserve0, Reserve1, BlockTimestampLast)
iff
VCallValue == 0
returns Reserve0 : Reserve1 : BlockTimestampLast
behaviour initialize of UniswapV2Pair
interface initialize(address _token0, address _token1)
for all
Factory : address
storage
factory |-> Factory
token0 |-> _ => _token0
token1 |-> _ => _token1
iff
CALLER_ID == Factory
VCallValue == 0
The burn
function burns all the liquidity tokens owned by the pair
contract and sends a proportional amount of each token in the pair to the
address specified by to
.
Sending liquidity tokens to the contract and calling burn
should happen
atomically, otherwise the tokens can be withdrawn by a third-party with a call
to skim
.
burn
also optionally generates protocol fees, if the value of feeTo
is not
0
. The fees are sent to the feeTo
address.
This function requires that the UniswapV2Pair
contract's balance of the
two pair tokens does not exceed MAX_UINT_112 - 1
.
There are 4 specs for burn
: 2 main variants, based on whether a fee is minted;
and 2 subvariants, based on whether KLast
is zero. The subvariants are
there for performance reasons, to ensure the proofs run in a reasonable amount
of time, without causing timeouts or out-of-memory errors.
There is an additional variant, back
, which explores the case where the to
argument to burn
is the address of the pair contract.
feeMinted
noFee
kLastZero
kLastNonZero
back
There are other possible variants which we're not exploring here:
totalSupply == 0
feeTo == CALLER_ID
The totalSupply == 0
variant would cover the case when burn
is called when
no liquidity has been minted yet. In that case, there will be a division by
zero, which will cause the caller to lose their gas. This scenario is relatively
rare and relatively low-risk, so it hasn't been prioritized.
The feeTo == CALLER_ID
and to == CALLER_ID
variants would require a separate
spec to work around storage collisions. Because these variants do not
meaningfully affect the security of the system, they haven't been prioritized.
behaviour burn-feeMinted of UniswapV2Pair
interface burn(address to)
for all
Reserve0 : uint112
Reserve1 : uint112
BlockTimestampLast : uint32
Token0 : address UniswapV2Pair
Token1 : address UniswapV2Pair
Balance : uint256
Balance_FeeTo : uint256
Balance0 : uint112
Balance1 : uint112
Balance0_To : uint256
Balance1_To : uint256
FeeTo : address
Factory : address UniswapV2Factory
KLast : uint256
Supply : uint256
Price0 : uint256
Price1 : uint256
LockState : uint256
storage
reserve0_reserve1_blockTimestampLast |-> \
#WordPackUInt112UInt112UInt32(Reserve0, Reserve1, BlockTimestampLast) => \
#WordPackUInt112UInt112UInt32((Balance0 - Amount0), (Balance1 - Amount1), BlockTimestamp)
token0 |-> Token0
token1 |-> Token1
factory |-> Factory
kLast |-> KLast => (Balance0 - Amount0) * (Balance1 - Amount1)
totalSupply |-> Supply => (Supply + Fee) - Balance
balanceOf[FeeTo] |-> Balance_FeeTo => Balance_FeeTo + Fee
balanceOf[ACCT_ID] |-> Balance => 0
price0CumulativeLast |-> Price0 => \
#if (TimeElapsed > 0) and (Reserve0 =/= 0) and (Reserve1 =/= 0) #then \
chop(PriceIncrease0 + Price0) \
#else \
Price0 \
#fi
price1CumulativeLast |-> Price1 => \
#if (TimeElapsed > 0) and (Reserve0 =/= 0) and (Reserve1 =/= 0) #then \
chop(PriceIncrease1 + Price1) \
#else \
Price1 \
#fi
lockState |-> LockState => LockState
storage Token0
balanceOf[ACCT_ID] |-> Balance0 => Balance0 - Amount0
balanceOf[to] |-> Balance0_To => Balance0_To + Amount0
storage Token1
balanceOf[ACCT_ID] |-> Balance1 => Balance1 - Amount1
balanceOf[to] |-> Balance1_To => Balance1_To + Amount1
storage Factory
feeTo |-> FeeTo
returns Amount0 : Amount1
where
FeeOn := FeeTo =/= 0
RootK := #sqrt(Reserve0 * Reserve1)
RootKLast := #sqrt(KLast)
Fee := (Supply * (RootK - RootKLast)) / ((RootK * 5) + RootKLast)
Amount0 := (Balance * Balance0) / (Supply + Fee)
Amount1 := (Balance * Balance1) / (Supply + Fee)
BlockTimestamp := TIME mod pow32
TimeElapsed := (BlockTimestamp -Word BlockTimestampLast ) mod pow32
PriceIncrease0 := ((pow112 * Reserve1) / Reserve0) * TimeElapsed
PriceIncrease1 := ((pow112 * Reserve0) / Reserve1) * TimeElapsed
iff in range uint256
// _mintFee
RootK
RootKLast
RootK - RootKLast
Supply * (RootK - RootKLast)
RootK * 5
(RootK * 5) + RootKLast
Fee
Supply + Fee
Balance_FeeTo + Fee
// burn
Balance * Balance0
Balance * Balance1
Amount0
Amount1
Supply - Balance
Balance0_To + Amount0
Balance1_To + Amount1
iff in range uint112
Balance0 - Amount0
Balance1 - Amount1
iff
Amount0 > 0
Amount1 > 0
LockState == 1
VCallValue == 0
VCallDepth < 1024
if
// fee is minted
FeeTo =/= 0
KLast =/= 0
RootK > RootKLast
Fee > 0
// avoid storage collisions
to =/= ACCT_ID
FeeTo =/= ACCT_ID
// avoid division by zero
Supply =/= 0
calls
UniswapV2Pair.balanceOf
UniswapV2Factory.feeTo
behaviour burn-noFee-kLastNonZero of UniswapV2Pair
interface burn(address to)
for all
Reserve0 : uint112
Reserve1 : uint112
BlockTimestampLast : uint32
Token0 : address UniswapV2Pair
Token1 : address UniswapV2Pair
Balance : uint256
Balance_FeeTo : uint256
Balance0 : uint112
Balance1 : uint112
Balance0_To : uint256
Balance1_To : uint256
FeeTo : address
Factory : address UniswapV2Factory
KLast : uint256
Supply : uint256
Price0 : uint256
Price1 : uint256
LockState : uint256
storage
reserve0_reserve1_blockTimestampLast |-> \
#WordPackUInt112UInt112UInt32(Reserve0, Reserve1, BlockTimestampLast) => \
#WordPackUInt112UInt112UInt32((Balance0 - Amount0), (Balance1 - Amount1), BlockTimestamp)
token0 |-> Token0
token1 |-> Token1
factory |-> Factory
kLast |-> KLast => 0
totalSupply |-> Supply => Supply - Balance
balanceOf[FeeTo] |-> Balance_FeeTo
balanceOf[ACCT_ID] |-> Balance => 0
price0CumulativeLast |-> Price0 => \
#if (TimeElapsed > 0) and (Reserve0 =/= 0) and (Reserve1 =/= 0) #then \
chop(PriceIncrease0 + Price0) \
#else \
Price0 \
#fi
price1CumulativeLast |-> Price1 => \
#if (TimeElapsed > 0) and (Reserve0 =/= 0) and (Reserve1 =/= 0) #then \
chop(PriceIncrease1 + Price1) \
#else \
Price1 \
#fi
lockState |-> LockState => LockState
storage Token0
balanceOf[ACCT_ID] |-> Balance0 => Balance0 - Amount0
balanceOf[to] |-> Balance0_To => Balance0_To + Amount0
storage Token1
balanceOf[ACCT_ID] |-> Balance1 => Balance1 - Amount1
balanceOf[to] |-> Balance1_To => Balance1_To + Amount1
storage Factory
feeTo |-> FeeTo
returns Amount0 : Amount1
where
Amount0 := (Balance * Balance0) / Supply
Amount1 := (Balance * Balance1) / Supply
BlockTimestamp := TIME mod pow32
TimeElapsed := (BlockTimestamp -Word BlockTimestampLast ) mod pow32
PriceIncrease0 := ((pow112 * Reserve1) / Reserve0) * TimeElapsed
PriceIncrease1 := ((pow112 * Reserve0) / Reserve1) * TimeElapsed
iff in range uint256
Balance * Balance0
Balance * Balance1
Amount0
Amount1
Supply - Balance
Balance0_To + Amount0
Balance1_To + Amount1
iff in range uint112
Balance0 - Amount0
Balance1 - Amount1
iff
Amount0 > 0
Amount1 > 0
LockState == 1
VCallValue == 0
VCallDepth < 1024
if
// fee is not minted
FeeTo == 0
// KLast is non-zero
KLast =/= 0
// avoid storage collisions
to =/= ACCT_ID
FeeTo =/= ACCT_ID
// avoid division by zero
Supply =/= 0
calls
UniswapV2Pair.balanceOf
UniswapV2Factory.feeTo
behaviour burn-noFee-kLastZero of UniswapV2Pair
interface burn(address to)
for all
Reserve0 : uint112
Reserve1 : uint112
BlockTimestampLast : uint32
Token0 : address UniswapV2Pair
Token1 : address UniswapV2Pair
Balance : uint256
Balance_FeeTo : uint256
Balance0 : uint112
Balance1 : uint112
Balance0_To : uint256
Balance1_To : uint256
FeeTo : address
Factory : address UniswapV2Factory
KLast : uint256
Supply : uint256
Price0 : uint256
Price1 : uint256
LockState : uint256
storage
reserve0_reserve1_blockTimestampLast |-> \
#WordPackUInt112UInt112UInt32(Reserve0, Reserve1, BlockTimestampLast) => \
#WordPackUInt112UInt112UInt32((Balance0 - Amount0), (Balance1 - Amount1), BlockTimestamp)
token0 |-> Token0
token1 |-> Token1
factory |-> Factory
kLast |-> KLast => 0
totalSupply |-> Supply => Supply - Balance
balanceOf[FeeTo] |-> Balance_FeeTo
balanceOf[ACCT_ID] |-> Balance => 0
price0CumulativeLast |-> Price0 => \
#if (TimeElapsed > 0) and (Reserve0 =/= 0) and (Reserve1 =/= 0) #then \
chop(PriceIncrease0 + Price0) \
#else \
Price0 \
#fi
price1CumulativeLast |-> Price1 => \
#if (TimeElapsed > 0) and (Reserve0 =/= 0) and (Reserve1 =/= 0) #then \
chop(PriceIncrease1 + Price1) \
#else \
Price1 \
#fi
lockState |-> LockState => LockState
storage Token0
balanceOf[ACCT_ID] |-> Balance0 => Balance0 - Amount0
balanceOf[to] |-> Balance0_To => Balance0_To + Amount0
storage Token1
balanceOf[ACCT_ID] |-> Balance1 => Balance1 - Amount1
balanceOf[to] |-> Balance1_To => Balance1_To + Amount1
storage Factory
feeTo |-> FeeTo
returns Amount0 : Amount1
where
Amount0 := (Balance * Balance0) / Supply
Amount1 := (Balance * Balance1) / Supply
BlockTimestamp := TIME mod pow32
TimeElapsed := (BlockTimestamp -Word BlockTimestampLast ) mod pow32
PriceIncrease0 := ((pow112 * Reserve1) / Reserve0) * TimeElapsed
PriceIncrease1 := ((pow112 * Reserve0) / Reserve1) * TimeElapsed
iff in range uint256
Balance * Balance0
Balance * Balance1
Amount0
Amount1
Supply - Balance
Balance0_To + Amount0
Balance1_To + Amount1
iff in range uint112
Balance0 - Amount0
Balance1 - Amount1
iff
Amount0 > 0
Amount1 > 0
LockState == 1
VCallValue == 0
VCallDepth < 1024
if
// fee is not minted
FeeTo == 0
// KLast is zero
KLast =/= 0
// avoid storage collisions
to =/= ACCT_ID
FeeTo =/= ACCT_ID
// avoid division by zero
Supply =/= 0
calls
UniswapV2Pair.balanceOf
UniswapV2Factory.feeTo
behaviour burn-back-noFee-kLastNonZero of UniswapV2Pair
interface burn(address to)
for all
Reserve0 : uint112
Reserve1 : uint112
BlockTimestampLast : uint32
Token0 : address UniswapV2Pair
Token1 : address UniswapV2Pair
Balance : uint256
Balance_FeeTo : uint256
Balance0 : uint112
Balance1 : uint112
FeeTo : address
Factory : address UniswapV2Factory
KLast : uint256
Supply : uint256
Price0 : uint256
Price1 : uint256
LockState : uint256
storage
reserve0_reserve1_blockTimestampLast |-> #WordPackUInt112UInt112UInt32(Reserve0, Reserve1, BlockTimestampLast) => #WordPackUInt112UInt112UInt32(Balance0, Balance1, BlockTimestamp)
token0 |-> Token0
token1 |-> Token1
factory |-> Factory
kLast |-> KLast => 0
totalSupply |-> Supply => Supply - Balance
balanceOf[ACCT_ID] |-> Balance => 0
price0CumulativeLast |-> Price0 => #if (TimeElapsed > 0) and (Reserve0 =/= 0) and (Reserve1 =/= 0) #then chop(PriceIncrease0 + Price0) #else Price0 #fi
price1CumulativeLast |-> Price1 => #if (TimeElapsed > 0) and (Reserve0 =/= 0) and (Reserve1 =/= 0) #then chop(PriceIncrease1 + Price1) #else Price1 #fi
lockState |-> LockState => LockState
storage Token0
balanceOf[ACCT_ID] |-> Balance0 => Balance0
storage Token1
balanceOf[ACCT_ID] |-> Balance1 => Balance1
storage Factory
feeTo |-> FeeTo
returns Amount0 : Amount1
where
Amount0 := (Balance * Balance0) / Supply
Amount1 := (Balance * Balance1) / Supply
BlockTimestamp := TIME mod pow32
TimeElapsed := (BlockTimestamp -Word BlockTimestampLast ) mod pow32
PriceIncrease0 := ((pow112 * Reserve1) / Reserve0) * TimeElapsed
PriceIncrease1 := ((pow112 * Reserve0) / Reserve1) * TimeElapsed
iff in range uint256
// burn
Balance * Balance0
Balance * Balance1
Amount0
Amount1
Supply - Balance
iff in range uint112
// _safeTransfer
Balance0 - Amount0
Balance1 - Amount1
iff
Amount0 > 0
Amount1 > 0
LockState == 1
VCallValue == 0
VCallDepth < 1024
if
// variant: transfer back to pair contract
to == ACCT_ID
FeeTo == 0
KLast =/= 0
Supply =/= 0
calls
UniswapV2Pair.balanceOf
UniswapV2Factory.feeTo
behaviour sync of UniswapV2Pair
interface sync()
for all
LockState : uint256
Token0 : address UniswapV2Pair
Token1 : address UniswapV2Pair
Price0 : uint256
Price1 : uint256
Balance0 : uint256
Balance1 : uint256
Reserve0 : uint112
Reserve1 : uint112
BlockTimestampLast : uint32
storage
lockState |-> LockState => LockState
token0 |-> Token0
token1 |-> Token1
reserve0_reserve1_blockTimestampLast |-> #WordPackUInt112UInt112UInt32(Reserve0, Reserve1, BlockTimestampLast) => \
#WordPackUInt112UInt112UInt32(Balance0, Balance1, (TIME mod pow32))
price0CumulativeLast |-> Price0 => \
#if Reserve0 =/= 0 and Reserve1 =/= 0 and (((TIME mod pow32) -Word BlockTimestampLast) mod pow32) > 0 \
#then chop(((((pow112 * Reserve1) / Reserve0) * (((TIME mod pow32) -Word BlockTimestampLast) mod pow32)) + Price0)) \
#else Price0 \
#fi
price1CumulativeLast |-> Price1 => \
#if Reserve0 =/= 0 and Reserve1 =/= 0 and (((TIME mod pow32) -Word BlockTimestampLast) mod pow32) > 0 \
#then chop(((((pow112 * Reserve0) / Reserve1) * (((TIME mod pow32) -Word BlockTimestampLast) mod pow32)) + Price1)) \
#else Price1 \
#fi
storage Token0
balanceOf[ACCT_ID] |-> Balance0
storage Token1
balanceOf[ACCT_ID] |-> Balance1
iff in range uint112
Balance0
Balance1
iff
LockState == 1
VCallValue == 0
VCallDepth < 1024
calls
UniswapV2Pair.balanceOf
behaviour skim of UniswapV2Pair
interface skim(address to)
for all
LockState : uint256
Token0 : address UniswapV2Pair
Token1 : address UniswapV2Pair
SrcBal0 : uint256
SrcBal1 : uint256
DstBal0 : uint256
DstBal1 : uint256
Reserve0 : uint112
Reserve1 : uint112
BlockTimestampLast : uint32
storage
lockState |-> LockState => LockState
token0 |-> Token0
token1 |-> Token1
reserve0_reserve1_blockTimestampLast |-> #WordPackUInt112UInt112UInt32(Reserve0, Reserve1, BlockTimestampLast)
storage Token0
balanceOf[ACCT_ID] |-> SrcBal0 => Reserve0
balanceOf[to] |-> DstBal0 => DstBal0 + (SrcBal0 - Reserve0)
storage Token1
balanceOf[ACCT_ID] |-> SrcBal1 => Reserve1
balanceOf[to] |-> DstBal1 => DstBal1 + (SrcBal1 - Reserve1)
iff in range uint256
SrcBal0 - Reserve0
DstBal0 + (SrcBal0 - Reserve0)
SrcBal1 - Reserve1
DstBal1 + (SrcBal1 - Reserve1)
iff
LockState == 1
VCallValue == 0
VCallDepth < 1024
if
to =/= ACCT_ID
calls
UniswapV2Pair.balanceOf
behaviour skim-back of UniswapV2Pair
interface skim(address to)
for all
LockState : uint256
Token0 : address UniswapV2Pair
Token1 : address UniswapV2Pair
SrcBal0 : uint256
SrcBal1 : uint256
Reserve0 : uint112
Reserve1 : uint112
BlockTimestampLast : uint32
storage
lockState |-> LockState => LockState
token0 |-> Token0
token1 |-> Token1
reserve0_reserve1_blockTimestampLast |-> #WordPackUInt112UInt112UInt32(Reserve0, Reserve1, BlockTimestampLast)
storage Token0
balanceOf[ACCT_ID] |-> SrcBal0 => SrcBal0
storage Token1
balanceOf[ACCT_ID] |-> SrcBal1 => SrcBal1
iff in range uint256
SrcBal0 - Reserve0
SrcBal1 - Reserve1
iff
LockState == 1
VCallValue == 0
VCallDepth < 1024
if
to == ACCT_ID
calls
UniswapV2Pair.balanceOf
behaviour swap of UniswapV2Pair
interface swap(uint amount0Out, uint amount1Out, address to, bytes calldata data)
for all
LockState : uint256
Reserve0 : uint112
Reserve1 : uint112
Balance0 : uint112
Balance1 : uint112
BlockTimestampLast : uint32
Token0 : address UniswapV2Pair
Token1 : address UniswapV2Pair
Price0 : uint256
Price1 : uint256
DstBal0 : uint256
DstBal1 : uint256
BlockTimestampLast : uint32
storage
reserve0_reserve1_blockTimestampLast |-> #WordPackUInt112UInt112UInt32(Reserve0, Reserve1, BlockTimestampLast) => #WordPackUInt112UInt112UInt32(Balance0 - amount0Out, Balance1 - amount1Out, TIME)
token0 |-> Token0
token1 |-> Token1
lockState |-> LockState
price0CumulativeLast |-> Price0 => #if TIME - BlockTimestampLast =/= 0 #then chop((((pow112 * Reserve1) / Reserve0) * (TIME - BlockTimestampLast)) + Price0) #else Price0 #fi
price1CumulativeLast |-> Price1 => #if TIME - BlockTimestampLast =/= 0 #then chop((((pow112 * Reserve0) / Reserve1) * (TIME - BlockTimestampLast)) + Price1) #else Price1 #fi
storage Token0
balanceOf[ACCT_ID] |-> Balance0 => Balance0 - amount0Out
balanceOf[to] |-> DstBal0 => DstBal0 + amount0Out
storage Token1
balanceOf[ACCT_ID] |-> Balance1 => Balance1 - amount1Out
balanceOf[to] |-> DstBal1 => DstBal1 + amount1Out
where
Amount0In := (Reserve0 - amount0Out) < (Balance0 - amount0Out)
Amount1In := (Reserve1 - amount1Out) < (Balance1 - amount1Out)
ValidEmptyCall := (VCallDepth < 1024) andBool (0 <= ACCT_ID_balance)
A := Reserve0 < Balance0
B := (((Balance0 * 1000) - ((Balance0 - Reserve0) * 3)) * ((Balance1 - amount1Out) * 1000)) < ((Reserve0 * Reserve1) * 1000000)
C := ((Balance0 * 1000) * (((Balance1 - amount1Out) * 1000) - ((Balance1 - (amount1Out + (Reserve1 - amount1Out))) * 3))) < ((Reserve0 * Reserve1) * 1000000)
D := (((Balance0 * 1000) - ((Balance0 - Reserve0) * 3)) * (((Balance1 - amount1Out) * 1000) - ((Balance1 - (amount1Out + (Reserve1 - amount1Out))) * 3))) < ((Reserve0 * Reserve1) * 1000000)
V := ((Balance1 - amount1Out) * 1000) ==K 0
L := A and (notBool B) and (notBool V) and (notBool Amount1In)
R := (notBool A) and (notBool C) and Amount1In
iff
VCallValue == 0
1 == LockState
amount1Out < Reserve1
to =/= Token0
to =/= Token1
ValidEmptyCall
0 < amount1Out
0 < Reserve0
#rangeUInt(256, Balance1 - amount1Out)
#rangeUInt(256, DstBal1 + amount1Out)
A impliesBool ((Amount1In impliesBool (notBool D)) and ((notBool Amount1In) impliesBool ((notBool V) and (notBool B))))
(notBool A) impliesBool ((notBool C) and Amount1In)
if
Token0 =/= Token1
ACCT_ID =/= to
calldata ==K .WordStack
TIME < pow32
TIME >= BlockTimestampLast
amount0Out == 0
TODO amount0Out =/= 0
behaviour swap2 of UniswapV2Pair
interface swap(uint amount0Out, uint amount1Out, address to, bytes calldata data)
for all
LockState : uint256
Reserve0 : uint112
Reserve1 : uint112
Balance0 : uint112
Balance1 : uint112
BlockTimestampLast : uint32
Token0 : address UniswapV2Pair
Token1 : address UniswapV2Pair
Price0 : uint256
Price1 : uint256
DstBal0 : uint256
DstBal1 : uint256
BlockTimestampLast : uint32
storage
reserve0_reserve1_blockTimestampLast |-> #WordPackUInt112UInt112UInt32(Reserve0, Reserve1, BlockTimestampLast) => #WordPackUInt112UInt112UInt32(Balance0 - amount0Out, Balance1 - amount1Out, TIME)
token0 |-> Token0
token1 |-> Token1
lockState |-> LockState
price0CumulativeLast |-> Price0 => #if TIME - BlockTimestampLast =/= 0 #then chop((((pow112 * Reserve1) / Reserve0) * (TIME - BlockTimestampLast)) + Price0) #else Price0 #fi
price1CumulativeLast |-> Price1 => #if TIME - BlockTimestampLast =/= 0 #then chop((((pow112 * Reserve0) / Reserve1) * (TIME - BlockTimestampLast)) + Price1) #else Price1 #fi
storage Token0
balanceOf[ACCT_ID] |-> Balance0 => Balance0 - amount0Out
balanceOf[to] |-> DstBal0 => DstBal0 + amount0Out
storage Token1
balanceOf[ACCT_ID] |-> Balance1 => Balance1 - amount1Out
balanceOf[to] |-> DstBal1 => DstBal1 + amount1Out
where
Amount0In := (Reserve0 - amount0Out) < (Balance0 - amount0Out)
Amount1In := (Reserve1 - amount1Out) < (Balance1 - amount1Out)
ValidEmptyCall := (VCallDepth < 1024) andBool (0 <= ACCT_ID_balance)
N := Reserve1 < Balance1
Q := (((Balance0 - amount0Out) * 1000) * ((Balance1 * 1000) - ((Balance1 - Reserve1) * 3))) < ((Reserve0 * Reserve1) * 1000000)
O := (Balance1 * 1000) ==K 0
P := ((((Balance0 - amount0Out) * 1000) - ((Balance0 - (amount0Out + (Reserve0 - amount0Out))) * 3)) * (Balance1 * 1000)) < ((Reserve0 * Reserve1) * 1000000)
S := ((((Balance0 - amount0Out) * 1000) - ((Balance0 - (amount0Out + (Reserve0 - amount0Out))) * 3)) * ((Balance1 * 1000) - ((Balance1 - Reserve1) * 3))) < ((Reserve0 * Reserve1) * 1000000)
iff
VCallValue == 0
1 == LockState
amount1Out < Reserve1
to =/= Token0
to =/= Token1
ValidEmptyCall
amount0Out < Reserve0
#rangeUInt(256, Balance0 - amount0Out)
#rangeUInt(256, DstBal0 + amount0Out)
(notBool Amount0In) impliesBool (N and (notBool Q))
Amount0In impliesBool ((N impliesBool (notBool S)) and ((notBool N) impliesBool ((notBool O) and (notBool P))))
if
Token0 =/= Token1
ACCT_ID =/= to
calldata ==K .WordStack
TIME < pow32
TIME >= BlockTimestampLast
amount0Out =/= 0
amount1Out == 0
behaviour swap3 of UniswapV2Pair
interface swap(uint amount0Out, uint amount1Out, address to, bytes calldata data)
for all
LockState : uint256
Reserve0 : uint112
Reserve1 : uint112
Balance0 : uint112
Balance1 : uint112
BlockTimestampLast : uint32
Token0 : address UniswapV2Pair
Token1 : address UniswapV2Pair
Price0 : uint256
Price1 : uint256
DstBal0 : uint256
DstBal1 : uint256
BlockTimestampLast : uint32
storage
reserve0_reserve1_blockTimestampLast |-> #WordPackUInt112UInt112UInt32(Reserve0, Reserve1, BlockTimestampLast) => #WordPackUInt112UInt112UInt32(Balance0 - amount0Out, Balance1 - amount1Out, TIME)
token0 |-> Token0
token1 |-> Token1
lockState |-> LockState
price0CumulativeLast |-> Price0 => #if TIME - BlockTimestampLast =/= 0 #then chop((((pow112 * Reserve1) / Reserve0) * (TIME - BlockTimestampLast)) + Price0) #else Price0 #fi
price1CumulativeLast |-> Price1 => #if TIME - BlockTimestampLast =/= 0 #then chop((((pow112 * Reserve0) / Reserve1) * (TIME - BlockTimestampLast)) + Price1) #else Price1 #fi
storage Token0
balanceOf[ACCT_ID] |-> Balance0 => Balance0 - amount0Out
balanceOf[to] |-> DstBal0 => DstBal0 + amount0Out
storage Token1
balanceOf[ACCT_ID] |-> Balance1 => Balance1 - amount1Out
balanceOf[to] |-> DstBal1 => DstBal1 + amount1Out
where
Amount0In := (Reserve0 - amount0Out) < (Balance0 - amount0Out)
Amount1In := (Reserve1 - amount1Out) < (Balance1 - amount1Out)
ValidEmptyCall := (VCallDepth < 1024) andBool (0 <= ACCT_ID_balance)
V := ((Balance1 - amount1Out) * 1000) ==K 0
W := ((((Balance0 - amount0Out) * 1000) - ((Balance0 - (amount0Out + (Reserve0 - amount0Out))) * 3)) * ((Balance1 - amount1Out) * 1000)) < ((Reserve0 * Reserve1) * 1000000)
Y := ((((Balance0 - amount0Out) * 1000) - ((Balance0 - (amount0Out + (Reserve0 - amount0Out))) * 3)) * (((Balance1 - amount1Out) * 1000) - ((Balance1 - (amount1Out + (Reserve1 - amount1Out))) * 3))) < ((Reserve0 * Reserve1) * 1000000)
T := #rangeUInt(256, ((Balance0 - amount0Out) * 1000) * (((Balance1 - amount1Out) * 1000) - ((Balance1 - (amount1Out + (Reserve1 - amount1Out))) * 3)))
Z := #rangeUInt(256, (((Balance0 - amount0Out) * 1000) - ((Balance0 - (amount0Out + (Reserve0 - amount0Out))) * 3)) * ((Balance1 - amount1Out) * 1000))
U := #rangeUInt(256, (((Balance0 - amount0Out) * 1000) - ((Balance0 - (amount0Out + (Reserve0 - amount0Out))) * 3)) * (((Balance1 - amount1Out) * 1000) - ((Balance1 - (amount1Out + (Reserve1 - amount1Out))) * 3)))
X := (((Balance0 - amount0Out) * 1000) * (((Balance1 - amount1Out) * 1000) - ((Balance1 - (amount1Out + (Reserve1 - amount1Out))) * 3))) < ((Reserve0 * Reserve1) * 1000000)
K11 := (Amount0In and Amount1In) impliesBool (U and (notBool Y))
K01 := (Amount0In and (notBool Amount1In)) impliesBool ((notBool V) and Z and (notBool W))
K10 := ((notBool Amount0In)) impliesBool (T and Amount1In and (notBool X))
iff
VCallValue == 0
1 == LockState
amount1Out < Reserve1
amount0Out < Reserve0
to =/= Token0
to =/= Token1
ValidEmptyCall
#rangeUInt(256, Balance0 - amount0Out)
#rangeUInt(256, DstBal0 + amount0Out)
#rangeUInt(256, Balance1 - amount1Out)
#rangeUInt(256, DstBal1 + amount1Out)
K11
K01
K10
if
Token0 =/= Token1
ACCT_ID =/= to
calldata ==K .WordStack
TIME < pow32
TIME >= BlockTimestampLast
amount0Out =/= 0
amount1Out =/= 0
mint
allows anyone to mint new liquidity tokens to the to
address in return for adding tokens to
the pool.
6 specs in total are required to exhaustively cover the behaviour. They cover the following cases:
- no fee minted (
FeeLiquidity == 0
)- first call to contract (
TotalSupply == 0
)- diff:
to =/= 0
- same:
to == 0
- diff:
- subsequent call to contract (
TotalSupply > 0
- lt:
(Amount0 * TotalSupply) / Reserve0 < (Amount1 * TotalSupply) / Reserve1
- gte:
(Amount0 * TotalSupply) / Reserve0 >= (Amount1 * TotalSupply) / Reserve1
- lt:
- first call to contract (
- fee minted (
FeeLiquidity > 0
)- diff:
to =/= FeeTo
- same:
to == FeeTo
- diff:
The decomposition into both diff
and same
cases, and fee and no fee cases, is required to avoid
logical inconsistencies when writing to multiple locations within the same storage mapping in a
single spec. The decomposition into first / subsequent cases in the no fee minted branch is a
performance optimization to keep proof times somewhat reasonable.
behaviour mint-noFee-first-diff of UniswapV2Pair
interface mint(address to)
for all
Token0 : address UniswapV2Pair
Token1 : address UniswapV2Pair
Balance0 : uint256
Balance1 : uint256
Reserve0 : uint112
Reserve1 : uint112
PriceLast0 : uint256
PriceLast1 : uint256
BlockTimestampLast : uint32
Factory : address UniswapV2Factory
FeeTo : address
KLast : uint256
DstBal : uint256
Burned : uint256
TotalSupply : uint256
LockState : uint256
where
// --- input token amounts ---
Amount0 := Balance0 - Reserve0
Amount1 := Balance1 - Reserve1
// --- fee liquidity Calculations ---
FeeOn := FeeTo =/= 0
RootK := #sqrt(Reserve0 * Reserve1)
RootKLast := #sqrt(KLast)
FeeLiquidity := (TotalSupply * (RootK - RootKLast)) / ((RootK * 5) + RootKLast)
// --- LP share minting ---
MINIMUM_LIQUIDITY := 1000
// --- time elapsed since last block in which _update was called ---
TimeElapsed := ((TIME mod pow32) -Word BlockTimestampLast) mod pow32
storage Token0
balanceOf[ACCT_ID] |-> Balance0
storage Token1
balanceOf[ACCT_ID] |-> Balance1
storage Factory
feeTo |-> FeeTo
storage
token0 |-> Token0
token1 |-> Token1
factory |-> Factory
// --- reentrancy guard ---
lockState |-> LockState => LockState
// --- cache invariant ---
kLast |-> KLast => #if FeeOn #then Balance0 * Balance1 #else 0 #fi
// --- mint tokens ---
balanceOf[to] |-> DstBal => DstBal + #sqrt(Amount0 * Amount1) - MINIMUM_LIQUIDITY
balanceOf[0] |-> Burned => Burned + MINIMUM_LIQUIDITY
totalSupply |-> TotalSupply => TotalSupply + #sqrt(Amount0 * Amount1)
// --- sync reserves to balances, update cached timestamp ---
reserve0_reserve1_blockTimestampLast |-> #WordPackUInt112UInt112UInt32(Reserve0, Reserve1, BlockTimestampLast) \
=> #WordPackUInt112UInt112UInt32(Balance0, Balance1, (TIME mod pow32))
// --- price accumulator updates ---
price0CumulativeLast |-> PriceLast0 => \
#if Reserve0 =/= 0 and Reserve1 =/= 0 and TimeElapsed > 0 \
#then chop(((((pow112 * Reserve1) / Reserve0) * TimeElapsed) + PriceLast0)) \
#else PriceLast0 \
#fi
price1CumulativeLast |-> PriceLast1 => \
#if Reserve0 =/= 0 and Reserve1 =/= 0 and TimeElapsed > 0 \
#then chop(((((pow112 * Reserve0) / Reserve1) * TimeElapsed) + PriceLast1)) \
#else PriceLast1 \
#fi
iff in range uint112
Balance0
Balance1
iff in range uint256
Amount0
Amount1
Amount0 * Amount1
#sqrt(Amount0 * Amount1) - MINIMUM_LIQUIDITY
Burned + MINIMUM_LIQUIDITY
DstBal + #sqrt(Amount0 * Amount1) - MINIMUM_LIQUIDITY
iff
// --- conditional artihmetic safety checks (_mintFee) ---
(FeeOn) impliesBool #rangeUInt(256, Balance0 * Balance1)
(FeeOn and KLast > 0) impliesBool #rangeUInt(256, Reserve0 * Reserve1)
(FeeOn and KLast > 0 and RootK > RootKLast) impliesBool ( \
#rangeUInt(256, RootK * 5) \
and #rangeUInt(256, RootK * 5 + RootKLast) \
and #rangeUInt(256, RootK - RootKLast) \
and #rangeUInt(256, TotalSupply * (RootK - RootKLast)) \
)
// --- LP shares must be minted ---
#sqrt(Amount0 * Amount1) - MINIMUM_LIQUIDITY > 0
// --- no reentrancy ---
LockState == 1
// --- mint is not payable ---
VCallValue == 0
// --- call stack does not overflow ---
VCallDepth < 1024
if
// --- no fee minting ---
FeeLiquidity == 0
// --- no storage collisions ---
to =/= 0
// --- first call ---
TotalSupply == 0
calls
UniswapV2Pair.balanceOf
UniswapV2Factory.feeTo
returns #sqrt(Amount0 * Amount1) - MINIMUM_LIQUIDITY
behaviour mint-noFee-first-same of UniswapV2Pair
interface mint(address to)
for all
Token0 : address UniswapV2Pair
Token1 : address UniswapV2Pair
Balance0 : uint256
Balance1 : uint256
Reserve0 : uint112
Reserve1 : uint112
PriceLast0 : uint256
PriceLast1 : uint256
BlockTimestampLast : uint32
Factory : address UniswapV2Factory
FeeTo : address
KLast : uint256
Burned : uint256
TotalSupply : uint256
LockState : uint256
where
// --- input token amounts ---
Amount0 := Balance0 - Reserve0
Amount1 := Balance1 - Reserve1
// --- fee liquidity Calculations ---
FeeOn := FeeTo =/= 0
RootK := #sqrt(Reserve0 * Reserve1)
RootKLast := #sqrt(KLast)
FeeLiquidity := (TotalSupply * (RootK - RootKLast)) / ((RootK * 5) + RootKLast)
// --- LP share minting ---
MINIMUM_LIQUIDITY := 1000
// --- time elapsed since last block in which _update was called ---
TimeElapsed := ((TIME mod pow32) -Word BlockTimestampLast) mod pow32
storage Token0
balanceOf[ACCT_ID] |-> Balance0
storage Token1
balanceOf[ACCT_ID] |-> Balance1
storage Factory
feeTo |-> FeeTo
storage
token0 |-> Token0
token1 |-> Token1
factory |-> Factory
// --- reentrancy guard ---
lockState |-> LockState => LockState
// --- cache invariant ---
kLast |-> KLast => #if FeeOn #then Balance0 * Balance1 #else 0 #fi
// --- mint tokens ---
balanceOf[0] |-> Burned => Burned + #sqrt(Amount0 * Amount1)
totalSupply |-> TotalSupply => TotalSupply + #sqrt(Amount0 * Amount1)
// --- sync reserves to balances, update cached timestamp ---
reserve0_reserve1_blockTimestampLast |-> #WordPackUInt112UInt112UInt32(Reserve0, Reserve1, BlockTimestampLast) \
=> #WordPackUInt112UInt112UInt32(Balance0, Balance1, (TIME mod pow32))
// --- price accumulator updates ---
price0CumulativeLast |-> PriceLast0 => \
#if Reserve0 =/= 0 and Reserve1 =/= 0 and TimeElapsed > 0 \
#then chop(((((pow112 * Reserve1) / Reserve0) * TimeElapsed) + PriceLast0)) \
#else PriceLast0 \
#fi
price1CumulativeLast |-> PriceLast1 => \
#if Reserve0 =/= 0 and Reserve1 =/= 0 and TimeElapsed > 0 \
#then chop(((((pow112 * Reserve0) / Reserve1) * TimeElapsed) + PriceLast1)) \
#else PriceLast1 \
#fi
iff in range uint112
Balance0
Balance1
iff in range uint256
Amount0
Amount1
Amount0 * Amount1
#sqrt(Amount0 * Amount1) - MINIMUM_LIQUIDITY
Burned + MINIMUM_LIQUIDITY
Burned + MINIMUM_LIQUIDITY + #sqrt(Amount0 * Amount1) - MINIMUM_LIQUIDITY
iff
// --- conditional artihmetic safety checks (_mintFee) ---
(FeeOn) impliesBool #rangeUInt(256, Balance0 * Balance1)
(FeeOn and KLast > 0) impliesBool #rangeUInt(256, Reserve0 * Reserve1)
(FeeOn and KLast > 0 and RootK > RootKLast) impliesBool ( \
#rangeUInt(256, RootK * 5) \
and #rangeUInt(256, RootK * 5 + RootKLast) \
and #rangeUInt(256, RootK - RootKLast) \
and #rangeUInt(256, TotalSupply * (RootK - RootKLast)) \
)
// --- LP shares must be minted ---
#sqrt(Amount0 * Amount1) - MINIMUM_LIQUIDITY > 0
// --- no reentrancy ---
LockState == 1
// --- mint is not payable ---
VCallValue == 0
// --- call stack does not overflow ---
VCallDepth < 1024
if
// --- no fee minting ---
FeeLiquidity == 0
// --- burn all liquidity ---
to == 0
// --- first call ---
TotalSupply == 0
calls
UniswapV2Pair.balanceOf
UniswapV2Factory.feeTo
returns #sqrt(Amount0 * Amount1) - MINIMUM_LIQUIDITY
behaviour mint-noFee-subsequent-lt of UniswapV2Pair
interface mint(address to)
for all
Token0 : address UniswapV2Pair
Token1 : address UniswapV2Pair
Balance0 : uint256
Balance1 : uint256
Reserve0 : uint112
Reserve1 : uint112
PriceLast0 : uint256
PriceLast1 : uint256
BlockTimestampLast : uint32
Factory : address UniswapV2Factory
FeeTo : address
KLast : uint256
DstBal : uint256
Burned : uint256
TotalSupply : uint256
LockState : uint256
where
// --- input token amounts ---
Amount0 := Balance0 - Reserve0
Amount1 := Balance1 - Reserve1
// --- fee liquidity Calculations ---
FeeOn := FeeTo =/= 0
RootK := #sqrt(Reserve0 * Reserve1)
RootKLast := #sqrt(KLast)
FeeLiquidity := (TotalSupply * (RootK - RootKLast)) / ((RootK * 5) + RootKLast)
// --- LP share burning ---
SharesBurned := #if TotalSupply == 0 #then MINIMUM_LIQUIDITY #else 0 #fi
// --- time elapsed since last block in which _update was called ---
TimeElapsed := ((TIME mod pow32) -Word BlockTimestampLast) mod pow32
storage Token0
balanceOf[ACCT_ID] |-> Balance0
storage Token1
balanceOf[ACCT_ID] |-> Balance1
storage Factory
feeTo |-> FeeTo
storage
token0 |-> Token0
token1 |-> Token1
factory |-> Factory
// --- reentrancy guard ---
lockState |-> LockState => LockState
// --- cache invariant ---
kLast |-> KLast => #if FeeOn #then Balance0 * Balance1 #else 0 #fi
// --- mint tokens ---
balanceOf[to] |-> DstBal => DstBal + (Amount0 * TotalSupply / Reserve0)
totalSupply |-> TotalSupply => TotalSupply + (Amount0 * TotalSupply / Reserve0)
// --- sync reserves to balances, update cached timestamp ---
reserve0_reserve1_blockTimestampLast |-> #WordPackUInt112UInt112UInt32(Reserve0, Reserve1, BlockTimestampLast) \
=> #WordPackUInt112UInt112UInt32(Balance0, Balance1, (TIME mod pow32))
// --- price accumulator updates ---
price0CumulativeLast |-> PriceLast0 => \
#if Reserve0 =/= 0 and Reserve1 =/= 0 and TimeElapsed > 0 \
#then chop(((((pow112 * Reserve1) / Reserve0) * TimeElapsed) + PriceLast0)) \
#else PriceLast0 \
#fi
price1CumulativeLast |-> PriceLast1 => \
#if Reserve0 =/= 0 and Reserve1 =/= 0 and TimeElapsed > 0 \
#then chop(((((pow112 * Reserve0) / Reserve1) * TimeElapsed) + PriceLast1)) \
#else PriceLast1 \
#fi
iff in range uint112
Balance0
Balance1
iff in range uint256
Amount0
Amount1
Amount0 * TotalSupply
Amount1 * TotalSupply
DstBal + (Amount0 * TotalSupply / Reserve0)
TotalSupply + (Amount0 * TotalSupply / Reserve0)
iff
// --- no divide by zeros ---
Reserve0 > 0
Reserve1 > 0
// --- conditional artihmetic safety checks ---
(FeeOn) impliesBool #rangeUInt(256, Balance0 * Balance1)
(FeeOn and KLast > 0) impliesBool #rangeUInt(256, Reserve0 * Reserve1)
(FeeOn and KLast > 0 and RootK > RootKLast) impliesBool ( \
#rangeUInt(256, RootK * 5) \
and #rangeUInt(256, RootK * 5 + RootKLast) \
and #rangeUInt(256, RootK - RootKLast) \
and #rangeUInt(256, TotalSupply * (RootK - RootKLast)) \
)
// --- LP shares must be minted ---
(Amount0 * TotalSupply) / Reserve0 > 0
// --- no reentrancy ---
LockState == 1
// --- mint is not payable ---
VCallValue == 0
// --- call stack does not overflow ---
VCallDepth < 1024
if
// --- no fee minting ---
FeeLiquidity == 0
// --- subsequent lt ---
TotalSupply > 0
(Amount0 * TotalSupply) / Reserve0 < (Amount1 * TotalSupply) / Reserve1
calls
UniswapV2Pair.balanceOf
UniswapV2Factory.feeTo
returns (Amount0 * TotalSupply) / Reserve0
behaviour mint-noFee-subsequent-gte of UniswapV2Pair
interface mint(address to)
for all
Token0 : address UniswapV2Pair
Token1 : address UniswapV2Pair
Balance0 : uint256
Balance1 : uint256
Reserve0 : uint112
Reserve1 : uint112
PriceLast0 : uint256
PriceLast1 : uint256
BlockTimestampLast : uint32
Factory : address UniswapV2Factory
FeeTo : address
KLast : uint256
DstBal : uint256
Burned : uint256
TotalSupply : uint256
LockState : uint256
where
// --- input token amounts ---
Amount0 := Balance0 - Reserve0
Amount1 := Balance1 - Reserve1
// --- fee liquidity Calculations ---
FeeOn := FeeTo =/= 0
RootK := #sqrt(Reserve0 * Reserve1)
RootKLast := #sqrt(KLast)
FeeLiquidity := (TotalSupply * (RootK - RootKLast)) / ((RootK * 5) + RootKLast)
// --- LP share burning ---
SharesBurned := #if TotalSupply == 0 #then MINIMUM_LIQUIDITY #else 0 #fi
// --- time elapsed since last block in which _update was called ---
TimeElapsed := ((TIME mod pow32) -Word BlockTimestampLast) mod pow32
storage Token0
balanceOf[ACCT_ID] |-> Balance0
storage Token1
balanceOf[ACCT_ID] |-> Balance1
storage Factory
feeTo |-> FeeTo
storage
token0 |-> Token0
token1 |-> Token1
factory |-> Factory
// -- reentrancy guard ---
lockState |-> LockState => LockState
// -- cache invariant ---
kLast |-> KLast => #if FeeOn #then Balance0 * Balance1 #else 0 #fi
// -- mint tokens ---
balanceOf[to] |-> DstBal => DstBal + (Amount1 * TotalSupply / Reserve1)
totalSupply |-> TotalSupply => TotalSupply + (Amount1 * TotalSupply / Reserve1)
// --- sync reserves to balances, update cached timestamp ---
reserve0_reserve1_blockTimestampLast |-> #WordPackUInt112UInt112UInt32(Reserve0, Reserve1, BlockTimestampLast) \
=> #WordPackUInt112UInt112UInt32(Balance0, Balance1, (TIME mod pow32))
// --- price accumulator updates ---
price0CumulativeLast |-> PriceLast0 => \
#if Reserve0 =/= 0 and Reserve1 =/= 0 and TimeElapsed > 0 \
#then chop(((((pow112 * Reserve1) / Reserve0) * TimeElapsed) + PriceLast0)) \
#else PriceLast0 \
#fi
price1CumulativeLast |-> PriceLast1 => \
#if Reserve0 =/= 0 and Reserve1 =/= 0 and TimeElapsed > 0 \
#then chop(((((pow112 * Reserve0) / Reserve1) * TimeElapsed) + PriceLast1)) \
#else PriceLast1 \
#fi
iff in range uint112
Balance0
Balance1
iff in range uint256
Amount0
Amount1
Amount0 * TotalSupply
Amount1 * TotalSupply
DstBal + (Amount1 * TotalSupply / Reserve1)
TotalSupply + (Amount1 * TotalSupply / Reserve1)
iff
// --- no divide by zeros ---
Reserve0 > 0
Reserve1 > 0
// --- conditional artihmetic safety checks ---
(FeeOn) impliesBool #rangeUInt(256, Balance0 * Balance1)
(FeeOn and KLast > 0) impliesBool #rangeUInt(256, Reserve0 * Reserve1)
(FeeOn and KLast > 0 and RootK > RootKLast) impliesBool ( \
#rangeUInt(256, RootK * 5) \
and #rangeUInt(256, RootK * 5 + RootKLast) \
and #rangeUInt(256, RootK - RootKLast) \
and #rangeUInt(256, TotalSupply * (RootK - RootKLast)) \
)
// --- LP shares must be minted ---
(Amount1 * TotalSupply) / Reserve1 > 0
// --- no reentrancy ---
LockState == 1
// --- mint is not payable ---
VCallValue == 0
// --- call stack does not overflow ---
VCallDepth < 1024
if
// --- no fee minting ---
FeeLiquidity == 0
// --- subsequent gte ---
TotalSupply > 0
(Amount0 * TotalSupply) / Reserve0 >= (Amount1 * TotalSupply) / Reserve1
calls
UniswapV2Pair.balanceOf
UniswapV2Factory.feeTo
returns (Amount1 * TotalSupply) / Reserve1
behaviour mint-feeMinted-diff of UniswapV2Pair
interface mint(address to)
for all
Token0 : address UniswapV2Pair
Token1 : address UniswapV2Pair
Balance0 : uint256
Balance1 : uint256
Reserve0 : uint112
Reserve1 : uint112
PriceLast0 : uint256
PriceLast1 : uint256
BlockTimestampLast : uint32
Factory : address UniswapV2Factory
FeeTo : address
KLast : uint256
DstBal : uint256
FeeBal : uint256
TotalSupply : uint256
LockState : uint256
where
// --- input token amounts ---
Amount0 := Balance0 - Reserve0
Amount1 := Balance1 - Reserve1
// --- fee liquidity Calculations ---
FeeOn := FeeTo =/= 0
RootK := #sqrt(Reserve0 * Reserve1)
RootKLast := #sqrt(KLast)
FeeLiquidity := (TotalSupply * (RootK - RootKLast)) / ((RootK * 5) + RootKLast)
// --- LP share minting ---
Reserve0Liquidity := (Amount0 * (TotalSupply + FeeLiquidity)) / Reserve0
Reserve1Liquidity := (Amount1 * (TotalSupply + FeeLiquidity)) / Reserve1
// --- time elapsed since last block in which _update was called ---
TimeElapsed := ((TIME mod pow32) -Word BlockTimestampLast) mod pow32
storage Token0
balanceOf[ACCT_ID] |-> Balance0
storage Token1
balanceOf[ACCT_ID] |-> Balance1
storage Factory
feeTo |-> FeeTo
storage
token0 |-> Token0
token1 |-> Token1
factory |-> Factory
// -- reentrancy guard ---
lockState |-> LockState => LockState
// -- cache invariant ---
kLast |-> KLast => Balance0 * Balance1
// -- mint tokens ---
balanceOf[to] |-> DstBal => DstBal + #min(Reserve0Liquidity, Reserve1Liquidity)
balanceOf[FeeTo] |-> FeeBal => FeeBal + FeeLiquidity
totalSupply |-> TotalSupply => TotalSupply + FeeLiquidity + #min(Reserve0Liquidity, Reserve1Liquidity)
// --- sync reserves to balances, update cached timestamp ---
reserve0_reserve1_blockTimestampLast |-> #WordPackUInt112UInt112UInt32(Reserve0, Reserve1, BlockTimestampLast) \
=> #WordPackUInt112UInt112UInt32(Balance0, Balance1, (TIME mod pow32))
// --- price accumulator updates ---
price0CumulativeLast |-> PriceLast0 => \
#if Reserve0 =/= 0 and Reserve1 =/= 0 and TimeElapsed > 0 \
#then chop(((((pow112 * Reserve1) / Reserve0) * TimeElapsed) + PriceLast0)) \
#else PriceLast0 \
#fi
price1CumulativeLast |-> PriceLast1 => \
#if Reserve0 =/= 0 and Reserve1 =/= 0 and TimeElapsed > 0 \
#then chop(((((pow112 * Reserve0) / Reserve1) * TimeElapsed) + PriceLast1)) \
#else PriceLast1 \
#fi
iff in range uint112
Balance0
Balance1
iff in range uint256
Amount0
Amount1
Balance0 * Balance1
Reserve0 * Reserve1
Amount0 * (TotalSupply + FeeLiquidity)
Amount1 * (TotalSupply + FeeLiquidity)
RootK * 5
RootK * 5 + RootKLast
RootK - RootKLast
TotalSupply * (RootK - RootKLast)
FeeBal + FeeLiquidity
TotalSupply + FeeLiquidity
iff
Reserve0 > 0
Reserve1 > 0
Reserve0Liquidity < Reserve1Liquidity impliesBool ( \
#rangeUInt(256, DstBal + Reserve0Liquidity) \
and #rangeUInt(256, TotalSupply + FeeLiquidity + Reserve0Liquidity) \
and Reserve0Liquidity > 0 \
)
Reserve0Liquidity >= Reserve1Liquidity impliesBool ( \
#rangeUInt(256, DstBal + Reserve1Liquidity) \
and #rangeUInt(256, TotalSupply + FeeLiquidity + Reserve1Liquidity) \
and Reserve1Liquidity > 0 \
)
// --- no reentrancy ---
LockState == 1
// --- mint is not payable ---
VCallValue == 0
// --- call stack does not overflow ---
VCallDepth < 1024
if
// --- fee minted ---
FeeOn
KLast > 0
RootK > RootKLast
FeeLiquidity > 0
// implied by FeeLiquidity > 0, but added here to make life easier for the prover
TotalSupply > 0
// --- no storage collisions ---
to =/= FeeTo
calls
UniswapV2Pair.balanceOf
UniswapV2Factory.feeTo
returns #min(Reserve0Liquidity, Reserve1Liquidity)
behaviour mint-feeMinted-same of UniswapV2Pair
interface mint(address to)
for all
Token0 : address UniswapV2Pair
Token1 : address UniswapV2Pair
Balance0 : uint256
Balance1 : uint256
Reserve0 : uint112
Reserve1 : uint112
PriceLast0 : uint256
PriceLast1 : uint256
BlockTimestampLast : uint32
Factory : address UniswapV2Factory
FeeTo : address
KLast : uint256
FeeBal : uint256
TotalSupply : uint256
LockState : uint256
where
// --- input token amounts ---
Amount0 := Balance0 - Reserve0
Amount1 := Balance1 - Reserve1
// --- fee liquidity Calculations ---
FeeOn := FeeTo =/= 0
RootK := #sqrt(Reserve0 * Reserve1)
RootKLast := #sqrt(KLast)
FeeLiquidity := (TotalSupply * (RootK - RootKLast)) / ((RootK * 5) + RootKLast)
// --- LP share minting ---
Reserve0Liquidity := (Amount0 * (TotalSupply + FeeLiquidity)) / Reserve0
Reserve1Liquidity := (Amount1 * (TotalSupply + FeeLiquidity)) / Reserve1
// --- time elapsed since last block in which _update was called ---
TimeElapsed := ((TIME mod pow32) -Word BlockTimestampLast) mod pow32
storage Token0
balanceOf[ACCT_ID] |-> Balance0
storage Token1
balanceOf[ACCT_ID] |-> Balance1
storage Factory
feeTo |-> FeeTo
storage
token0 |-> Token0
token1 |-> Token1
factory |-> Factory
// -- reentrancy guard ---
lockState |-> LockState => LockState
// -- cache invariant ---
kLast |-> KLast => Balance0 * Balance1
// -- mint tokens ---
balanceOf[FeeTo] |-> FeeBal => FeeBal + FeeLiquidity + #min(Reserve0Liquidity, Reserve1Liquidity)
totalSupply |-> TotalSupply => TotalSupply + FeeLiquidity + #min(Reserve0Liquidity, Reserve1Liquidity)
// --- sync reserves to balances, update cached timestamp ---
reserve0_reserve1_blockTimestampLast |-> #WordPackUInt112UInt112UInt32(Reserve0, Reserve1, BlockTimestampLast) \
=> #WordPackUInt112UInt112UInt32(Balance0, Balance1, (TIME mod pow32))
// --- price accumulator updates ---
price0CumulativeLast |-> PriceLast0 => \
#if Reserve0 =/= 0 and Reserve1 =/= 0 and TimeElapsed > 0 \
#then chop(((((pow112 * Reserve1) / Reserve0) * TimeElapsed) + PriceLast0)) \
#else PriceLast0 \
#fi
price1CumulativeLast |-> PriceLast1 => \
#if Reserve0 =/= 0 and Reserve1 =/= 0 and TimeElapsed > 0 \
#then chop(((((pow112 * Reserve0) / Reserve1) * TimeElapsed) + PriceLast1)) \
#else PriceLast1 \
#fi
iff in range uint112
Balance0
Balance1
iff in range uint256
Amount0
Amount1
Balance0 * Balance1
Reserve0 * Reserve1
Amount0 * (TotalSupply + FeeLiquidity)
Amount1 * (TotalSupply + FeeLiquidity)
RootK * 5
RootK * 5 + RootKLast
RootK - RootKLast
TotalSupply * (RootK - RootKLast)
FeeBal + FeeLiquidity
TotalSupply + FeeLiquidity
iff
Reserve0 > 0
Reserve1 > 0
Reserve0Liquidity < Reserve1Liquidity impliesBool ( \
#rangeUInt(256, FeeBal + FeeLiquidity + Reserve0Liquidity) \
and #rangeUInt(256, TotalSupply + FeeLiquidity + Reserve0Liquidity) \
and Reserve0Liquidity > 0 \
)
Reserve0Liquidity >= Reserve1Liquidity impliesBool ( \
#rangeUInt(256, FeeBal + FeeLiquidity + Reserve1Liquidity) \
and #rangeUInt(256, TotalSupply + FeeLiquidity + Reserve1Liquidity) \
and Reserve1Liquidity > 0 \
)
// --- no reentrancy ---
LockState == 1
// --- mint is not payable ---
VCallValue == 0
// --- call stack does not overflow ---
VCallDepth < 1024
if
// --- fee minted ---
FeeOn
KLast > 0
RootK > RootKLast
FeeLiquidity > 0
// implied by FeeLiquidity > 0, but added here to make life easier for the prover
TotalSupply > 0
// --- fee recipient mints ---
to == FeeTo
calls
UniswapV2Pair.balanceOf
UniswapV2Factory.feeTo
returns #min(Reserve0Liquidity, Reserve1Liquidity)
UniswapV2 liquidity token behaviours.
behaviour name of UniswapV2Pair
interface name()
iff
VCallValue == 0
returnsRaw #asByteStackInWidthaux(32, 31, 32, #enc(#string("Uniswap V2")))
behaviour symbol of UniswapV2Pair
interface symbol()
iff
VCallValue == 0
returnsRaw #asByteStackInWidthaux(32, 31, 32, #enc(#string("UNI-V2")))
behaviour decimals of UniswapV2Pair
interface decimals()
iff
VCallValue == 0
returns 18
behaviour totalSupply of UniswapV2Pair
interface totalSupply()
for all
Supply : uint256
storage
totalSupply |-> Supply
iff
VCallValue == 0
returns Supply
behaviour balanceOf of UniswapV2Pair
interface balanceOf(address who)
for all
BalanceOf : uint256
storage
balanceOf[who] |-> BalanceOf
iff
VCallValue == 0
returns BalanceOf
behaviour allowance of UniswapV2Pair
interface allowance(address holder, address spender)
for all
Allowed : uint256
storage
allowance[holder][spender] |-> Allowed
iff
VCallValue == 0
returns Allowed
behaviour DOMAIN_SEPARATOR of UniswapV2Pair
interface DOMAIN_SEPARATOR()
for all
Dom : uint256
storage
DOMAIN_SEPARATOR |-> Dom
iff
VCallValue == 0
returns Dom
behaviour PERMIT_TYPEHASH of UniswapV2Pair
interface PERMIT_TYPEHASH()
iff
VCallValue == 0
returns Constants.PermitTypehash
behaviour nonces of UniswapV2Pair
interface nonces(address who)
for all
Nonce : uint256
storage
nonces[who] |-> Nonce
iff
VCallValue == 0
returns Nonce
behaviour transfer-diff of UniswapV2Pair
interface transfer(address to, uint value)
for all
SrcBal : uint256
DstBal : uint256
storage
balanceOf[CALLER_ID] |-> SrcBal => SrcBal - value
balanceOf[to] |-> DstBal => DstBal + value
iff
VCallValue == 0
iff in range uint256
SrcBal - value
DstBal + value
if
to =/= CALLER_ID
returns 1
behaviour transfer-same of UniswapV2Pair
interface transfer(address to, uint value)
for all
SrcBal : uint256
storage
balanceOf[CALLER_ID] |-> SrcBal => SrcBal
iff
VCallValue == 0
iff in range uint256
SrcBal - value
if
to == CALLER_ID
returns 1
behaviour approve of UniswapV2Pair
interface approve(address spender, uint value)
for all
Allowance : uint256
storage
allowance[CALLER_ID][spender] |-> Allowance => Value
iff
VCallValue == 0
returns 1
behaviour transferFrom-diff of UniswapV2Pair
interface transferFrom(address from, address to, uint value)
for all
SrcBal : uint256
DstBal : uint256
Allowed : uint256
storage
allowance[from][CALLER_ID] |-> Allowed => #if (Allowed == maxUInt256) #then Allowed #else Allowed - value #fi
balanceOf[from] |-> SrcBal => SrcBal - value
balanceOf[to] |-> DstBal => DstBal + value
iff in range uint256
SrcBal - value
DstBal + value
iff
value <= Allowed
VCallValue == 0
if
from =/= to
returns 1
behaviour transferFrom-same of UniswapV2Pair
interface transferFrom(address from, address to, uint value)
for all
FromBal : uint256
Allowed : uint256
storage
allowance[from][CALLER_ID] |-> Allowed => #if (Allowed == maxUInt256) #then Allowed #else Allowed - value #fi
balanceOf[from] |-> FromBal => FromBal
iff in range uint256
FromBal - value
iff
value <= Allowed
VCallValue == 0
if
from == to
returns 1
Authority over the transfer of Uniswap V2 liquidity tokens can be delegated in a zero-gas manner:
anyone who holds a message signed by owner
can use permit
to set the vaule in the approvals
mapping for owner
-> spender
to value
. Each message has a deadline
, after which it is no
longer valid.
The v
, r
and s
parameters are the individual components of the signature, split as required by
ecrecover
.
The message is constructed and verified according to
EIP-712
, where the typehash is
given by keccak("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")
and the domain seperator is set in the constructor with:
DOMAIN_SEPARATOR = keccak256(
abi.encode(
keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),
keccak256(bytes(name)),
keccak256(bytes('1')),
chainId,
address(this)
)
);
Checks against ECDSA Malleability are omitted as the existence of multiple valid signatures for the same message does not cause an issue: the user's wishes are being carried out, and the nonce protects against replay.
Nonces can overflow, allowing for replay attacks against owner
once uint256(-1)
permit
operations have been processed on their behalf. This should not be a concern in practice, especially
if messages have deadlines. The behaviour of the contract in the call where overflow occurs is not
covered by this spec.
Note that the nonce
is omitted from the signature of the solidity function, but is still included
and checked as part of the signed message.
behaviour permit of UniswapV2Pair
interface permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s)
for all
Nonce : uint256
Allowed : uint256
Domain_separator : bytes32
storage
nonces[owner] |-> Nonce => Nonce + 1
DOMAIN_SEPARATOR |-> Domain_separator
allowance[owner][spender] |-> Allowed => value
iff
0 =/= maxUInt160 & #symEcrec( \
keccakIntList( \
#asWord(#parseHexWord("0x19") : #parseHexWord("0x1") : .WordStack) \
Domain_separator \
keccakIntList(Constants.PermitTypehash owner spender value Nonce deadline) \
), v, r, s)
owner == maxUInt160 & #symEcrec( \
keccakIntList( \
#asWord(#parseHexWord("0x19") : #parseHexWord("0x1") : .WordStack) \
Domain_separator \
keccakIntList(Constants.PermitTypehash owner spender value Nonce deadline) \
), v, r, s)
TIME <= deadline
VCallValue == 0
VCallDepth < 1024
if
// ignore overflow case
#rangeUInt(256, Nonce + 1)