Skip to content

Commit 7d0291d

Browse files
committed
tested and modelled basic BPool
1 parent a0acb62 commit 7d0291d

File tree

3 files changed

+176
-68
lines changed

3 files changed

+176
-68
lines changed

model/balancer_constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
MIN_WEIGHT = BONE
1111
MAX_WEIGHT = BONE * Decimal('50')
1212
MAX_TOTAL_WEIGHT = BONE * Decimal('50')
13-
MIN_BALANCE = BONE / Decimal('0.000001')
13+
MIN_BALANCE = Decimal('0.000000000001')
1414
INIT_POOL_SUPPLY = BONE * Decimal('100')
1515
MIN_BPOW_BASE = Decimal('0.000000000000000001')
1616
MAX_BPOW_BASE = (Decimal('2') * BONE) - ONE_WEI

model/balancer_pool.py

Lines changed: 130 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from collections import namedtuple
22
from 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
56
from model.balancer_math import BalancerMath
67
from 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

Comments
 (0)