11from collections import namedtuple
22from decimal import Decimal , ROUND_HALF_DOWN
3+ import decimal
34
4- from model .balancer_constants import MIN_FEE , MAX_BOUND_TOKENS , INIT_POOL_SUPPLY , EXIT_FEE , MAX_IN_RATIO , MAX_OUT_RATIO
5+ from model .balancer_constants import MAX_TOTAL_WEIGHT , MAX_WEIGHT , MIN_BALANCE , MIN_FEE , MAX_BOUND_TOKENS , INIT_POOL_SUPPLY , EXIT_FEE , MAX_IN_RATIO , MAX_OUT_RATIO , MIN_WEIGHT
56from model .balancer_math import BalancerMath
67from dataclasses import dataclass
78
@@ -29,7 +30,8 @@ def __init__(self, initial_pool_supply: Decimal = INIT_POOL_SUPPLY):
2930 self ._swap_fee = MIN_FEE
3031 self ._records = {} # NOTE: we are assuming python 3.6+ ordered dictionaries
3132 self .total_weight = Decimal ('0' )
32- self .pool_token_supply = initial_pool_supply
33+ self ._pool_token_supply = initial_pool_supply
34+ self .factory_fees = Decimal ('0' )
3335
3436 def get_total_denorm_weight (self ):
3537 return self .total_weight
@@ -42,60 +44,78 @@ def get_normal_weight(self, token: str):
4244 return denorm / self .total_weight
4345
4446 def get_balance (self , token : str ):
45- return self ._records [token ].balance
47+ if self ._records .get (token ) is not None :
48+ return self ._records [token ].balance
49+ else :
50+ return 0
4651
4752 def get_num_tokens (self ):
4853 return len (self ._records )
4954
55+ def get_pool_token_supply (self ):
56+ return self ._pool_token_supply
57+
5058 def _mint_pool_share (self , amount : Decimal ):
51- self .pool_token_supply += amount
59+ self ._pool_token_supply += amount
5260
5361 def _burn_pool_share (self , amount : Decimal ):
54- self .pool_token_supply -= amount
62+ self ._pool_token_supply -= amount
5563
5664 def set_swap_fee (self , amount : Decimal ):
5765 self ._swap_fee = amount
5866
59- def bind (self , token : str , balance : Decimal , denorm : int ):
67+ def bind (self , token : str , balance : Decimal , denorm : int ) -> Decimal :
6068
6169 if self ._records .get (token ) is not None and self ._records .get (token ).bound is True :
62- raise Exception ('ERR_ALREADY_BOUND ' )
70+ raise Exception ('ERR_IS_BOUND ' )
6371 # TODO limit number of tokens to MAX_BOUND_TOKENS
72+ if len (self ._records ) >= MAX_BOUND_TOKENS :
73+ raise Exception ("ERR_MAX_TOKENS" )
6474 self ._records [token ] = TokenRecord (True , token , 0 , 0 )
65- self .rebind (token , balance , denorm )
75+ return self .rebind (token , balance , denorm )
6676
67- def rebind (self , token : str , balance : Decimal , denorm : int ):
68- if not self ._records [token ].bound :
77+ def rebind (self , token : str , balance : Decimal , denorm : int ) -> Decimal :
78+ print (MIN_BALANCE )
79+ print (balance )
80+ if self ._records .get (token ) is None or self ._records .get (token ).bound is False :
6981 raise Exception ("ERR_NOT_BOUND" )
70- # TODO if self._finalized:
71- # raise Exception("ERR_IS_FINALIZED")
72- # TODO require(denorm >= MIN_WEIGHT, "ERR_MIN_WEIGHT")
73- # TODO require(denorm <= MAX_WEIGHT, "ERR_MAX_WEIGHT")
74- # TODO require(balance >= MIN_BALANCE, "ERR_MIN_BALANCE")
82+ if denorm < MIN_WEIGHT :
83+ raise Exception ("ERR_MIN_WEIGHT" )
84+ if denorm > MAX_WEIGHT :
85+ raise Exception ("ERR_MAX_WEIGHT" )
86+ if balance < MIN_BALANCE :
87+ raise Exception ("ERR_MIN_BALANCE" )
7588 old_weight = self ._records [token ].denorm
7689 if denorm > old_weight :
7790 self .total_weight = self .total_weight + (denorm - old_weight )
78- # TODO if total_weight <= MAX_TOTAL_WEIGHT, "ERR_MAX_TOTAL_WEIGHT")
91+ if self .total_weight > MAX_TOTAL_WEIGHT :
92+ raise Exception ("ERR_MAX_TOTAL_WEIGHT" )
7993 elif denorm < old_weight :
8094 self .total_weight = self .total_weight + (old_weight - denorm )
8195 self ._records [token ].denorm = denorm
82- # old_balance = self._records[token].balance
96+ old_balance = self ._records [token ].balance
8397 self ._records [token ].balance = balance
84- # TODO simulate token exit fees and transfers for rebind?
85- # if balance > old_balance:
86- # NOTE: we are not simulating token transfer yet
87- # _pullUnderlying(token, msg.sender, bsub(balance, old_balance))
88- # pass
89- # elif balance < old_balance:
90- # # In this case liquidity is being withdrawn, so charge EXIT_FEE
91- # uint tokenBalanceWithdrawn = bsub(old_balance, balance)
92- # uint tokenExitFee = bmul(tokenBalanceWithdrawn, EXIT_FEE)
93- # _pushUnderlying(token, msg.sender, bsub(tokenBalanceWithdrawn, tokenExitFee))
94- # _pushUnderlying(token, _factory, tokenExitFee)
95-
96- def unbind (self ):
97- # TODO check need for this
98- pass
98+ if balance > old_balance :
99+ return - (balance - old_balance )
100+ elif balance < old_balance :
101+ # In this case liquidity is being withdrawn, so charge EXIT_FEE
102+ token_balance_withdrawn = old_balance - balance
103+ token_exit_fee = token_balance_withdrawn * EXIT_FEE
104+ self .factory_fees += token_exit_fee
105+ return token_balance_withdrawn - token_exit_fee
106+
107+ def unbind (self , token : str ) -> dict :
108+ if self ._records .get (token ) is None or self ._records .get (token ).bound is False :
109+ raise Exception ("ERR_NOT_BOUND" )
110+ record = self ._records [token ]
111+
112+ token_balance = record .balance
113+ token_exit_fee = token_balance * EXIT_FEE
114+
115+ self .total_weight -= record .denorm
116+ del self ._records [token ]
117+ self .factory_fees += token_exit_fee
118+ return {token : token_balance - token_exit_fee }
99119
100120 def get_spot_price (self , token_in : str , token_out : str ) -> Decimal :
101121 min_pool_amount_out = self ._records [token_in ]
@@ -107,26 +127,25 @@ def get_spot_price_sans_fee(self, token_in: str, token_out: str) -> Decimal:
107127 out_record = self ._records [token_out ]
108128 return self .calc_spot_price (min_pool_amount_out .balance , min_pool_amount_out .denorm , out_record .balance , out_record .denorm , Decimal ('0' ))
109129
110- def join_pool (self , pool_amount_out : Decimal , max_amounts_in : dict ) -> Decimal :
111- # TODO
112- # require(_finalized, "ERR_NOT_FINALIZED")
113- ratio = pool_amount_out / self .pool_token_supply
114- # TODO require(ratio != 0, "ERR_MATH_APPROX")
115-
130+ def join_pool (self , pool_amount_out : Decimal , max_amounts_in : dict ) -> dict :
131+ ratio = pool_amount_out / self ._pool_token_supply
132+ if ratio == 0 :
133+ return Exception ("ERR_MATH_APPROX" )
134+ results = {}
116135 for token in self ._records :
117136 record = self ._records [token ]
118137 token_amount_in = ratio * record .balance
119- # TODO require(token_amount_in != 0, "ERR_MATH_APPROX")
138+ if token_amount_in == 0 :
139+ return Exception ("ERR_MATH_APPROX" )
120140 if token_amount_in > max_amounts_in [token ]:
121141 raise Exception ('ERR_LIMIT_IN' )
122142 record .balance += token_amount_in
123- # TODO: balances of the rest of tokens should be pulled
124- # _pullUnderlying(t, msg.sender, token_amount_in)
143+ results [token ] = token_amount_in
125144 self ._mint_pool_share (pool_amount_out )
126- return pool_amount_out
145+ return results
127146
128147 def exit_pool (self , pool_amount_in : Decimal , min_amounts_out : dict ) -> dict :
129- pool_total = self .pool_token_supply
148+ pool_total = self ._pool_token_supply
130149 exit_fee = pool_amount_in * EXIT_FEE
131150 pool_amount_in_afer_exit_fee = pool_amount_in - exit_fee
132151 ratio = pool_amount_in_afer_exit_fee / pool_total
@@ -189,7 +208,6 @@ def swap_exact_amount_in(self, token_in: str, token_amount_in: Decimal, token_ou
189208 token_weight_out = out_record .denorm ,
190209 swap_fee = self ._swap_fee
191210 )
192- # TODO do we need this safety checks
193211 if spot_price_after < spot_price_before :
194212 raise Exception ("ERR_MATH_APPROX" )
195213 if spot_price_after > max_price :
@@ -267,7 +285,7 @@ def swap_exact_amount_out(self, token_in: str, max_amount_in: Decimal, token_out
267285 # @param minPoolAmountOut - minimum of pool tokens to receive
268286 # @return poolAmountOut - amount of pool tokens minted and transferred
269287 def join_swap_extern_amount_in (self , token_in : str , token_amount_in : Decimal , min_pool_amount_out : Decimal ) -> Decimal :
270- # require(_finalized, "ERR_NOT_FINALIZED");
288+ # require(_finalized, "ERR_NOT_FINALIZED")
271289 if not self ._records [token_in ].bound :
272290 raise Exception ("ERR_NOT_BOUND" )
273291 if token_amount_in > self ._records [token_in ].balance * MAX_IN_RATIO :
@@ -278,7 +296,7 @@ def join_swap_extern_amount_in(self, token_in: str, token_amount_in: Decimal, mi
278296 pool_amount_out = self .calc_pool_out_given_single_in (
279297 token_balance_in = in_record .balance ,
280298 token_weight_in = in_record .denorm ,
281- pool_supply = self .pool_token_supply ,
299+ pool_supply = self ._pool_token_supply ,
282300 total_weight = self .total_weight ,
283301 token_amount_in = token_amount_in ,
284302 swap_fee = self ._swap_fee
@@ -291,8 +309,8 @@ def join_swap_extern_amount_in(self, token_in: str, token_amount_in: Decimal, mi
291309
292310 self ._mint_pool_share (pool_amount_out )
293311 # NOTE user balance can be inferred from params (substract tai), pool out is already returning
294- # _pushPoolShare(msg.sender, pool_amount_out);
295- # _pullUnderlying(token_in, msg.sender, token_amount_in);
312+ # _pushPoolShare(msg.sender, pool_amount_out)
313+ # _pullUnderlying(token_in, msg.sender, token_amount_in)
296314 return pool_amount_out
297315
298316
@@ -306,7 +324,7 @@ def join_swap_pool_amount_out(self, token_in: str, pool_amount_out: Decimal, max
306324 token_amount_in = self .calc_single_in_given_pool_out (
307325 token_balance_in = in_record .balance ,
308326 token_weight_in = in_record .denorm ,
309- pool_supply = self .pool_token_supply ,
327+ pool_supply = self ._pool_token_supply ,
310328 total_weight = self .total_weight ,
311329 pool_amount_out = pool_amount_out ,
312330 swap_fee = self ._swap_fee )
@@ -322,8 +340,69 @@ def join_swap_pool_amount_out(self, token_in: str, pool_amount_out: Decimal, max
322340
323341 in_record .balance = in_record .balance + token_amount_in
324342 self ._mint_pool_share (pool_amount_out )
325- # _pushPoolShare(msg.sender, poolAmountOut);
326- # _pullUnderlying(tokenIn, msg.sender, tokenAmountIn);
343+ # NOTE not modeling balance change for sender
344+ # _pushPoolShare(msg.sender, poolAmountOut)
345+ # _pullUnderlying(tokenIn, msg.sender, tokenAmountIn)
327346
328347 return token_amount_in
348+
349+ def exit_swap_pool_amount_in (self , token_out : str , pool_amount_in : Decimal , min_amount_out : Decimal ) -> Decimal :
350+ if not self ._records [token_out ].bound :
351+ raise Exception ("ERR_NOT_BOUND" )
352+
353+ out_record = self ._records [token_out ]
354+
355+ token_amount_out = self .calc_single_out_given_pool_in (
356+ token_balance_out = out_record .balance ,
357+ token_weight_out = out_record .denorm ,
358+ pool_supply = self ._pool_token_supply ,
359+ total_weight = self .total_weight ,
360+ pool_amount_in = pool_amount_in ,
361+ swap_fee = self ._swap_fee )
362+
363+ if token_amount_out < min_amount_out :
364+ raise Exception ("ERR_LIMIT_OUT" )
365+ if token_amount_out > out_record .balance * MAX_OUT_RATIO :
366+ raise Exception ("ERR_MAX_OUT_RATIO" )
367+
368+ out_record .balance = out_record .balance - token_amount_out
369+
370+ exit_fee = pool_amount_in * EXIT_FEE
371+ self ._burn_pool_share (pool_amount_in - exit_fee )
372+
373+ # _pushPoolShare(_factory, exit_fee)
374+ # _pushUnderlying(token_out, msg.sender, token_amount_out)
375+
376+ return token_amount_out
377+
378+ def exit_swap_extern_amount_out (self , token_out : str , token_amount_out : Decimal , max_pool_amount_in : Decimal ) -> Decimal :
379+ if not self ._records [token_out ].bound :
380+ raise Exception ("ERR_NOT_BOUND" )
381+
382+ out_record = self ._records [token_out ]
383+ if token_amount_out > out_record .balance * MAX_OUT_RATIO :
384+ raise Exception ("ERR_MAX_OUT_RATIO" )
385+
386+
387+ pool_amount_in = self .calc_pool_in_given_single_out (
388+ token_balance_out = out_record .balance ,
389+ token_weight_out = out_record .denorm ,
390+ pool_supply = self ._pool_token_supply ,
391+ total_weight = self .total_weight ,
392+ token_amount_out = token_amount_out ,
393+ swap_fee = self ._swap_fee
394+ )
395+ if pool_amount_in == 0 :
396+ raise Exception ("ERR_MATH_APPROX" )
397+ if pool_amount_in > max_pool_amount_in :
398+ raise Exception ("ERR_LIMIT_IN" )
399+
400+ out_record .balance -= token_amount_out
401+
402+ exitFee = pool_amount_in * EXIT_FEE
403+ self ._burn_pool_share (pool_amount_in - exitFee )
404+
405+ #_pushPoolShare(_factory, exitFee);
406+ return pool_amount_in ;
407+
329408
0 commit comments