diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml new file mode 100644 index 0000000..ebef91b --- /dev/null +++ b/.github/workflows/linters.yml @@ -0,0 +1,15 @@ +name: Lint + +on: [push, pull_request] + +permissions: + contents: read + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: psf/black@stable + with: + version: "~= 22.0" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..b32d3bf --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,6 @@ +repos: + - repo: https://github.com/psf/black + rev: 22.10.0 + hooks: + - id: black + exclude: \.py-tpl$ diff --git a/balpy/__init__.py b/balpy/__init__.py index e7f8991..87f3acb 100644 --- a/balpy/__init__.py +++ b/balpy/__init__.py @@ -1,3 +1,2 @@ from .balancerErrors import handleException from balpy import balpy - diff --git a/balpy/balancerErrors.py b/balpy/balancerErrors.py index a472ca1..9cd34b5 100644 --- a/balpy/balancerErrors.py +++ b/balpy/balancerErrors.py @@ -1,107 +1,110 @@ -codes = {0:"ADD_OVERFLOW", - 1:"SUB_OVERFLOW", - 2:"SUB_UNDERFLOW", - 3:"MUL_OVERFLOW", - 4:"ZERO_DIVISION", - 5:"DIV_INTERNAL", - 6:"X_OUT_OF_BOUNDS", - 7:"Y_OUT_OF_BOUNDS", - 8:"PRODUCT_OUT_OF_BOUNDS", - 9:"INVALID_EXPONENT", - 100:"OUT_OF_BOUNDS", - 101:"UNSORTED_ARRAY", - 102:"UNSORTED_TOKENS", - 103:"INPUT_LENGTH_MISMATCH", - 104:"ZERO_TOKEN", - 200:"MIN_TOKENS", - 201:"MAX_TOKENS", - 202:"MAX_SWAP_FEE_PERCENTAGE", - 203:"MIN_SWAP_FEE_PERCENTAGE", - 204:"MINIMUM_BPT", - 205:"CALLER_NOT_VAULT", - 206:"UNINITIALIZED", - 207:"BPT_IN_MAX_AMOUNT", - 208:"BPT_OUT_MIN_AMOUNT", - 209:"EXPIRED_PERMIT", - 300:"MIN_AMP", - 301:"MAX_AMP", - 302:"MIN_WEIGHT", - 303:"MAX_STABLE_TOKENS", - 304:"MAX_IN_RATIO", - 305:"MAX_OUT_RATIO", - 306:"MIN_BPT_IN_FOR_TOKEN_OUT", - 307:"MAX_OUT_BPT_FOR_TOKEN_IN", - 308:"NORMALIZED_WEIGHT_INVARIANT", - 309:"INVALID_TOKEN", - 310:"UNHANDLED_JOIN_KIND", - 311:"ZERO_INVARIANT", - 400:"REENTRANCY", - 401:"SENDER_NOT_ALLOWED", - 402:"PAUSED", - 403:"PAUSE_WINDOW_EXPIRED", - 404:"MAX_PAUSE_WINDOW_DURATION", - 405:"MAX_BUFFER_PERIOD_DURATION", - 406:"INSUFFICIENT_BALANCE", - 407:"INSUFFICIENT_ALLOWANCE", - 408:"ERC20_TRANSFER_FROM_ZERO_ADDRESS", - 409:"ERC20_TRANSFER_TO_ZERO_ADDRESS", - 410:"ERC20_MINT_TO_ZERO_ADDRESS", - 411:"ERC20_BURN_FROM_ZERO_ADDRESS", - 412:"ERC20_APPROVE_FROM_ZERO_ADDRESS", - 413:"ERC20_APPROVE_TO_ZERO_ADDRESS", - 414:"ERC20_TRANSFER_EXCEEDS_ALLOWANCE", - 415:"ERC20_DECREASED_ALLOWANCE_BELOW_ZERO", - 416:"ERC20_TRANSFER_EXCEEDS_BALANCE", - 417:"ERC20_BURN_EXCEEDS_ALLOWANCE", - 418:"SAFE_ERC20_CALL_FAILED", - 419:"ADDRESS_INSUFFICIENT_BALANCE", - 420:"ADDRESS_CANNOT_SEND_VALUE", - 421:"SAFE_CAST_VALUE_CANT_FIT_INT256", - 422:"GRANT_SENDER_NOT_ADMIN", - 423:"REVOKE_SENDER_NOT_ADMIN", - 424:"RENOUNCE_SENDER_NOT_ALLOWED", - 425:"BUFFER_PERIOD_EXPIRED", - 500:"INVALID_POOL_ID", - 501:"CALLER_NOT_POOL", - 502:"SENDER_NOT_ASSET_MANAGER", - 503:"USER_DOESNT_ALLOW_RELAYER", - 504:"INVALID_SIGNATURE", - 505:"EXIT_BELOW_MIN", - 506:"JOIN_ABOVE_MAX", - 507:"SWAP_LIMIT", - 508:"SWAP_DEADLINE", - 509:"CANNOT_SWAP_SAME_TOKEN", - 510:"UNKNOWN_AMOUNT_IN_FIRST_SWAP", - 511:"MALCONSTRUCTED_MULTIHOP_SWAP", - 512:"INTERNAL_BALANCE_OVERFLOW", - 513:"INSUFFICIENT_INTERNAL_BALANCE", - 514:"INVALID_ETH_INTERNAL_BALANCE", - 515:"INVALID_POST_LOAN_BALANCE", - 516:"INSUFFICIENT_ETH", - 517:"UNALLOCATED_ETH", - 518:"ETH_TRANSFER", - 519:"CANNOT_USE_ETH_SENTINEL", - 520:"TOKENS_MISMATCH", - 521:"TOKEN_NOT_REGISTERED", - 522:"TOKEN_ALREADY_REGISTERED", - 523:"TOKENS_ALREADY_SET", - 524:"TOKENS_LENGTH_MUST_BE_2", - 525:"NONZERO_TOKEN_BALANCE", - 526:"BALANCE_TOTAL_OVERFLOW", - 600:"SWAP_FEE_PERCENTAGE_TOO_HIGH", - 601:"FLASH_LOAN_FEE_PERCENTAGE_TOO_HIGH", - 602:"INSUFFICIENT_FLASH_LOAN_FEE_AMOUNT" -}; +codes = { + 0: "ADD_OVERFLOW", + 1: "SUB_OVERFLOW", + 2: "SUB_UNDERFLOW", + 3: "MUL_OVERFLOW", + 4: "ZERO_DIVISION", + 5: "DIV_INTERNAL", + 6: "X_OUT_OF_BOUNDS", + 7: "Y_OUT_OF_BOUNDS", + 8: "PRODUCT_OUT_OF_BOUNDS", + 9: "INVALID_EXPONENT", + 100: "OUT_OF_BOUNDS", + 101: "UNSORTED_ARRAY", + 102: "UNSORTED_TOKENS", + 103: "INPUT_LENGTH_MISMATCH", + 104: "ZERO_TOKEN", + 200: "MIN_TOKENS", + 201: "MAX_TOKENS", + 202: "MAX_SWAP_FEE_PERCENTAGE", + 203: "MIN_SWAP_FEE_PERCENTAGE", + 204: "MINIMUM_BPT", + 205: "CALLER_NOT_VAULT", + 206: "UNINITIALIZED", + 207: "BPT_IN_MAX_AMOUNT", + 208: "BPT_OUT_MIN_AMOUNT", + 209: "EXPIRED_PERMIT", + 300: "MIN_AMP", + 301: "MAX_AMP", + 302: "MIN_WEIGHT", + 303: "MAX_STABLE_TOKENS", + 304: "MAX_IN_RATIO", + 305: "MAX_OUT_RATIO", + 306: "MIN_BPT_IN_FOR_TOKEN_OUT", + 307: "MAX_OUT_BPT_FOR_TOKEN_IN", + 308: "NORMALIZED_WEIGHT_INVARIANT", + 309: "INVALID_TOKEN", + 310: "UNHANDLED_JOIN_KIND", + 311: "ZERO_INVARIANT", + 400: "REENTRANCY", + 401: "SENDER_NOT_ALLOWED", + 402: "PAUSED", + 403: "PAUSE_WINDOW_EXPIRED", + 404: "MAX_PAUSE_WINDOW_DURATION", + 405: "MAX_BUFFER_PERIOD_DURATION", + 406: "INSUFFICIENT_BALANCE", + 407: "INSUFFICIENT_ALLOWANCE", + 408: "ERC20_TRANSFER_FROM_ZERO_ADDRESS", + 409: "ERC20_TRANSFER_TO_ZERO_ADDRESS", + 410: "ERC20_MINT_TO_ZERO_ADDRESS", + 411: "ERC20_BURN_FROM_ZERO_ADDRESS", + 412: "ERC20_APPROVE_FROM_ZERO_ADDRESS", + 413: "ERC20_APPROVE_TO_ZERO_ADDRESS", + 414: "ERC20_TRANSFER_EXCEEDS_ALLOWANCE", + 415: "ERC20_DECREASED_ALLOWANCE_BELOW_ZERO", + 416: "ERC20_TRANSFER_EXCEEDS_BALANCE", + 417: "ERC20_BURN_EXCEEDS_ALLOWANCE", + 418: "SAFE_ERC20_CALL_FAILED", + 419: "ADDRESS_INSUFFICIENT_BALANCE", + 420: "ADDRESS_CANNOT_SEND_VALUE", + 421: "SAFE_CAST_VALUE_CANT_FIT_INT256", + 422: "GRANT_SENDER_NOT_ADMIN", + 423: "REVOKE_SENDER_NOT_ADMIN", + 424: "RENOUNCE_SENDER_NOT_ALLOWED", + 425: "BUFFER_PERIOD_EXPIRED", + 500: "INVALID_POOL_ID", + 501: "CALLER_NOT_POOL", + 502: "SENDER_NOT_ASSET_MANAGER", + 503: "USER_DOESNT_ALLOW_RELAYER", + 504: "INVALID_SIGNATURE", + 505: "EXIT_BELOW_MIN", + 506: "JOIN_ABOVE_MAX", + 507: "SWAP_LIMIT", + 508: "SWAP_DEADLINE", + 509: "CANNOT_SWAP_SAME_TOKEN", + 510: "UNKNOWN_AMOUNT_IN_FIRST_SWAP", + 511: "MALCONSTRUCTED_MULTIHOP_SWAP", + 512: "INTERNAL_BALANCE_OVERFLOW", + 513: "INSUFFICIENT_INTERNAL_BALANCE", + 514: "INVALID_ETH_INTERNAL_BALANCE", + 515: "INVALID_POST_LOAN_BALANCE", + 516: "INSUFFICIENT_ETH", + 517: "UNALLOCATED_ETH", + 518: "ETH_TRANSFER", + 519: "CANNOT_USE_ETH_SENTINEL", + 520: "TOKENS_MISMATCH", + 521: "TOKEN_NOT_REGISTERED", + 522: "TOKEN_ALREADY_REGISTERED", + 523: "TOKENS_ALREADY_SET", + 524: "TOKENS_LENGTH_MUST_BE_2", + 525: "NONZERO_TOKEN_BALANCE", + 526: "BALANCE_TOTAL_OVERFLOW", + 600: "SWAP_FEE_PERCENTAGE_TOO_HIGH", + 601: "FLASH_LOAN_FEE_PERCENTAGE_TOO_HIGH", + 602: "INSUFFICIENT_FLASH_LOAN_FEE_AMOUNT", +} + def translateError(code): - return(codes[code]) + return codes[code] + def handleException(error): - stringError = str('{}'.format(error)); - if "BAL#" in stringError: - code = int(stringError.split("BAL#")[1]) - balError = translateError(code); - descriptiveError = "Balancer Smart Contract threw error: " + balError; - return(descriptiveError); - else: - return(stringError); + stringError = str("{}".format(error)) + if "BAL#" in stringError: + code = int(stringError.split("BAL#")[1]) + balError = translateError(code) + descriptiveError = "Balancer Smart Contract threw error: " + balError + return descriptiveError + else: + return stringError diff --git a/balpy/balancerv2cad/scripts/project_helper.py b/balpy/balancerv2cad/scripts/project_helper.py index 7907dc5..5a2d08b 100644 --- a/balpy/balancerv2cad/scripts/project_helper.py +++ b/balpy/balancerv2cad/scripts/project_helper.py @@ -6,6 +6,7 @@ import sys import balancerv2cad as ks + # from balancerv2cad import PKG_NAME, BASE_DIR @@ -15,14 +16,12 @@ def stub_gen() -> None: for the package """ out_file = f"src/{ks.__package__}-stubs" - path = str(ks.BASE_DIR / 'src' / f'{ks.__package__}') + path = str(ks.BASE_DIR / "src" / f"{ks.__package__}") try: - sp.run(f'stubgen -p {ks.__package__} -o {out_file}', - check=True, - shell=True) - sp.run(f'mypy {path}', check=True, shell=True) + sp.run(f"stubgen -p {ks.__package__} -o {out_file}", check=True, shell=True) + sp.run(f"mypy {path}", check=True, shell=True) except sp.CalledProcessError as error: - print(f'{error}') + print(f"{error}") sys.exit(1) @@ -30,11 +29,11 @@ def run_analyzer() -> None: """ Run the mypy static type checking analyzer """ - path = str(ks.BASE_DIR / 'src' / f'{ks.__package__}') + path = str(ks.BASE_DIR / "src" / f"{ks.__package__}") try: - sp.run(f'mypy {path}', check=True, shell=True) + sp.run(f"mypy {path}", check=True, shell=True) except sp.CalledProcessError as error: - print(f'{error}') + print(f"{error}") sys.exit(1) @@ -46,7 +45,7 @@ def run_tests(): These functions can be as complicated or as simple as you like """ try: - sp.run('pytest --capture=tee-sys', check=True, shell=True) + sp.run("pytest --capture=tee-sys", check=True, shell=True) except sp.CalledProcessError as error: print(f"{error}") sys.exit(1) diff --git a/balpy/balancerv2cad/src/balancerv2cad/BalancerConstants.py b/balpy/balancerv2cad/src/balancerv2cad/BalancerConstants.py index 4d0e0b2..ea54fbe 100644 --- a/balpy/balancerv2cad/src/balancerv2cad/BalancerConstants.py +++ b/balpy/balancerv2cad/src/balancerv2cad/BalancerConstants.py @@ -1,16 +1,16 @@ from decimal import Decimal -BONE = Decimal('1') -MIN_FEE = Decimal('0.000001') -MAX_FEE = Decimal('0.1') -INIT_POOL_SUPPLY = BONE * Decimal('100') +BONE = Decimal("1") +MIN_FEE = Decimal("0.000001") +MAX_FEE = Decimal("0.1") +INIT_POOL_SUPPLY = BONE * Decimal("100") MIN_BOUND_TOKENS = 2 MAX_BOUND_TOKENS = 8 -AMPLIFICATION_PARAMETER = Decimal('200') +AMPLIFICATION_PARAMETER = Decimal("200") MIN_WEIGHT = 0.01 _MAX_WEIGHTED_TOKENS = 100 _MAX_IN_RATIO = 0.3 _MAX_OUT_RATIO = 0.3 _MAX_INVARIANT_RATIO = 3 -_MIN_INVARIANT_RATIO = 0.7 \ No newline at end of file +_MIN_INVARIANT_RATIO = 0.7 diff --git a/balpy/balancerv2cad/src/balancerv2cad/StableMath.py b/balpy/balancerv2cad/src/balancerv2cad/StableMath.py index 6e6bda8..50797b6 100644 --- a/balpy/balancerv2cad/src/balancerv2cad/StableMath.py +++ b/balpy/balancerv2cad/src/balancerv2cad/StableMath.py @@ -5,6 +5,8 @@ from balancerv2cad.util import * getcontext().prec = 28 + + @dataclass class BalancerMathResult: result: Decimal @@ -13,7 +15,7 @@ class BalancerMathResult: class StableMath: -# ------------------------------------- + # ------------------------------------- @staticmethod def calculateInvariant(amplificationParameter: Decimal, balances: list) -> Decimal: @@ -30,33 +32,42 @@ def calculateInvariant(amplificationParameter: Decimal, balances: list) -> Decim for bal in balances: bal_sum += bal num_tokens = len(balances) - if(bal_sum==0): + if bal_sum == 0: return 0 prevInvariant = 0 invariant = bal_sum - ampTimesTotal = amplificationParameter*num_tokens + ampTimesTotal = amplificationParameter * num_tokens for i in range(255): - P_D = num_tokens*balances[0] + P_D = num_tokens * balances[0] for j in range(1, num_tokens): - P_D = ceil(((P_D*balances[j])*num_tokens)/invariant) + P_D = ceil(((P_D * balances[j]) * num_tokens) / invariant) prevInvariant = invariant - invariant = ceil(((num_tokens*invariant)*invariant + (ampTimesTotal*bal_sum)*P_D) / ((num_tokens + 1)*invariant + (ampTimesTotal - 1)*P_D)) - if(invariant > prevInvariant): - if(invariant - prevInvariant <= 1): + invariant = ceil( + ((num_tokens * invariant) * invariant + (ampTimesTotal * bal_sum) * P_D) + / ((num_tokens + 1) * invariant + (ampTimesTotal - 1) * P_D) + ) + if invariant > prevInvariant: + if invariant - prevInvariant <= 1: break - elif(prevInvariant - invariant <= 1): + elif prevInvariant - invariant <= 1: break return Decimal(invariant) - - # Flow of calculations: # amountsTokenOut -> amountsOutProportional -> # amountOutPercentageExcess -> amountOutBeforeFee -> newInvariant -> amountBPTIn @staticmethod - def calcBptInGivenExactTokensOut(amplificationParameter: Decimal, balances: list, amountsOut: list, bptTotalSupply: Decimal, swapFee: Decimal) -> Decimal: - currentInvariants = StableMath.calculateInvariant(amplificationParameter, balances) + def calcBptInGivenExactTokensOut( + amplificationParameter: Decimal, + balances: list, + amountsOut: list, + bptTotalSupply: Decimal, + swapFee: Decimal, + ) -> Decimal: + currentInvariants = StableMath.calculateInvariant( + amplificationParameter, balances + ) # calculate the sum of all token balances sumBalances = Decimal(0) for i in range(len(balances)): @@ -68,8 +79,12 @@ def calcBptInGivenExactTokensOut(amplificationParameter: Decimal, balances: list getcontext().prec = 28 for i in range(len(balances)): currentWeight = divUp(balances[i], sumBalances) - tokenBalanceRatiosWithoutFee[i] = balances[i] - divUp(amountsOut[i], balances[i]) - weightedBalanceRatio = weightedBalanceRatio + mulUp(tokenBalanceRatiosWithoutFee[i], currentWeight) + tokenBalanceRatiosWithoutFee[i] = balances[i] - divUp( + amountsOut[i], balances[i] + ) + weightedBalanceRatio = weightedBalanceRatio + mulUp( + tokenBalanceRatiosWithoutFee[i], currentWeight + ) newBalances = [] for i in range(len(balances)): @@ -77,23 +92,42 @@ def calcBptInGivenExactTokensOut(amplificationParameter: Decimal, balances: list if weightedBalanceRatio <= tokenBalanceRatiosWithoutFee[i]: tokenBalancePercentageExcess = 0 else: - tokenBalancePercentageExcess = weightedBalanceRatio - Decimal(divUp(tokenBalanceRatiosWithoutFee[i], Decimal(complement(tokenBalanceRatiosWithoutFee[i])))) + tokenBalancePercentageExcess = weightedBalanceRatio - Decimal( + divUp( + tokenBalanceRatiosWithoutFee[i], + Decimal(complement(tokenBalanceRatiosWithoutFee[i])), + ) + ) swapFeeExcess = mulUp(swapFee, Decimal(tokenBalancePercentageExcess)) - amountOutBeforeFee = Decimal(divUp(amountsOut[i], complement(swapFeeExcess))) + amountOutBeforeFee = Decimal( + divUp(amountsOut[i], complement(swapFeeExcess)) + ) newBalances.append(balances[i] - amountOutBeforeFee) # get the new invariant, taking Decimalo account swap fees - newInvariant = StableMath.calculateInvariant(amplificationParameter, newBalances) + newInvariant = StableMath.calculateInvariant( + amplificationParameter, newBalances + ) # return amountBPTIn - return bptTotalSupply * Decimal(divUp(newInvariant, complement(currentInvariants))) - + return bptTotalSupply * Decimal( + divUp(newInvariant, complement(currentInvariants)) + ) @staticmethod - def calcBptOutGivenExactTokensIn(amplificationParameter: Decimal, balances: list, amountsIn: list, bptTotalSupply: Decimal, swapFee: Decimal, swapFeePercentage: Decimal) -> Decimal: + def calcBptOutGivenExactTokensIn( + amplificationParameter: Decimal, + balances: list, + amountsIn: list, + bptTotalSupply: Decimal, + swapFee: Decimal, + swapFeePercentage: Decimal, + ) -> Decimal: # get current invariant - currentInvariant = StableMath.calculateInvariant(amplificationParameter, balances) + currentInvariant = StableMath.calculateInvariant( + amplificationParameter, balances + ) sumBalances = Decimal(0) for i in range(len(balances)): @@ -104,8 +138,12 @@ def calcBptOutGivenExactTokensIn(amplificationParameter: Decimal, balances: list weightedBalanceRatio = 0 for i in range(len(balances)): currentWeight = divDown(Decimal(balances[i]), Decimal(sumBalances)) - tokenBalanceRatiosWithoutFee.append(balances[i] + divDown(Decimal(amountsIn[i]), Decimal(balances[i]))) - weightedBalanceRatio = weightedBalanceRatio + mulDown(tokenBalanceRatiosWithoutFee[i], currentWeight) + tokenBalanceRatiosWithoutFee.append( + balances[i] + divDown(Decimal(amountsIn[i]), Decimal(balances[i])) + ) + weightedBalanceRatio = weightedBalanceRatio + mulDown( + tokenBalanceRatiosWithoutFee[i], currentWeight + ) tokenBalancePercentageExcess = Decimal(0) newBalances = [] @@ -113,22 +151,36 @@ def calcBptOutGivenExactTokensIn(amplificationParameter: Decimal, balances: list if weightedBalanceRatio >= tokenBalanceRatiosWithoutFee[i]: tokenBalancePercentageExcess = Decimal(0) else: - tokenBalancePercentageExcess = tokenBalanceRatiosWithoutFee[i] - divUp(weightedBalanceRatio, (tokenBalanceRatiosWithoutFee[i])) - - swapFeeExcess = mulUp(Decimal(swapFeePercentage), tokenBalancePercentageExcess) - amountInAfterFee = mulDown(Decimal(amountsIn[i]), Decimal(complement(swapFeeExcess))) + tokenBalancePercentageExcess = tokenBalanceRatiosWithoutFee[i] - divUp( + weightedBalanceRatio, (tokenBalanceRatiosWithoutFee[i]) + ) + + swapFeeExcess = mulUp( + Decimal(swapFeePercentage), tokenBalancePercentageExcess + ) + amountInAfterFee = mulDown( + Decimal(amountsIn[i]), Decimal(complement(swapFeeExcess)) + ) newBalances.append(balances[i] + amountInAfterFee) # get new invariant, taking swap fees Decimalo account - newInvariant = Decimal(StableMath.calculateInvariant(amplificationParameter, newBalances)) + newInvariant = Decimal( + StableMath.calculateInvariant(amplificationParameter, newBalances) + ) # return amountBPTOut - return Decimal(mulDown(bptTotalSupply, divDown(newInvariant, currentInvariant))) # TODO omitted subtracting ONE from current_invariant + return Decimal( + mulDown(bptTotalSupply, divDown(newInvariant, currentInvariant)) + ) # TODO omitted subtracting ONE from current_invariant @staticmethod - - - def calcDueTokenProtocolSwapFeeAmount(amplificationParameter: Decimal, balances: list, lastInvariant: Decimal, tokenIndex: int, protocolSwapFeePercentage: float) -> Decimal: + def calcDueTokenProtocolSwapFeeAmount( + amplificationParameter: Decimal, + balances: list, + lastInvariant: Decimal, + tokenIndex: int, + protocolSwapFeePercentage: float, + ) -> Decimal: # /************************************************************************************************************** # // oneTokenSwapFee - polynomial equation to solve // # // af = fee amount to calculate in one token // @@ -140,21 +192,29 @@ def calcDueTokenProtocolSwapFeeAmount(amplificationParameter: Decimal, balances: # // S = sum of final balances but f // # // P = product of final balances but f // # ******* - finalBalanceFeeToken = StableMath.getTokenBalanceGivenInvariantAndAllOtherBalances( - amplificationParameter, - balances, - lastInvariant, - tokenIndex) + finalBalanceFeeToken = ( + StableMath.getTokenBalanceGivenInvariantAndAllOtherBalances( + amplificationParameter, balances, lastInvariant, tokenIndex + ) + ) - if(balances[tokenIndex] > finalBalanceFeeToken): + if balances[tokenIndex] > finalBalanceFeeToken: accumulatedTokenSwapFees = balances[tokenIndex] - finalBalanceFeeToken else: accumulatedTokenSwapFees = 0 - return divDown(mulDown(accumulatedTokenSwapFees, Decimal(protocolSwapFeePercentage))) + return divDown( + mulDown(accumulatedTokenSwapFees, Decimal(protocolSwapFeePercentage)) + ) @staticmethod - def calcInGivenOut(amplificationParameter: Decimal, balances: list, tokenIndexIn: int, tokenIndexOut: int, tokenAmountOut: Decimal) -> Decimal: + def calcInGivenOut( + amplificationParameter: Decimal, + balances: list, + tokenIndexIn: int, + tokenIndexOut: int, + tokenAmountOut: Decimal, + ) -> Decimal: # /************************************************************************************************************** # // inGivenOut token x for y - polynomial equation to solve // @@ -172,20 +232,21 @@ def calcInGivenOut(amplificationParameter: Decimal, balances: list, tokenIndexIn balances[tokenIndexOut] = balances[tokenIndexOut] - tokenAmountOut finalBalanceIn = StableMath.getTokenBalanceGivenInvariantAndAllOtherBalances( - amplificationParameter, - balances, - invariant, - tokenIndexIn + amplificationParameter, balances, invariant, tokenIndexIn ) - balances[tokenIndexOut] = balances[tokenIndexOut]+ tokenAmountOut - result = finalBalanceIn - balances[tokenIndexIn] + Decimal(1/1e18) + balances[tokenIndexOut] = balances[tokenIndexOut] + tokenAmountOut + result = finalBalanceIn - balances[tokenIndexIn] + Decimal(1 / 1e18) return result @staticmethod - - - def calcOutGivenIn(amplificationParameter: Decimal, balances: list, tokenIndexIn: int, tokenIndexOut: int, tokenAmountIn: Decimal) -> Decimal: + def calcOutGivenIn( + amplificationParameter: Decimal, + balances: list, + tokenIndexIn: int, + tokenIndexOut: int, + tokenAmountIn: Decimal, + ) -> Decimal: # /************************************************************************************************************** # // outGivenIn token x for y - polynomial equation to solve // @@ -198,20 +259,24 @@ def calcOutGivenIn(amplificationParameter: Decimal, balances: list, tokenIndexIn # // S = sum of final balances but y // # // P = product of final balances but y // # **************************************************************************************************************/ - print("Context", "OUTGIVENIN" ) + print("Context", "OUTGIVENIN") invariant = StableMath.calculateInvariant(amplificationParameter, balances) print("Invariant", invariant) balances[tokenIndexIn] = balances[tokenIndexIn] + tokenAmountIn - finalBalanceOut = StableMath.getTokenBalanceGivenInvariantAndAllOtherBalances(amplificationParameter, balances, invariant, tokenIndexOut) + finalBalanceOut = StableMath.getTokenBalanceGivenInvariantAndAllOtherBalances( + amplificationParameter, balances, invariant, tokenIndexOut + ) print("FinalBalance Out", finalBalanceOut) balances[tokenIndexIn] = balances[tokenIndexIn] - tokenAmountIn - result = balances [tokenIndexOut] - finalBalanceOut # TODO took out .sub(1) at the end of this statement + result = ( + balances[tokenIndexOut] - finalBalanceOut + ) # TODO took out .sub(1) at the end of this statement print(result) - print("END-CONTEXT", "OUTGIVENIN" ) + print("END-CONTEXT", "OUTGIVENIN") return result # Flow of calculations: @@ -219,16 +284,25 @@ def calcOutGivenIn(amplificationParameter: Decimal, balances: list, tokenIndexIn # amountInPercentageExcess -> amountIn @staticmethod - - - def calcTokenInGivenExactBptOut(amplificationParameter: Decimal, balances: list, tokenIndex: int, bptAmountOut: Decimal, bptTotalSupply: Decimal, swapFeePercentage: Decimal) -> Decimal: + def calcTokenInGivenExactBptOut( + amplificationParameter: Decimal, + balances: list, + tokenIndex: int, + bptAmountOut: Decimal, + bptTotalSupply: Decimal, + swapFeePercentage: Decimal, + ) -> Decimal: # Token in so we round up overall - #Get the current invariant - currentInvariant = Decimal(StableMath.calculateInvariant(amplificationParameter, balances)) + # Get the current invariant + currentInvariant = Decimal( + StableMath.calculateInvariant(amplificationParameter, balances) + ) # Calculate new invariant - newInvariant = divUp((bptTotalSupply + bptAmountOut), mulUp(bptTotalSupply, currentInvariant)) + newInvariant = divUp( + (bptTotalSupply + bptAmountOut), mulUp(bptTotalSupply, currentInvariant) + ) # First calculate the sum of all token balances, which will be used # to calculate the current weight of each token @@ -237,7 +311,11 @@ def calcTokenInGivenExactBptOut(amplificationParameter: Decimal, balances: list, sumBalances += balances[i] # get amountInAfterFee - newBalanceTokenIndex = StableMath.getTokenBalanceGivenInvariantAndAllOtherBalances(amplificationParameter, balances, newInvariant, tokenIndex) + newBalanceTokenIndex = ( + StableMath.getTokenBalanceGivenInvariantAndAllOtherBalances( + amplificationParameter, balances, newInvariant, tokenIndex + ) + ) amountInAfterFee = newBalanceTokenIndex - balances[tokenIndex] # Get tokenBalancePercentageExcess @@ -248,9 +326,9 @@ def calcTokenInGivenExactBptOut(amplificationParameter: Decimal, balances: list, return divUp(amountInAfterFee, complement(complement(swapFeeExcess))) @staticmethod - - - def calcTokensOutGivenExactBptIn(balances: list, bptAmountIn: Decimal, bptTotalSupply: Decimal) -> list: + def calcTokensOutGivenExactBptIn( + balances: list, bptAmountIn: Decimal, bptTotalSupply: Decimal + ) -> list: # /********************************************************************************************** # // exactBPTInForTokensOut // @@ -274,13 +352,22 @@ def calcTokensOutGivenExactBptIn(balances: list, bptAmountIn: Decimal, bptTotalS # amountOutPercentageExcess -> amountOut @staticmethod - - - def calcTokenOutGivenExactBptIn(amplificationParameter, balances: list, tokenIndex: int, bptAmountIn: Decimal, bptTotalSupply: Decimal, swapFeePercentage: Decimal) -> Decimal: + def calcTokenOutGivenExactBptIn( + amplificationParameter, + balances: list, + tokenIndex: int, + bptAmountIn: Decimal, + bptTotalSupply: Decimal, + swapFeePercentage: Decimal, + ) -> Decimal: # Get current invariant - currentInvariant = StableMath.calculateInvariant(amplificationParameter, balances) + currentInvariant = StableMath.calculateInvariant( + amplificationParameter, balances + ) # calculate the new invariant - newInvariant = bptTotalSupply - divUp(bptAmountIn, mulUp(bptTotalSupply, currentInvariant)) + newInvariant = bptTotalSupply - divUp( + bptAmountIn, mulUp(bptTotalSupply, currentInvariant) + ) # calculate the sum of all th etoken balances, which will be used to calculate # the current weight of each token @@ -289,7 +376,11 @@ def calcTokenOutGivenExactBptIn(amplificationParameter, balances: list, tokenInd sumBalances += balances[i] # get amountOutBeforeFee - newBalanceTokenIndex = StableMath.getTokenBalanceGivenInvariantAndAllOtherBalances(amplificationParameter, balances, newInvariant, tokenIndex) + newBalanceTokenIndex = ( + StableMath.getTokenBalanceGivenInvariantAndAllOtherBalances( + amplificationParameter, balances, newInvariant, tokenIndex + ) + ) amountOutBeforeFee = balances[tokenIndex] - newBalanceTokenIndex # calculate tokenBalancePercentageExcess @@ -300,33 +391,37 @@ def calcTokenOutGivenExactBptIn(amplificationParameter, balances: list, tokenInd return mulDown(amountOutBeforeFee, complement(swapFeeExcess)) - # ------------------------------------ @staticmethod - - - def getTokenBalanceGivenInvariantAndAllOtherBalances(amplificationParameter: Decimal, balances: List[Decimal], invariant: Decimal, tokenIndex: int) -> Decimal: + def getTokenBalanceGivenInvariantAndAllOtherBalances( + amplificationParameter: Decimal, + balances: List[Decimal], + invariant: Decimal, + tokenIndex: int, + ) -> Decimal: getcontext().prec = 28 ampTimesTotal = amplificationParameter * len(balances) bal_sum = Decimal(sum(balances)) P_D = len(balances) * balances[0] for i in range(1, len(balances)): - P_D = (P_D*balances[i]*len(balances))/invariant + P_D = (P_D * balances[i] * len(balances)) / invariant bal_sum -= balances[tokenIndex] - c = invariant*invariant/ampTimesTotal + c = invariant * invariant / ampTimesTotal c = divUp(mulUp(c, balances[tokenIndex]), P_D) b = bal_sum + divDown(invariant, ampTimesTotal) prevTokenbalance = 0 - tokenBalance = divUp((invariant*invariant+c), (invariant+b)) + tokenBalance = divUp((invariant * invariant + c), (invariant + b)) for i in range(255): prevTokenbalance = tokenBalance - tokenBalance = divUp((mulUp(tokenBalance,tokenBalance) + c),((tokenBalance*Decimal(2))+b-invariant)) - if(tokenBalance > prevTokenbalance): - if(tokenBalance-prevTokenbalance <= 1/1e18): + tokenBalance = divUp( + (mulUp(tokenBalance, tokenBalance) + c), + ((tokenBalance * Decimal(2)) + b - invariant), + ) + if tokenBalance > prevTokenbalance: + if tokenBalance - prevTokenbalance <= 1 / 1e18: break - elif(prevTokenbalance-tokenBalance <= 1/1e18): + elif prevTokenbalance - tokenBalance <= 1 / 1e18: break return tokenBalance - diff --git a/balpy/balancerv2cad/src/balancerv2cad/StablePool.py b/balpy/balancerv2cad/src/balancerv2cad/StablePool.py index 99011c9..177262c 100644 --- a/balpy/balancerv2cad/src/balancerv2cad/StablePool.py +++ b/balpy/balancerv2cad/src/balancerv2cad/StablePool.py @@ -6,25 +6,32 @@ class StablePool(StableMath): - def __init__(self, initial_pool_supply: Decimal = INIT_POOL_SUPPLY): self._swap_fee = MIN_FEE self._pool_token_supply = initial_pool_supply - self.factory_fees = Decimal('0') + self.factory_fees = Decimal("0") self._balances = {} - def swap(self, token_in: str, token_out: str, amount: Decimal, given_in: bool = True): - if(isinstance(amount,int) or isinstance(amount,float)): + def swap( + self, token_in: str, token_out: str, amount: Decimal, given_in: bool = True + ): + if isinstance(amount, int) or isinstance(amount, float): amount = Decimal(amount) - elif(not isinstance(amount, Decimal)): + elif not isinstance(amount, Decimal): raise Exception("INCORRECT_TYPE") - factory_fee = amount*self._swap_fee + factory_fee = amount * self._swap_fee swap_amount = amount - factory_fee self.factory_fees += factory_fee balances = [self._balances[token_in], self._balances[token_out]] - if(given_in): amount_out = StableMath.calcOutGivenIn(AMPLIFICATION_PARAMETER, balances, 0, 1, swap_amount) - else: amount_out = StableMath.calcInGivenOut(AMPLIFICATION_PARAMETER, balances, 0, 1, swap_amount) + if given_in: + amount_out = StableMath.calcOutGivenIn( + AMPLIFICATION_PARAMETER, balances, 0, 1, swap_amount + ) + else: + amount_out = StableMath.calcInGivenOut( + AMPLIFICATION_PARAMETER, balances, 0, 1, swap_amount + ) self._balances[token_out] -= amount_out self._balances[token_in] += swap_amount @@ -37,7 +44,7 @@ def join_pool(self, balances: dict): if key in self._balances: self._balances[key] += balances[key] else: - self._balances.update({key:balances[key]}) + self._balances.update({key: balances[key]}) def get_amplification_parameter(self): return self.AMPLIFICATION_PARAMETER @@ -48,9 +55,10 @@ def _get_total_tokens(self): def exit_pool(self, balances: dict): bals = self._balances - balances for key in bals: - if(bals[key]<0): bals[key] = 0 + if bals[key] < 0: + bals[key] = 0 self._balances = bals - + def set_swap_fee(self, amount: Decimal): self._swap_fee = amount diff --git a/balpy/balancerv2cad/src/balancerv2cad/WeightedMath.py b/balpy/balancerv2cad/src/balancerv2cad/WeightedMath.py index bb28555..a2d4613 100644 --- a/balpy/balancerv2cad/src/balancerv2cad/WeightedMath.py +++ b/balpy/balancerv2cad/src/balancerv2cad/WeightedMath.py @@ -6,7 +6,6 @@ class WeightedMath: - @staticmethod def calculate_invariant(normalized_weights: List[Decimal], balances: List[Decimal]): @@ -19,15 +18,19 @@ def calculate_invariant(normalized_weights: List[Decimal], balances: List[Decima invariant = Decimal(1) for i in range(len(normalized_weights)): - invariant = mulDown(invariant, (powDown(balances[i], normalized_weights[i]))) + invariant = mulDown( + invariant, (powDown(balances[i], normalized_weights[i])) + ) return invariant @staticmethod - def calc_out_given_in(balance_in: Decimal, - weight_in: Decimal, - balance_out: Decimal, - weight_out: Decimal, - amount_in: Decimal) -> Decimal: + def calc_out_given_in( + balance_in: Decimal, + weight_in: Decimal, + balance_out: Decimal, + weight_out: Decimal, + amount_in: Decimal, + ) -> Decimal: # /********************************************************************************************** # // outGivenIn // @@ -44,14 +47,18 @@ def calc_out_given_in(balance_in: Decimal, exponent = divDown(weight_in, weight_out) # weightIn.divDown(weightOut); power = powUp(base, exponent) # base.powUp(exponent); - return mulDown(balance_out, complement(power)) # balanceOut.mulDown(power.complement()); + return mulDown( + balance_out, complement(power) + ) # balanceOut.mulDown(power.complement()); @staticmethod - def calc_in_given_out(balance_in: Decimal, - weight_in: Decimal, - balance_out: Decimal, - weight_out: Decimal, - amount_out: Decimal): + def calc_in_given_out( + balance_in: Decimal, + weight_in: Decimal, + balance_out: Decimal, + weight_out: Decimal, + amount_out: Decimal, + ): # /********************************************************************************************** # // inGivenOut // @@ -69,33 +76,46 @@ def calc_in_given_out(balance_in: Decimal, ratio = power - Decimal(1) result = mulUp(balance_in, ratio) return result - + @staticmethod - def calc_bpt_out_given_exact_tokens_in(balances: List[Decimal], normalized_weights: List[Decimal], amounts_in: List[Decimal], - bptTotalSupply: Decimal, - swap_fee: Decimal): + def calc_bpt_out_given_exact_tokens_in( + balances: List[Decimal], + normalized_weights: List[Decimal], + amounts_in: List[Decimal], + bptTotalSupply: Decimal, + swap_fee: Decimal, + ): balance_ratios_with_fee = [None] * len(amounts_in) invariant_ratio_with_fees = 0 for i in range(len(balances)): - balance_ratios_with_fee[i] = divDown((balances[i] + amounts_in[i]), balances[i]) - invariant_ratio_with_fees = mulDown((invariant_ratio_with_fees + balance_ratios_with_fee[i]), normalized_weights[i]) #.add(balance_ratios_with_fee[i].mulDown(normalized_weights[i])); - + balance_ratios_with_fee[i] = divDown( + (balances[i] + amounts_in[i]), balances[i] + ) + invariant_ratio_with_fees = mulDown( + (invariant_ratio_with_fees + balance_ratios_with_fee[i]), + normalized_weights[i], + ) # .add(balance_ratios_with_fee[i].mulDown(normalized_weights[i])); invariant_ratio = Decimal(1) for i in range(len(balances)): amount_in_without_fee = None - if(balance_ratios_with_fee[i] > invariant_ratio_with_fees): - non_taxable_amount = mulDown(balances[i], (invariant_ratio - Decimal(1))) + if balance_ratios_with_fee[i] > invariant_ratio_with_fees: + non_taxable_amount = mulDown( + balances[i], (invariant_ratio - Decimal(1)) + ) taxable_amount = amounts_in[i] - non_taxable_amount - amount_in_without_fee = non_taxable_amount + (mulDown(taxable_amount, Decimal(1) - swap_fee)) + amount_in_without_fee = non_taxable_amount + ( + mulDown(taxable_amount, Decimal(1) - swap_fee) + ) else: amount_in_without_fee = amounts_in[i] - balance_ratio = divDown((balances[i] + amount_in_without_fee), balances[i]) - invariant_ratio = mulDown(invariant_ratio, (powDown(balance_ratio, normalized_weights[i]))) + invariant_ratio = mulDown( + invariant_ratio, (powDown(balance_ratio, normalized_weights[i])) + ) if invariant_ratio >= 1: return mulDown(bptTotalSupply, (invariant_ratio - Decimal(1))) @@ -108,7 +128,7 @@ def calc_token_in_given_exact_bpt_out( normalized_weight: Decimal, bpt_amount_out: Decimal, bpt_total_supply: Decimal, - swap_fee: Decimal + swap_fee: Decimal, ) -> Decimal: # /****************************************************************************************** @@ -132,43 +152,53 @@ def calc_token_in_given_exact_bpt_out( return non_taxable_amount + (divUp(taxable_amount, complement(swap_fee))) @staticmethod - def calc_bpt_in_given_exact_tokens_out( balances: List[Decimal], normalized_weights: List[Decimal], amounts_out: List[Decimal], bpt_total_supply: Decimal, - swap_fee: Decimal + swap_fee: Decimal, ) -> Decimal: balance_ratios_without_fee = [Decimal(0)] * len(amounts_out) invariant_ratio_without_fees = Decimal(0) for i in range(len(balances)): - balance_ratios_without_fee[i] = divUp((balances[i] - amounts_out[i]), balances[i]) - sys.stdout.write(f"{balances[i]}{amounts_out[i]}{balances[i]}{balance_ratios_without_fee} balance ratio") - invariant_ratio_without_fees = invariant_ratio_without_fees + (mulUp(balance_ratios_without_fee[i], normalized_weights[i])) + balance_ratios_without_fee[i] = divUp( + (balances[i] - amounts_out[i]), balances[i] + ) + sys.stdout.write( + f"{balances[i]}{amounts_out[i]}{balances[i]}{balance_ratios_without_fee} balance ratio" + ) + invariant_ratio_without_fees = invariant_ratio_without_fees + ( + mulUp(balance_ratios_without_fee[i], normalized_weights[i]) + ) invariant_ratio = Decimal(1) for i in range(len(balances)): amount_out_with_fee = None - if(invariant_ratio_without_fees > balance_ratios_without_fee[i]): - non_taxable_amount = mulDown(balances[i], (complement(invariant_ratio_without_fees))) + if invariant_ratio_without_fees > balance_ratios_without_fee[i]: + non_taxable_amount = mulDown( + balances[i], (complement(invariant_ratio_without_fees)) + ) taxable_amount = amounts_out[i] - non_taxable_amount - amount_out_with_fee = non_taxable_amount + (divUp(taxable_amount, complement(swap_fee))) + amount_out_with_fee = non_taxable_amount + ( + divUp(taxable_amount, complement(swap_fee)) + ) else: amount_out_with_fee = amounts_out[i] balance_ratio = divUp((balances[i] - amount_out_with_fee), balances[i]) - invariant_ratio = mulDown(invariant_ratio, (powDown(balance_ratio, normalized_weights[i]))) + invariant_ratio = mulDown( + invariant_ratio, (powDown(balance_ratio, normalized_weights[i])) + ) return mulUp(bpt_total_supply, complement(invariant_ratio)) @staticmethod - def calc_token_out_given_exact_bpt_in( balance: Decimal, normalized_weight: Decimal, bpt_amount_in: Decimal, bpt_total_supply: Decimal, - swap_fee: Decimal + swap_fee: Decimal, ) -> Decimal: # /***************************************************************************************** @@ -190,11 +220,8 @@ def calc_token_out_given_exact_bpt_in( return non_taxable_amount + mulDown(taxable_amount, complement(swap_fee)) @staticmethod - def calc_tokens_out_given_exact_bpt_in( - balances: List[Decimal], - bpt_amount_in: Decimal, - total_bpt: Decimal + balances: List[Decimal], bpt_amount_in: Decimal, total_bpt: Decimal ) -> List: # /********************************************************************************************** @@ -213,13 +240,13 @@ def calc_tokens_out_given_exact_bpt_in( return amounts_out @staticmethod - def calc_due_token_protocol_swap_fee_amount( balance: Decimal, normalized_weight: Decimal, previous_invariant: Decimal, current_invariant: Decimal, - protocol_swap_fee_percentage: Decimal) -> Decimal: + protocol_swap_fee_percentage: Decimal, + ) -> Decimal: # /********************************************************************************* # /* protocol_swap_fee_percentage * balanceToken * ( 1 - (previous_invariant / current_invariant) ^ (1 / weightToken)) diff --git a/balpy/balancerv2cad/src/balancerv2cad/WeightedPool.py b/balpy/balancerv2cad/src/balancerv2cad/WeightedPool.py index c2c2f3a..d884548 100644 --- a/balpy/balancerv2cad/src/balancerv2cad/WeightedPool.py +++ b/balpy/balancerv2cad/src/balancerv2cad/WeightedPool.py @@ -4,83 +4,90 @@ class WeightedPool(WeightedMath): - def __init__(self, initial_pool_supply: Decimal = INIT_POOL_SUPPLY): self._swap_fee = MIN_FEE - self.total_weight = Decimal('0') + self.total_weight = Decimal("0") self._pool_token_supply = initial_pool_supply self.factory_fees = {} self._balances = {} self._weights = {} - - def swap(self, token_in: str, token_out: str, amount: Decimal, given_in: bool = True): - if(isinstance(amount,int) or isinstance(amount,float)): + def swap( + self, token_in: str, token_out: str, amount: Decimal, given_in: bool = True + ): + if isinstance(amount, int) or isinstance(amount, float): amount = Decimal(amount) - elif(not isinstance(amount, Decimal)): + elif not isinstance(amount, Decimal): raise Exception("INCORRECT_TYPE") - factory_fee = amount*self._swap_fee + factory_fee = amount * self._swap_fee swap_amount = amount - factory_fee self.factory_fees[token_in] += factory_fee balances = [self._balances[token_in], self._balances[token_out]] weights = [self._weights[token_in], self._weights[token_out]] - - if(given_in): - amount_out = WeightedMath.calc_out_given_in(balances[0], weights[0], balances[1], weights[1], swap_amount) + + if given_in: + amount_out = WeightedMath.calc_out_given_in( + balances[0], weights[0], balances[1], weights[1], swap_amount + ) self._balances[token_out] -= amount_out self._balances[token_in] += swap_amount - + else: - amount_in = WeightedMath.calc_in_given_out(balances[0], weights[0], balances[1], weights[1], swap_amount) + amount_in = WeightedMath.calc_in_given_out( + balances[0], weights[0], balances[1], weights[1], swap_amount + ) self._balances[token_out] -= swap_amount self._balances[token_in] += amount_in return amount_out if given_in else amount_in - + def join_pool(self, balances: dict, weights={}): - if(not balances.keys()==weights.keys()): raise Exception('KEYS NOT EQUAL') + if not balances.keys() == weights.keys(): + raise Exception("KEYS NOT EQUAL") for key in weights: - if(not isinstance(weights[key],Decimal)): - weights[key] = Decimal(weights[key]) - self.factory_fees.update({key:Decimal(0)}) - if(not isinstance(balances[key],Decimal)): - balances[key] = Decimal(balances[key]) - + if not isinstance(weights[key], Decimal): + weights[key] = Decimal(weights[key]) + self.factory_fees.update({key: Decimal(0)}) + if not isinstance(balances[key], Decimal): + balances[key] = Decimal(balances[key]) + for key in balances: if key in self._balances: self._balances[key] += balances[key] else: - self._balances.update({key:balances[key]}) + self._balances.update({key: balances[key]}) self._weights = weights - if(len(self._balances)>8): + if len(self._balances) > 8: raise Exception("over 8 tokens") - + def exit_pool(self, balances: dict): bals = self._balances - balances for key in bals: - if(bals[key]<0): bals[key] = 0 + if bals[key] < 0: + bals[key] = 0 self._balances = bals - + def set_swap_fee(self, amount: Decimal): - if(isinstance(amount,int) or isinstance(amount,float)): + if isinstance(amount, int) or isinstance(amount, float): amount = Decimal(amount) - elif(not isinstance(amount, Decimal)): + elif not isinstance(amount, Decimal): raise Exception("INCORRECT_TYPE") self._swap_fee = amount - + def set_weights(self, weights: dict): - if(not weights.keys() in self._weights): raise Exception('WEIGHT TICKER NOT FOUND, JOIN POOL FIRST') + if not weights.keys() in self._weights: + raise Exception("WEIGHT TICKER NOT FOUND, JOIN POOL FIRST") for key in weights: - if(isinstance(amount,int) or isinstance(amount,float)): + if isinstance(amount, int) or isinstance(amount, float): amount = Decimal(amount) - elif(not isinstance(amount, Decimal)): + elif not isinstance(amount, Decimal): raise Exception("INCORRECT_TYPE") self._weights[key] = weights[key] - + def _mint_pool_share(self, amount: Decimal): self._pool_token_supply += amount - + def _burn_pool_share(self, amount: Decimal): self._pool_token_supply -= amount @@ -88,5 +95,4 @@ def get_balances(self): return self._balances def get_factory_fees(self): - return self.factory_fees - + return self.factory_fees diff --git a/balpy/balancerv2cad/src/balancerv2cad/logger/pkg_logger.py b/balpy/balancerv2cad/src/balancerv2cad/logger/pkg_logger.py index 786123e..fa3e360 100644 --- a/balpy/balancerv2cad/src/balancerv2cad/logger/pkg_logger.py +++ b/balpy/balancerv2cad/src/balancerv2cad/logger/pkg_logger.py @@ -23,6 +23,7 @@ class PackageLogger: """ Logging class """ + def __init__(self) -> None: # defaults to project root directory, change this # and __init__.py if you want the log file to go elsewhere @@ -49,19 +50,15 @@ def __load_configuration(self) -> None: dictConfig(LOGGING_CONFIG) except ValueError as error: print( - f'{Fore.RED}Loading default logging config failed, syntax error\n\n{error}' + f"{Fore.RED}Loading default logging config failed, syntax error\n\n{error}" ) sys.exit(1) except KeyError as error: - print( - f'{Fore.RED}Loading logging config failed, syntax error\n\n{error}' - ) + print(f"{Fore.RED}Loading logging config failed, syntax error\n\n{error}") sys.exit(1) @staticmethod - def get_logger( - name: Literal['production'] = DEFAULT_LOGGER_NAME - ) -> logging.Logger: + def get_logger(name: Literal["production"] = DEFAULT_LOGGER_NAME) -> logging.Logger: """ Return a logger by name. Only logger names that are defined in the config @@ -71,8 +68,8 @@ def get_logger( """ # first test to see if the name is a valid defined logger name valid: bool = False - if LOGGING_CONFIG['loggers']: - for logger_name in LOGGING_CONFIG['loggers']: + if LOGGING_CONFIG["loggers"]: + for logger_name in LOGGING_CONFIG["loggers"]: if logger_name == name: valid = True @@ -80,8 +77,8 @@ def get_logger( # name passed is not a valid listed logger, # return dev as default logger print( - f'\n{Back.BLACK}{Fore.RED}{name}: IS NOT A VALID LOGGER\n' - f'{Back.BLACK}{Fore.YELLOW}FALLING BACK TO {DEFAULT_LOGGER_NAME}\n' + f"\n{Back.BLACK}{Fore.RED}{name}: IS NOT A VALID LOGGER\n" + f"{Back.BLACK}{Fore.YELLOW}FALLING BACK TO {DEFAULT_LOGGER_NAME}\n" ) logger = logging.getLogger(DEFAULT_LOGGER_NAME) return logger diff --git a/balpy/balancerv2cad/src/balancerv2cad/main.py b/balpy/balancerv2cad/src/balancerv2cad/main.py index 4e1eea0..777a16f 100644 --- a/balpy/balancerv2cad/src/balancerv2cad/main.py +++ b/balpy/balancerv2cad/src/balancerv2cad/main.py @@ -20,15 +20,22 @@ def run() -> None: config: dict = dotenv_values(".env") try: - logger.info("CAPTAIN_ONE: %s :: package name: %s@%s", - config['CAPTAIN_ONE'], ks.__package__, ks.__version__) + logger.info( + "CAPTAIN_ONE: %s :: package name: %s@%s", + config["CAPTAIN_ONE"], + ks.__package__, + ks.__version__, + ) - logger.info("CAPTAIN_ONE: %s :: package name: %s@%s", - config['CAPTAIN_TWO'], ks.__package__, ks.__version__) + logger.info( + "CAPTAIN_ONE: %s :: package name: %s@%s", + config["CAPTAIN_TWO"], + ks.__package__, + ks.__version__, + ) except KeyError as error: - logger.error( - 'Could not find %s in .env file. Please consult the README', error) - logger.info('Testing for %s@%s', ks.__package__, ks.__version__) - logger.debug('Testing for %s@%s', ks.__package__, ks.__version__) - logger.warning('Testing for %s@%s', ks.__package__, ks.__version__) - logger.error('Testing for %s@%s', ks.__package__, ks.__version__) + logger.error("Could not find %s in .env file. Please consult the README", error) + logger.info("Testing for %s@%s", ks.__package__, ks.__version__) + logger.debug("Testing for %s@%s", ks.__package__, ks.__version__) + logger.warning("Testing for %s@%s", ks.__package__, ks.__version__) + logger.error("Testing for %s@%s", ks.__package__, ks.__version__) diff --git a/balpy/balancerv2cad/src/balancerv2cad/util.py b/balpy/balancerv2cad/src/balancerv2cad/util.py index 0ad5e75..60fa5f5 100644 --- a/balpy/balancerv2cad/src/balancerv2cad/util.py +++ b/balpy/balancerv2cad/src/balancerv2cad/util.py @@ -4,29 +4,29 @@ def mulUp(a: Decimal, b: Decimal) -> Decimal: getcontext().prec = 28 getcontext().rounding = ROUND_UP - return a*b + return a * b def divUp(a: Decimal, b: Decimal) -> Decimal: if a * b == 0: - + return Decimal(0) else: getcontext().prec = 28 getcontext().rounding = ROUND_UP - return a/b + return a / b -def mulDown(a: Decimal, b: Decimal)-> Decimal: +def mulDown(a: Decimal, b: Decimal) -> Decimal: getcontext().prec = 28 getcontext().rounding = ROUND_DOWN return a * b -def divDown(a: Decimal, b: Decimal)-> Decimal: +def divDown(a: Decimal, b: Decimal) -> Decimal: getcontext().prec = 28 getcontext().rounding = ROUND_DOWN - result = a/b + result = a / b return result @@ -35,13 +35,13 @@ def complement(a: Decimal) -> Decimal: return Decimal(1 - a) if a < 1 else Decimal(0) -def powUp(a: Decimal,b:Decimal) -> Decimal: +def powUp(a: Decimal, b: Decimal) -> Decimal: getcontext().prec = 28 getcontext().rounding = ROUND_UP return a**b -def powDown(a: Decimal,b: Decimal)-> Decimal: +def powDown(a: Decimal, b: Decimal) -> Decimal: getcontext().prec = 28 getcontext().rounding = ROUND_DOWN return a**b diff --git a/balpy/balancerv2cad/tests/conftest.py b/balpy/balancerv2cad/tests/conftest.py index efd5727..ca30862 100644 --- a/balpy/balancerv2cad/tests/conftest.py +++ b/balpy/balancerv2cad/tests/conftest.py @@ -30,9 +30,9 @@ def version_test() -> None: test version """ # Setup code - sys.stdout.write('\nRunning setup code for balancerv2cad module\n') + sys.stdout.write("\nRunning setup code for balancerv2cad module\n") yield balancerv2cad # tear down code - sys.stdout.write('Running Teardown code for balancerv2cad module\n') + sys.stdout.write("Running Teardown code for balancerv2cad module\n") diff --git a/balpy/balancerv2cad/tests/unit-test/test_StableMath.py b/balpy/balancerv2cad/tests/unit-test/test_StableMath.py index 05bb297..e987760 100644 --- a/balpy/balancerv2cad/tests/unit-test/test_StableMath.py +++ b/balpy/balancerv2cad/tests/unit-test/test_StableMath.py @@ -6,76 +6,83 @@ getcontext().prec = 21 MAX_RELATIVE_ERROR = Decimal(1e-17) + def expectEqualWithError(result: Decimal, expected: Decimal): - if result <= expected + MAX_RELATIVE_ERROR and result >= expected - MAX_RELATIVE_ERROR: + if ( + result <= expected + MAX_RELATIVE_ERROR + and result >= expected - MAX_RELATIVE_ERROR + ): return True return False class TestStableMath(unittest.TestCase): - - def test_calculateInvariants(self): - ''' + """ Tests for instance of Decimal - ''' + """ amp = Decimal(100) - balances = [Decimal(10),Decimal(12)] - result = StableMath.calculateInvariant(amp, balances) + balances = [Decimal(10), Decimal(12)] + result = StableMath.calculateInvariant(amp, balances) assert isinstance(result, Decimal) - ''' + """ Tests invariant for two tokens expected = 22 - ''' + """ amp = Decimal(100) - balances = [Decimal(10),Decimal(12)] - result = StableMath.calculateInvariant(amp, balances) + balances = [Decimal(10), Decimal(12)] + result = StableMath.calculateInvariant(amp, balances) assert expectEqualWithError(result, Decimal(22)) - ''' + """ Tests invariant for three tokens expected = 22 - ''' + """ amp = Decimal(100) - balances = [Decimal(10),Decimal(12), Decimal(14)] - result = StableMath.calculateInvariant(amp, balances) + balances = [Decimal(10), Decimal(12), Decimal(14)] + result = StableMath.calculateInvariant(amp, balances) assert expectEqualWithError(result, Decimal(36)) def test_calcInGivenOut(stablemath_test): - #TODO assert StableMath.calcInGivenOut(2,[222,3112,311],1,1,4) == 0.000002756210410895 - ''' + # TODO assert StableMath.calcInGivenOut(2,[222,3112,311],1,1,4) == 0.000002756210410895 + """ Tests for instance of Decimal - ''' + """ amp = Decimal(100) balances = [Decimal(10), Decimal(12), Decimal(14)] tokenIndexIn = 0 tokenIndexOut = 1 tokenAmountOut = Decimal(1) - result = StableMath.calcInGivenOut(amp, balances, tokenIndexIn, tokenIndexOut, tokenAmountOut) + result = StableMath.calcInGivenOut( + amp, balances, tokenIndexIn, tokenIndexOut, tokenAmountOut + ) assert isinstance(result, Decimal) - ''' + """ Tests in given out for two tokens - ''' + """ amp = Decimal(100) balances = [Decimal(10), Decimal(12)] tokenIndexIn = 0 tokenIndexOut = 1 tokenAmountOut = Decimal(1) - result = StableMath.calcInGivenOut(amp, balances, tokenIndexIn, tokenIndexOut, tokenAmountOut) + result = StableMath.calcInGivenOut( + amp, balances, tokenIndexIn, tokenIndexOut, tokenAmountOut + ) assert expectEqualWithError(result, Decimal(1)) - ''' + """ Tests in given out for three tokens - ''' + """ amp = Decimal(100) balances = [Decimal(10), Decimal(12), Decimal(14)] tokenIndexIn = 0 tokenIndexOut = 1 tokenAmountOut = Decimal(1) - result = StableMath.calcInGivenOut(amp, balances, tokenIndexIn, tokenIndexOut, tokenAmountOut) - expected = Decimal(1002381999332076302)/Decimal(1e18) + result = StableMath.calcInGivenOut( + amp, balances, tokenIndexIn, tokenIndexOut, tokenAmountOut + ) + expected = Decimal(1002381999332076302) / Decimal(1e18) assert expectEqualWithError(result, expected) - def test_calcOutGivenIn(stablemath_test): # def calcOutGivenIn(amplificationParameter: Decimal, balances: list[Decimal], tokenIndexIn: int, tokenIndexOut: int, tokenAmountIn: Decimal): @@ -84,30 +91,37 @@ def test_calcOutGivenIn(stablemath_test): tokenIndexIn = 0 tokenIndexOut = 1 tokenAmountIn = Decimal(1) - result = StableMath.calcOutGivenIn(amp, balances, tokenIndexIn, tokenIndexOut, tokenAmountIn) + result = StableMath.calcOutGivenIn( + amp, balances, tokenIndexIn, tokenIndexOut, tokenAmountIn + ) assert isinstance(result, Decimal) - ''' + """ Tests out given in for two tokens - ''' + """ amp = Decimal(10) balances = [Decimal(10), Decimal(11)] tokenIndexIn = 0 tokenIndexOut = 1 tokenAmountIn = Decimal(1) - result = StableMath.calcOutGivenIn(amp, balances, tokenIndexIn, tokenIndexOut, tokenAmountIn) - expected = Decimal(997840816806192585)/Decimal(1e18) + result = StableMath.calcOutGivenIn( + amp, balances, tokenIndexIn, tokenIndexOut, tokenAmountIn + ) + expected = Decimal(997840816806192585) / Decimal(1e18) assert expectEqualWithError(result, expected) - ''' + """ Tests out given in for three tokens - ''' + """ amp = Decimal(10) balances = [Decimal(10), Decimal(11), Decimal(12)] tokenIndexIn = 0 tokenIndexOut = 1 tokenAmountIn = Decimal(1) - result = StableMath.calcOutGivenIn(amp, balances, tokenIndexIn, tokenIndexOut, tokenAmountIn) - expected = Decimal(991747876655227989)/Decimal(1e18) + result = StableMath.calcOutGivenIn( + amp, balances, tokenIndexIn, tokenIndexOut, tokenAmountIn + ) + expected = Decimal(991747876655227989) / Decimal(1e18) assert expectEqualWithError(result, expected) + # def test_calcDueTokenProtoclSwapFeeAmount(stablemath_test): # ''' # Tests if output is instance of Decimal @@ -123,51 +137,52 @@ def test_calcOutGivenIn(stablemath_test): # expectedFeeAmount = StableMath.calc # assert expectEqualWithError(result, Decimal) def test_calcBptOutGivenExactTokensIn(stablemath_test): - ''' + """ Tests for instance of Decimal - ''' + """ amp = Decimal(22) - balances = [Decimal(2), Decimal(3),Decimal(4), Decimal(20)] + balances = [Decimal(2), Decimal(3), Decimal(4), Decimal(20)] amountsIn = [Decimal(2), Decimal(1), Decimal(2), Decimal(1000)] bptTotalsupply = Decimal(10) swapFee = Decimal(2) - swapFeePercentage = Decimal(.04) - result = StableMath.calcBptOutGivenExactTokensIn(amp, balances, amountsIn,bptTotalsupply, swapFee,swapFeePercentage) + swapFeePercentage = Decimal(0.04) + result = StableMath.calcBptOutGivenExactTokensIn( + amp, balances, amountsIn, bptTotalsupply, swapFee, swapFeePercentage + ) assert isinstance(result, Decimal) - ''' - ''' - + """ + """ def test_calcTokenInGivenExactBptOut(stablemath_test): amp = Decimal(100) - balances = [Decimal(10),Decimal(11)] - amountsOut = [Decimal(5),Decimal(6)] + balances = [Decimal(10), Decimal(11)] + amountsOut = [Decimal(5), Decimal(6)] bptTotalSupply = Decimal(100) protocolSwapFeePercentage = Decimal(0.1) - result = StableMath.calcBptInGivenExactTokensOut(amp, - balances, - amountsOut, - bptTotalSupply, - protocolSwapFeePercentage) + result = StableMath.calcBptInGivenExactTokensOut( + amp, balances, amountsOut, bptTotalSupply, protocolSwapFeePercentage + ) assert isinstance(result, Decimal) - def test_calcTokenOutGivenExactBptIn(stablemath_test): - balances = [Decimal(10),Decimal(11)] + balances = [Decimal(10), Decimal(11)] bptAmountIn = Decimal(10) - bptTotalSupply =Decimal(2) - result = StableMath.calcTokensOutGivenExactBptIn(balances,bptAmountIn, bptTotalSupply) + bptTotalSupply = Decimal(2) + result = StableMath.calcTokensOutGivenExactBptIn( + balances, bptAmountIn, bptTotalSupply + ) assert isinstance(result, list) def test_calcTokensOutGivenExactBptIn(stablemath_test): - balances = [Decimal(10),Decimal(11)] + balances = [Decimal(10), Decimal(11)] bptAmountIn = Decimal(10) - bptTotalSupply =Decimal(2) - result = StableMath.calcTokensOutGivenExactBptIn(balances,bptAmountIn,bptTotalSupply) + bptTotalSupply = Decimal(2) + result = StableMath.calcTokensOutGivenExactBptIn( + balances, bptAmountIn, bptTotalSupply + ) assert isinstance(result, list) - - #TODO give critical results + # TODO give critical results def test_getTokenBalanceGivenInvariantAndAllOtherBalances(stablemath_test): # assert StableMath.getTokenBalanceGivenInvariantAndAllOtherBalances(22, [2,3,4,20], 1, 2) == 0.002573235526125192 @@ -178,35 +193,44 @@ def test_getTokenBalanceGivenInvariantAndAllOtherBalances(stablemath_test): # [Decimal(2),Decimal(3),Decimal(4),Decimal(20)], # Decimal(1), # 2), Decimal(0.00067593918100831), 3) - #print(TestCase.assertAlmostEqual(1,1.00000000000001,3)) - - assert isinstance(StableMath.getTokenBalanceGivenInvariantAndAllOtherBalances( - Decimal(22), - [Decimal(2),Decimal(3),Decimal(4),Decimal(20)], - Decimal(1), - 2), Decimal) - + # print(TestCase.assertAlmostEqual(1,1.00000000000001,3)) + + assert isinstance( + StableMath.getTokenBalanceGivenInvariantAndAllOtherBalances( + Decimal(22), + [Decimal(2), Decimal(3), Decimal(4), Decimal(20)], + Decimal(1), + 2, + ), + Decimal, + ) amp = Decimal(10) - balances = [Decimal(11),Decimal(11),Decimal(12)] + balances = [Decimal(11), Decimal(11), Decimal(12)] invariant = Decimal(32.999999999) tokenIndex = 1 - result = StableMath.getTokenBalanceGivenInvariantAndAllOtherBalances(amp, balances, invariant, tokenIndex) + result = StableMath.getTokenBalanceGivenInvariantAndAllOtherBalances( + amp, balances, invariant, tokenIndex + ) assert expectEqualWithError(result, Decimal(10.008252123344772011)) amp = Decimal(100) - balances = [Decimal(10),Decimal(11)] + balances = [Decimal(10), Decimal(11)] invariant = Decimal(10) tokenIndex = 0 - result = StableMath.getTokenBalanceGivenInvariantAndAllOtherBalances(amp, balances, invariant, tokenIndex) + result = StableMath.getTokenBalanceGivenInvariantAndAllOtherBalances( + amp, balances, invariant, tokenIndex + ) assert expectEqualWithError(result, Decimal(0.098908137177552474646)) amp = Decimal(100) - balances = [Decimal(10),Decimal(11), Decimal(12)] + balances = [Decimal(10), Decimal(11), Decimal(12)] invariant = Decimal(10) tokenIndex = 0 - result = StableMath.getTokenBalanceGivenInvariantAndAllOtherBalances(amp, balances, invariant, tokenIndex) + result = StableMath.getTokenBalanceGivenInvariantAndAllOtherBalances( + amp, balances, invariant, tokenIndex + ) assert expectEqualWithError(result, Decimal(0.00071756564425404818025)) diff --git a/balpy/balancerv2cad/tests/unit-test/test_WeightedMath.py b/balpy/balancerv2cad/tests/unit-test/test_WeightedMath.py index 555c379..c8b556f 100644 --- a/balpy/balancerv2cad/tests/unit-test/test_WeightedMath.py +++ b/balpy/balancerv2cad/tests/unit-test/test_WeightedMath.py @@ -5,226 +5,285 @@ getcontext().prec = 28 MAX_RELATIVE_ERROR = Decimal(1e-17) + def expectEqualWithError(result: Decimal, expected: Decimal): - if result <= expected + MAX_RELATIVE_ERROR and result >= expected - MAX_RELATIVE_ERROR: + if ( + result <= expected + MAX_RELATIVE_ERROR + and result >= expected - MAX_RELATIVE_ERROR + ): return True return False -class TestWeightedMath: - def test_calculate_invariant(weightedmath_test): - #Test instance of Decimal - normalized_weight = [Decimal(0.3), Decimal(0.2), Decimal(0.5)] - balances = [Decimal(10), Decimal(12), Decimal(14)] - result = WeightedMath.calculate_invariant(normalized_weight, balances) - assert isinstance(result, Decimal) - - def test_calculate_invariant_zero_invariant(weightedmath_test): - #Tests zero Invariant - result = WeightedMath.calculate_invariant([Decimal(1)], [Decimal(0)]) - assert result == Decimal(0) - - def test_calculate_invariant_two_tokens(weightedmath_test): - #Test two tokens - normalized_weight = [Decimal(0.3), Decimal(0.7)] - balances = [Decimal(10), Decimal(12)] - result = WeightedMath.calculate_invariant(normalized_weight, balances) - expected = Decimal(11.361269771988886911) - assert expectEqualWithError(result, expected) - - def test_calculate_invariant_three_tokens(weightedmath_test): - #Test three tokens - normalized_weight = [Decimal(0.3), Decimal(0.2), Decimal(0.5)] - balances = [Decimal(10), Decimal(12), Decimal(14)] - result = WeightedMath.calculate_invariant(normalized_weight, balances) - expected = Decimal(12.271573899486561654) - assert expectEqualWithError(result, expected) - - def test_out_given_in_single_swap(weightedmath_test): - #Test instance of Decimal - token_balance_in = Decimal(100) - token_weight_in = Decimal(50) - token_balance_out = Decimal(100) - token_weight_out = Decimal(40) - token_amount_out = Decimal(15) - result = WeightedMath.calc_out_given_in(token_balance_in, token_weight_in, token_balance_out, token_weight_out, token_amount_out) - assert isinstance(result, Decimal) - - #Test out given in - expected = Decimal(1602931431298673722)/Decimal(1e17) - assert expectEqualWithError(result, expected) - - def test_in_given_out_single_swap(weightedmath_test): - #Test instance of Decimal - balance_in = Decimal(100) - weight_in = Decimal(50) - balance_out = Decimal(100) - weight_out = Decimal(40) - amount_out = Decimal(15) - result = WeightedMath.calc_in_given_out(balance_in, weight_in, balance_out, weight_out, amount_out) - assert isinstance(result, Decimal) - - expected = Decimal(1388456294146839515)/Decimal(1e17) - assert expectEqualWithError(result, expected) - - def test_out_given_in_extreme_amounts(weightedmath_test): - # Test instance of Decimal - - token_balance_in = Decimal(100) - token_weight_in = Decimal(50) - token_balance_out = Decimal(100) - token_weight_out = Decimal(40) - token_amount_out = Decimal(0.00000000001) - result = WeightedMath.calc_out_given_in(token_balance_in, token_weight_in, token_balance_out, token_weight_out, token_amount_out) - - assert isinstance(result, Decimal) - - # Test out given in - # min amount in - - expected = Decimal(12500000)/Decimal(1e18) - assert expectEqualWithError(result, expected) - def test_in_given_out_extreme_amounts(weightedmath_test): - #Test instance of Decimal - token_balance_in = Decimal(100) - token_weight_in = Decimal(50) - token_balance_out = Decimal(100) - token_weight_out = Decimal(40) - token_amount_out = Decimal(0.00000000001) - result = WeightedMath.calc_in_given_out(token_balance_in, token_weight_in, token_balance_out, token_weight_out, token_amount_out) - - assert isinstance(result, Decimal) - - # Test In given in - # min amount out - expected = Decimal(8000000)/Decimal(1e18) - assert expectEqualWithError(result, expected) - - def test_out_given_in_extreme_weights(weightedmath_test): - # Max weights relation - # Weight relation = 130.07 - token_balance_in = Decimal(100) - token_weight_in = Decimal(130.7) - token_balance_out = Decimal(100) - token_weight_out = Decimal(1) - token_amount_out = Decimal(15) - result = WeightedMath.calc_out_given_in(token_balance_in, token_weight_in, token_balance_out, token_weight_out, token_amount_out) - - assert isinstance(result, Decimal) - - #Test out given in - expected = Decimal(9999999883374836452)/Decimal(1e17) - assert expectEqualWithError(result, expected) - - def test_in_given_out_extreme_weights(weightedmath_test): - # Min weights relation - # Weight relation = 0.00769 - token_balance_in = Decimal(100) - token_weight_in = Decimal(0.00769) - token_balance_out = Decimal(100) - token_weight_out = Decimal(1) - token_amount_out = Decimal(15) - result = WeightedMath.calc_out_given_in(token_balance_in, token_weight_in, token_balance_out, token_weight_out, token_amount_out) - - assert isinstance(result, Decimal) - - #Test out given in - expected = Decimal(0.107419197916188066) - assert expectEqualWithError(result, expected) - - - - def test_calc_due_token_protocol_swap_fee_amount_two_tokens(weightedmath_test): - # Returns protocl swap fees - normalized_weights = [Decimal(0.3), Decimal(0.7)] - balances = [Decimal(10), Decimal(11)] - last_invariant = Decimal(10) - token_index = 1 - current_invariant = Decimal(10.689930449163329926) - protocol_swap_fee_percentage = Decimal(0.1) - result = WeightedMath.calc_due_token_protocol_swap_fee_amount(balances[token_index], normalized_weights[token_index], last_invariant, current_invariant, protocol_swap_fee_percentage) - assert isinstance(result, Decimal) - expected = Decimal(0.099999999999999999933) - assert expectEqualWithError(result,expected) - - # With large accumulated fees, caps the invariant growth - normalized_weights = [Decimal(0.3), Decimal(0.7)] - balances = [Decimal(10), Decimal(11)] - token_index = 1 - current_invariant = Decimal(10.689930449163329926) - last_invariant = current_invariant/Decimal(2) - protocol_swap_fee_percentage = Decimal(0.1) - result = WeightedMath.calc_due_token_protocol_swap_fee_amount(balances[token_index], normalized_weights[token_index], last_invariant, current_invariant, protocol_swap_fee_percentage) - - assert isinstance(result, Decimal) - expected = Decimal(0.439148057504926669190) - assert expectEqualWithError(result,expected) - - def test_calc_due_token_protocol_swap_fee_amount_three_tokens(weightedmath_test): - normalized_weights = [Decimal(0.3), Decimal(0.2), Decimal(0.5)] - balances = [Decimal(10), Decimal(11), Decimal(12)] - last_invariant = Decimal(10) - - token_index = 2 - current_invariant = Decimal(11.1652682095187556376) - - protocol_swap_fee_percentage = Decimal(0.1) - result = WeightedMath.calc_due_token_protocol_swap_fee_amount(balances[token_index], normalized_weights[token_index], last_invariant, current_invariant, protocol_swap_fee_percentage) - - assert isinstance(result, Decimal) - expected = Decimal(0.23740649734383223657) - assert expectEqualWithError(result,expected) - - - - def test_calc_bpt_out_given_exact_tokens_in(weightedmath_test): - #Test instance of Decimal - balances = [Decimal(10), Decimal(12), Decimal(14)] - normalized_weights = [Decimal(10), Decimal(12), Decimal(14)] - amounts_in = [Decimal(10), Decimal(12), Decimal(14)] - bpt_total_supply = Decimal(1) - swap_fee = Decimal(1) - result = WeightedMath.calc_bpt_out_given_exact_tokens_in(balances, normalized_weights, amounts_in, bpt_total_supply, swap_fee) - assert isinstance(result, Decimal) - - def test_calc_token_in_given_exact_bpt_out(weightedmath_test): - #Test instance of Decimal - balance = Decimal(1) - normalized_weight = Decimal(10) - bpt_amount_out = Decimal(10) - bpt_total_supply = Decimal(10) - swap_fee = Decimal(10) - result = WeightedMath.calc_token_in_given_exact_bpt_out(balance, normalized_weight, bpt_amount_out, bpt_total_supply, swap_fee) - assert isinstance(result, Decimal) - - def test_calc_bpt_in_given_exact_tokens_out(weightedmath_test): - #Test instance of Decimal - balances = [Decimal(10), Decimal(12), Decimal(14)] - normalized_weights = [Decimal(10), Decimal(12), Decimal(14)] - bpt_amount_out = [Decimal(10), Decimal(12), Decimal(14)] - bpt_total_supply = Decimal(1) - swap_fee = Decimal(1) - result = WeightedMath.calc_bpt_in_given_exact_tokens_out(balances, normalized_weights, bpt_amount_out, bpt_total_supply, swap_fee) - assert isinstance(result, Decimal) - - def test_calc_token_out_given_exact_bpt_in(weightedmath_test): - #Test instance of Decimal - balance = Decimal(10) - normalized_weight = Decimal(10) - bpt_amount_in = Decimal(2) - bpt_total_supply = Decimal(10) - swap_fee = Decimal(0) - result = WeightedMath.calc_token_out_given_exact_bpt_in(balance, normalized_weight, bpt_amount_in, bpt_total_supply, swap_fee) - assert isinstance(result, Decimal) - - def test_calc_tokens_out_given_exact_bpt_in(weightedmath_test): - #Test instance of Decimal - balance = Decimal(10) - normalized_weight = Decimal(1) - bpt_amount_in = Decimal(2) - bpt_total_supply = Decimal(1) - swap_fee = Decimal(1) - result = WeightedMath.calc_token_out_given_exact_bpt_in(balance, normalized_weight, bpt_amount_in, bpt_total_supply, swap_fee ) - assert isinstance(result, Decimal) - - +class TestWeightedMath: + def test_calculate_invariant(weightedmath_test): + # Test instance of Decimal + normalized_weight = [Decimal(0.3), Decimal(0.2), Decimal(0.5)] + balances = [Decimal(10), Decimal(12), Decimal(14)] + result = WeightedMath.calculate_invariant(normalized_weight, balances) + assert isinstance(result, Decimal) + + def test_calculate_invariant_zero_invariant(weightedmath_test): + # Tests zero Invariant + result = WeightedMath.calculate_invariant([Decimal(1)], [Decimal(0)]) + assert result == Decimal(0) + + def test_calculate_invariant_two_tokens(weightedmath_test): + # Test two tokens + normalized_weight = [Decimal(0.3), Decimal(0.7)] + balances = [Decimal(10), Decimal(12)] + result = WeightedMath.calculate_invariant(normalized_weight, balances) + expected = Decimal(11.361269771988886911) + assert expectEqualWithError(result, expected) + + def test_calculate_invariant_three_tokens(weightedmath_test): + # Test three tokens + normalized_weight = [Decimal(0.3), Decimal(0.2), Decimal(0.5)] + balances = [Decimal(10), Decimal(12), Decimal(14)] + result = WeightedMath.calculate_invariant(normalized_weight, balances) + expected = Decimal(12.271573899486561654) + assert expectEqualWithError(result, expected) + + def test_out_given_in_single_swap(weightedmath_test): + # Test instance of Decimal + token_balance_in = Decimal(100) + token_weight_in = Decimal(50) + token_balance_out = Decimal(100) + token_weight_out = Decimal(40) + token_amount_out = Decimal(15) + result = WeightedMath.calc_out_given_in( + token_balance_in, + token_weight_in, + token_balance_out, + token_weight_out, + token_amount_out, + ) + assert isinstance(result, Decimal) + + # Test out given in + expected = Decimal(1602931431298673722) / Decimal(1e17) + assert expectEqualWithError(result, expected) + + def test_in_given_out_single_swap(weightedmath_test): + # Test instance of Decimal + balance_in = Decimal(100) + weight_in = Decimal(50) + balance_out = Decimal(100) + weight_out = Decimal(40) + amount_out = Decimal(15) + result = WeightedMath.calc_in_given_out( + balance_in, weight_in, balance_out, weight_out, amount_out + ) + assert isinstance(result, Decimal) + + expected = Decimal(1388456294146839515) / Decimal(1e17) + assert expectEqualWithError(result, expected) + + def test_out_given_in_extreme_amounts(weightedmath_test): + # Test instance of Decimal + + token_balance_in = Decimal(100) + token_weight_in = Decimal(50) + token_balance_out = Decimal(100) + token_weight_out = Decimal(40) + token_amount_out = Decimal(0.00000000001) + result = WeightedMath.calc_out_given_in( + token_balance_in, + token_weight_in, + token_balance_out, + token_weight_out, + token_amount_out, + ) + + assert isinstance(result, Decimal) + + # Test out given in + # min amount in + + expected = Decimal(12500000) / Decimal(1e18) + assert expectEqualWithError(result, expected) + + def test_in_given_out_extreme_amounts(weightedmath_test): + # Test instance of Decimal + token_balance_in = Decimal(100) + token_weight_in = Decimal(50) + token_balance_out = Decimal(100) + token_weight_out = Decimal(40) + token_amount_out = Decimal(0.00000000001) + result = WeightedMath.calc_in_given_out( + token_balance_in, + token_weight_in, + token_balance_out, + token_weight_out, + token_amount_out, + ) + + assert isinstance(result, Decimal) + + # Test In given in + # min amount out + expected = Decimal(8000000) / Decimal(1e18) + assert expectEqualWithError(result, expected) + + def test_out_given_in_extreme_weights(weightedmath_test): + # Max weights relation + # Weight relation = 130.07 + token_balance_in = Decimal(100) + token_weight_in = Decimal(130.7) + token_balance_out = Decimal(100) + token_weight_out = Decimal(1) + token_amount_out = Decimal(15) + result = WeightedMath.calc_out_given_in( + token_balance_in, + token_weight_in, + token_balance_out, + token_weight_out, + token_amount_out, + ) + + assert isinstance(result, Decimal) + + # Test out given in + expected = Decimal(9999999883374836452) / Decimal(1e17) + assert expectEqualWithError(result, expected) + + def test_in_given_out_extreme_weights(weightedmath_test): + # Min weights relation + # Weight relation = 0.00769 + token_balance_in = Decimal(100) + token_weight_in = Decimal(0.00769) + token_balance_out = Decimal(100) + token_weight_out = Decimal(1) + token_amount_out = Decimal(15) + result = WeightedMath.calc_out_given_in( + token_balance_in, + token_weight_in, + token_balance_out, + token_weight_out, + token_amount_out, + ) + + assert isinstance(result, Decimal) + + # Test out given in + expected = Decimal(0.107419197916188066) + assert expectEqualWithError(result, expected) + + def test_calc_due_token_protocol_swap_fee_amount_two_tokens(weightedmath_test): + # Returns protocl swap fees + normalized_weights = [Decimal(0.3), Decimal(0.7)] + balances = [Decimal(10), Decimal(11)] + last_invariant = Decimal(10) + token_index = 1 + current_invariant = Decimal(10.689930449163329926) + protocol_swap_fee_percentage = Decimal(0.1) + result = WeightedMath.calc_due_token_protocol_swap_fee_amount( + balances[token_index], + normalized_weights[token_index], + last_invariant, + current_invariant, + protocol_swap_fee_percentage, + ) + assert isinstance(result, Decimal) + expected = Decimal(0.099999999999999999933) + assert expectEqualWithError(result, expected) + + # With large accumulated fees, caps the invariant growth + normalized_weights = [Decimal(0.3), Decimal(0.7)] + balances = [Decimal(10), Decimal(11)] + token_index = 1 + current_invariant = Decimal(10.689930449163329926) + last_invariant = current_invariant / Decimal(2) + protocol_swap_fee_percentage = Decimal(0.1) + result = WeightedMath.calc_due_token_protocol_swap_fee_amount( + balances[token_index], + normalized_weights[token_index], + last_invariant, + current_invariant, + protocol_swap_fee_percentage, + ) + + assert isinstance(result, Decimal) + expected = Decimal(0.439148057504926669190) + assert expectEqualWithError(result, expected) + + def test_calc_due_token_protocol_swap_fee_amount_three_tokens(weightedmath_test): + normalized_weights = [Decimal(0.3), Decimal(0.2), Decimal(0.5)] + balances = [Decimal(10), Decimal(11), Decimal(12)] + last_invariant = Decimal(10) + + token_index = 2 + current_invariant = Decimal(11.1652682095187556376) + + protocol_swap_fee_percentage = Decimal(0.1) + result = WeightedMath.calc_due_token_protocol_swap_fee_amount( + balances[token_index], + normalized_weights[token_index], + last_invariant, + current_invariant, + protocol_swap_fee_percentage, + ) + + assert isinstance(result, Decimal) + expected = Decimal(0.23740649734383223657) + assert expectEqualWithError(result, expected) + + def test_calc_bpt_out_given_exact_tokens_in(weightedmath_test): + # Test instance of Decimal + balances = [Decimal(10), Decimal(12), Decimal(14)] + normalized_weights = [Decimal(10), Decimal(12), Decimal(14)] + amounts_in = [Decimal(10), Decimal(12), Decimal(14)] + bpt_total_supply = Decimal(1) + swap_fee = Decimal(1) + result = WeightedMath.calc_bpt_out_given_exact_tokens_in( + balances, normalized_weights, amounts_in, bpt_total_supply, swap_fee + ) + assert isinstance(result, Decimal) + + def test_calc_token_in_given_exact_bpt_out(weightedmath_test): + # Test instance of Decimal + balance = Decimal(1) + normalized_weight = Decimal(10) + bpt_amount_out = Decimal(10) + bpt_total_supply = Decimal(10) + swap_fee = Decimal(10) + result = WeightedMath.calc_token_in_given_exact_bpt_out( + balance, normalized_weight, bpt_amount_out, bpt_total_supply, swap_fee + ) + assert isinstance(result, Decimal) + + def test_calc_bpt_in_given_exact_tokens_out(weightedmath_test): + # Test instance of Decimal + balances = [Decimal(10), Decimal(12), Decimal(14)] + normalized_weights = [Decimal(10), Decimal(12), Decimal(14)] + bpt_amount_out = [Decimal(10), Decimal(12), Decimal(14)] + bpt_total_supply = Decimal(1) + swap_fee = Decimal(1) + result = WeightedMath.calc_bpt_in_given_exact_tokens_out( + balances, normalized_weights, bpt_amount_out, bpt_total_supply, swap_fee + ) + assert isinstance(result, Decimal) + + def test_calc_token_out_given_exact_bpt_in(weightedmath_test): + # Test instance of Decimal + balance = Decimal(10) + normalized_weight = Decimal(10) + bpt_amount_in = Decimal(2) + bpt_total_supply = Decimal(10) + swap_fee = Decimal(0) + result = WeightedMath.calc_token_out_given_exact_bpt_in( + balance, normalized_weight, bpt_amount_in, bpt_total_supply, swap_fee + ) + assert isinstance(result, Decimal) + + def test_calc_tokens_out_given_exact_bpt_in(weightedmath_test): + # Test instance of Decimal + balance = Decimal(10) + normalized_weight = Decimal(1) + bpt_amount_in = Decimal(2) + bpt_total_supply = Decimal(1) + swap_fee = Decimal(1) + result = WeightedMath.calc_token_out_given_exact_bpt_in( + balance, normalized_weight, bpt_amount_in, bpt_total_supply, swap_fee + ) + assert isinstance(result, Decimal) diff --git a/balpy/balancerv2cad/tests/unit-test/test_kickstart.py b/balpy/balancerv2cad/tests/unit-test/test_kickstart.py index 08f7326..bc4525f 100644 --- a/balpy/balancerv2cad/tests/unit-test/test_kickstart.py +++ b/balpy/balancerv2cad/tests/unit-test/test_kickstart.py @@ -7,10 +7,9 @@ import balancerv2cad as ks - def test_pkgname_using_fixture(version_test): """ testing pkgname from passing the module in as a fixture """ - assert version_test.__package__ == 'balancerv2cad' + assert version_test.__package__ == "balancerv2cad" diff --git a/balpy/balpy.py b/balpy/balpy.py index deb3992..301b300 100644 --- a/balpy/balpy.py +++ b/balpy/balpy.py @@ -14,7 +14,12 @@ # low level web3 from web3 import Web3, middleware -from web3.gas_strategies.time_based import glacial_gas_price_strategy, slow_gas_price_strategy, medium_gas_price_strategy, fast_gas_price_strategy +from web3.gas_strategies.time_based import ( + glacial_gas_price_strategy, + slow_gas_price_strategy, + medium_gas_price_strategy, + fast_gas_price_strategy, +) from web3.middleware import geth_poa_middleware from web3._utils.abi import get_abi_output_types import eth_abi @@ -24,2162 +29,2788 @@ # balpy modules from . import balancerErrors as be -from .enums.stablePoolJoinExitKind import StablePoolJoinKind, StablePhantomPoolJoinKind, StablePoolExitKind +from .enums.stablePoolJoinExitKind import ( + StablePoolJoinKind, + StablePhantomPoolJoinKind, + StablePoolExitKind, +) from .enums.weightedPoolJoinExitKind import WeightedPoolJoinKind, WeightedPoolExitKind + class Suppressor(object): def __enter__(self): self.stdout = sys.stdout self.stderr = sys.stderr sys.stdout = self sys.stderr = self + def __exit__(self, type, value, traceback): sys.stdout = self.stdout sys.stderr = self.stderr if type is not None: - a=0; + a = 0 # Do normal exception handling - def write(self, x): pass + + def write(self, x): + pass + def getLongestStringLength(array): - maxLength = 0; - for a in array: - if len(a) > maxLength: - maxLength = len(a); - return(maxLength); + maxLength = 0 + for a in array: + if len(a) > maxLength: + maxLength = len(a) + return maxLength + def padWithSpaces(myString, endLength): - stringLength = len(myString); - extraSpace = endLength - stringLength; + stringLength = len(myString) + extraSpace = endLength - stringLength + + outputString = myString + "".join([" "] * extraSpace) + return outputString - outputString = myString + "".join([" "]*extraSpace); - return(outputString); class balpy(object): - - """ - Balancer Protocol Python API - Interface with Balancer V2 Smart contracts directly from Python - """ - - DELEGATE_OWNER = '0xBA1BA1ba1BA1bA1bA1Ba1BA1ba1BA1bA1ba1ba1B'; - ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; - - # Constants - INFINITE = 2 ** 256 - 1; #for infinite unlock - MAX_UINT_112 = 2**112 - 1; #for stablephantom max bpt - - - # Environment variable names - envVarEtherscan = "KEY_API_ETHERSCAN"; - envVarInfura = "KEY_API_INFURA"; - envVarPrivate = "KEY_PRIVATE"; - envVarCustomRPC = "BALPY_CUSTOM_RPC"; - - # Etherscan API call management - lastEtherscanCallTime = 0; - etherscanMaxRate = 5.0; #hz - etherscanSpeedDict = { - "slow":"SafeGasPrice", - "average":"ProposeGasPrice", - "fast":"FastGasPrice" - }; - speedDict = { - "glacial":glacial_gas_price_strategy, - "slow":slow_gas_price_strategy, - "average":medium_gas_price_strategy, - "fast":fast_gas_price_strategy - } - - # Network parameters - networkParams = { - "mainnet": {"id":1, "blockExplorerUrl":"etherscan.io", "balFrontend":"app.balancer.fi/#/" }, - "ropsten": {"id":3, "blockExplorerUrl":"ropsten.etherscan.io" }, - "rinkeby": {"id":4, "blockExplorerUrl":"rinkeby.etherscan.io" }, - "goerli": {"id":5, "blockExplorerUrl":"goerli.etherscan.io" }, - "optimism": {"id":10, "blockExplorerUrl":"optimistic.etherscan.io" }, - "kovan": {"id":42, "blockExplorerUrl":"kovan.etherscan.io", "balFrontend":"kovan.balancer.fi/#/" }, - "polygon": {"id":137, "blockExplorerUrl":"polygonscan.com", "balFrontend":"polygon.balancer.fi/#/" }, - "fantom": {"id":250, "blockExplorerUrl":"ftmscan.com", "balFrontend":"app.beets.fi/#/" }, - "arbitrum": {"id":42161, "blockExplorerUrl":"arbiscan.io", "balFrontend":"arbitrum.balancer.fi/#/" } - }; - - # ABIs and Deployment Addresses - abis = {}; - deploymentAddresses = {}; - contractDirectories = { - # ===== Vault Infra ===== - "Vault": { - "directory":"20210418-vault" - }, - "BalancerHelpers": { - "directory":"20210418-vault" - }, - "Authorizer": { - "directory":"20210418-authorizer" - }, - - # ====== Pools and Associated Contracts ====== - "WeightedPoolFactory": { - "directory":"20220908-weighted-pool-v2" - }, - "WeightedPool2TokensFactory": { - "directory":"20210418-weighted-pool" - }, - "StablePoolFactory": { - "directory":"20220609-stable-pool-v2" - }, - "LiquidityBootstrappingPoolFactory": { - "directory":"20210721-liquidity-bootstrapping-pool" - }, - "MetaStablePoolFactory": { - "directory":"20210727-meta-stable-pool" - }, - "WstETHRateProvider": { - "directory":"20210812-wsteth-rate-provider" - }, - "InvestmentPoolFactory": { - "directory":"20210907-investment-pool" - }, - "StablePhantomPoolFactory": { - "directory":"20211208-stable-phantom-pool" - }, - "ComposableStablePoolFactory": { - "directory":"20220906-composable-stable-pool" - }, - "AaveLinearPoolFactory": { - "directory":"20220817-aave-rebalanced-linear-pool" - }, - "ERC4626LinearPoolFactory": { - "directory":"20220304-erc4626-linear-pool" - }, - "NoProtocolFeeLiquidityBootstrappingPoolFactory": { - "directory":"20211202-no-protocol-fee-lbp" - }, - "ManagedPoolFactory": { - "directory":"20221021-managed-pool" - }, - - # ===== Relayers and Infra ===== - # TODO: update dir to 20220318-batch-relayer-v2 once deployed on all networks - "BalancerRelayer": { - "directory":"20211203-batch-relayer" - }, - "BatchRelayerLibrary": { - "directory":"20211203-batch-relayer" - }, - "LidoRelayer": { - "directory":"20210812-lido-relayer" - }, - - # ===== Liquidity Mining Infra ===== - "MerkleRedeem": { - "directory":"20210811-ldo-merkle" - }, - "MerkleOrchard": { - "directory":"20211012-merkle-orchard" - }, - - # ===== Gauges and Infra ===== - "AuthorizerAdaptor": { - "directory":"20220325-authorizer-adaptor" - }, - "BALTokenHolderFactory": { - "directory":"20220325-bal-token-holder-factory" - }, - "BalancerTokenAdmin": { - "directory":"20220325-balancer-token-admin" - }, - "GaugeAdder": { - "directory":"20220325-gauge-adder" - }, - "VotingEscrow": { - "directory":"20220325-gauge-controller" - }, - "GaugeController": { - "directory":"20220325-gauge-controller" - }, - "BalancerMinter": { - "directory":"20220325-gauge-controller" - }, - "LiquidityGaugeFactory": { - "directory":"20220325-mainnet-gauge-factory" - }, - "SingleRecipientGaugeFactory": { - "directory":"20220325-single-recipient-gauge-factory" - }, - "VotingEscrowDelegation": { - "directory":"20220325-ve-delegation" - }, - "VotingEscrowDelegationProxy": { - "directory":"20220325-ve-delegation" - }, - "veBALDeploymentCoordinator": { - "directory":"20220325-veBAL-deployment-coordinator" - }, - "ArbitrumRootGaugeFactory": { - "directory":"20220413-arbitrum-root-gauge-factory" - }, - "PolygonRootGaugeFactory": { - "directory":"20220413-polygon-root-gauge-factory" - }, - "ChildChainStreamer": { - "directory":"20220413-child-chain-gauge-factory" - }, - "ChildChainLiquidityGaugeFactory": { - "directory":"20220413-child-chain-gauge-factory" - }, - "veBALL2GaugeSetupCoordinator": { - "directory":"20220415-veBAL-L2-gauge-setup-coordinator" - }, - "veBALGaugeFixCoordinator": { - "directory":"20220418-veBAL-gauge-fix-coordinator" - }, - "FeeDistributor": { - "directory":"20220420-fee-distributor" - }, - "SmartWalletChecker": { - "directory":"20220420-smart-wallet-checker" - }, - "SmartWalletCheckerCoordinator": { - "directory":"20220421-smart-wallet-checker-coordinator" - }, - "DistributionScheduler": { - "directory":"20220422-distribution-scheduler" - }, - "ProtocolFeePercentagesProvider": { - "directory":"20220725-protocol-fee-percentages-provider" - } - }; - - decimals = {}; - - UserBalanceOpKind = { - "DEPOSIT_INTERNAL":0, - "WITHDRAW_INTERNAL":1, - "TRANSFER_INTERNAL":2, - "TRANSFER_EXTERNAL":3 - }; - inverseUserBalanceOpKind = { - 0:"DEPOSIT_INTERNAL", - 1:"WITHDRAW_INTERNAL", - 2:"TRANSFER_INTERNAL", - 3:"TRANSFER_EXTERNAL" - }; - def __init__(self, network=None, verbose=True, customConfigFile=None, manualEnv={}): - super(balpy, self).__init__(); - - self.verbose = verbose; - if self.verbose: - print(); - print("=============================================================="); - print("============== Initializing Balancer Python API =============="); - print("=============================================================="); - print(); - - if network is None: - print("No network set. Defaulting to kovan"); - network = "kovan"; - else: - print("Network is set to", network); - self.network = network.lower(); - - # set high decimal precision - getcontext().prec = 28; - - # grab parameters from env vars if they exist - self.infuraApiKey = os.environ.get(self.envVarInfura); - self.customRPC = os.environ.get(self.envVarCustomRPC); - self.etherscanApiKey = os.environ.get(self.envVarEtherscan); - self.privateKey = os.environ.get(self.envVarPrivate); - - # override params with manually passed args if they exist - if "infuraApiKey" in manualEnv.keys(): - self.infuraApiKey = manualEnv["infuraApiKey"]; - if "customRPC" in manualEnv.keys(): - self.customRPC = manualEnv["customRPC"]; - if "etherscanApiKey" in manualEnv.keys(): - self.etherscanApiKey = manualEnv["etherscanApiKey"]; - if "privateKey" in manualEnv.keys(): - self.privateKey = manualEnv["privateKey"]; - - if self.infuraApiKey is None and self.customRPC is None: - self.ERROR("You need to add your KEY_API_INFURA or BALPY_CUSTOM_RPC environment variables\n"); - self.ERROR("!! If you are using L2, you must use BALPY_CUSTOM_RPC !!"); - print("\t\texport " + self.envVarInfura + "="); - print("\t\t\tOR") - print("\t\texport " + self.envVarCustomRPC + "="); - print("\n\t\tNOTE: if you set " + self.envVarCustomRPC + ", it will override your Infura API key!") - quit(); - - if self.etherscanApiKey is None or self.privateKey is None: - self.ERROR("You need to add your keys to the your environment variables"); - print("\t\texport " + self.envVarEtherscan + "="); - print("\t\texport " + self.envVarPrivate + "="); - quit(); - - endpoint = self.customRPC; - if endpoint is None: - endpoint = 'https://' + self.network + '.infura.io/v3/' + self.infuraApiKey; - - self.endpoint = endpoint; - self.web3 = Web3(Web3.HTTPProvider(endpoint)); - - acct = self.web3.eth.account.privateKeyToAccount(self.privateKey); - self.web3.eth.default_account = acct.address; - self.address = acct.address; - - # initialize gas block caches - self.currGasPriceSpeed = None; - self.web3.middleware_onion.add(middleware.time_based_cache_middleware) - self.web3.middleware_onion.add(middleware.latest_block_based_cache_middleware) - self.web3.middleware_onion.add(middleware.simple_cache_middleware) - - # add support for PoA chains - self.web3.middleware_onion.inject(geth_poa_middleware, layer=0) - - if self.verbose: - print("Initialized account", self.web3.eth.default_account); - print("Connected to web3 at", endpoint); - - usingCustomConfig = (not customConfigFile is None); - customConfig = None; - if usingCustomConfig: - - # load custom config file if it exists, quit if not - if not os.path.isfile(customConfigFile): - self.ERROR("Custom config file" + customConfigFile + " not found!"); - quit(); - else: - with open(customConfigFile,'r') as f: - customConfig = json.load(f); - - # ensure all required fields are in the customConfig - requiredFields = ["contracts", "networkParams"] - hasAllRequirements = True; - for req in requiredFields: - if not req in customConfig.keys(): - hasAllRequirements = False; - if not hasAllRequirements: - self.ERROR("Not all custom fields are in the custom config!"); - print("You must include:"); - for req in requiredFields: - print("\t"+req); - print(); - quit(); - - # add network params for network - currNetworkParams = { - "id": customConfig["networkParams"]["id"], - "blockExplorerUrl": customConfig["networkParams"]["blockExplorerUrl"] - } - - if "balFrontend" in customConfig["networkParams"].keys(): - currNetworkParams["balFrontend"] = customConfig["networkParams"]["balFrontend"]; - self.networkParams[self.network] = currNetworkParams; - - self.mc = multicaller.multicaller( _chainId=self.networkParams[self.network]["id"], - _web3=self.web3, - _maxRetries=5, - _verbose=False); - - #reset for the edge case in which we're iterating through multiple networks - self.deploymentAddresses = {}; - missingContracts = []; - for contractType in self.contractDirectories.keys(): - subdir = self.contractDirectories[contractType]["directory"]; - - # get contract abi from deployment - abiPath = os.path.join('deployments', subdir , "abi", contractType + '.json'); - try: - f = pkgutil.get_data(__name__, abiPath).decode(); - currAbi = json.loads(f); - self.abis[contractType] = currAbi; - except BaseException as error: - self.ERROR('Error accessing file: {}'.format(abiPath)) - self.ERROR('{}'.format(error)) - - # get deployment address for given network - try: - if usingCustomConfig: - currAddress = self.web3.toChecksumAddress(customConfig["contracts"][contractType]); - else: - deploymentPath = os.path.join('deployments', subdir, "output", self.network + '.json'); - f = pkgutil.get_data(__name__, deploymentPath).decode(); - currData = json.loads(f); - currAddress = self.web3.toChecksumAddress(currData[contractType]); - self.deploymentAddresses[contractType] = currAddress; - except BaseException as error: - missingContracts.append(contractType); - - print("Available contracts on", self.network); - for element in self.deploymentAddresses.keys(): - address = self.deploymentAddresses[element]; - print("\t" + address + "\t" + element); - - print(); - print("Missing contracts on", self.network + ": [" + ", ".join(missingContracts) + "]"); - - print(); - print("=============================================================="); - - # ====================== - # ====Color Printing==== - # ====================== - def WARN(self, text): - WARNING_BEGIN = '\033[93m'; - WARNING_END = '\033[0m'; - print(WARNING_BEGIN + "[WARNING] " + text + WARNING_END); - - def ERROR(self, text): - ERROR_BEGIN = '\033[91m'; - ERROR_END = '\033[0m'; - print(ERROR_BEGIN + "[ERROR] " + text + ERROR_END); - - def GOOD(self, text): - GOOD_BEGIN = '\033[92m' - GOOD_END = '\033[0m'; - print(GOOD_BEGIN + text + GOOD_END); - - # ===================== - # ===Transaction Fns=== - # ===================== - def buildTx(self, fn, gasFactor, gasSpeed="average", nonceOverride=-1, gasEstimateOverride=-1, gasPriceGweiOverride=-1): - chainIdNetwork = self.networkParams[self.network]["id"]; - - # Get nonce if not overridden - if nonceOverride > -1: - nonce = nonceOverride; - else: - nonce = self.web3.eth.get_transaction_count(self.web3.eth.default_account) - - # Calculate gas estimate if not overridden - if gasEstimateOverride > -1: - gasEstimate = gasEstimateOverride; - else: - try: - gasEstimate = int(fn.estimateGas() * gasFactor); - except BaseException as error: - descriptiveError = be.handleException(error); - self.ERROR("Transaction failed at gas estimation!"); - self.ERROR(descriptiveError); - return(None); - - # Get gas price from Etherscan if not overridden - if gasPriceGweiOverride > -1: - gasPriceGwei = gasPriceGweiOverride; - else: - gasPriceGwei = self.getGasPrice(gasSpeed); - - print("\tGas Estimate:\t", gasEstimate); - print("\tGas Price:\t", gasPriceGwei, "Gwei"); - print("\tNonce:\t\t", nonce); - - # build transaction - data = fn.buildTransaction({'chainId': chainIdNetwork, - 'gas': gasEstimate, - 'gasPrice': self.web3.toWei(gasPriceGwei, 'gwei'), - 'nonce': nonce, - }); - return(data); - - def sendTx(self, tx, isAsync=False): - signedTx = self.web3.eth.account.sign_transaction(tx, self.privateKey); - txHash = self.web3.eth.send_raw_transaction(signedTx.rawTransaction).hex(); - - print(); - print("Sending transaction, view progress at:"); - print("\thttps://"+self.networkParams[self.network]["blockExplorerUrl"]+"/tx/"+txHash); - - if not isAsync: - self.waitForTx(txHash); - return(txHash); - - def waitForTx(self, txHash, timeOutSec=120): - txSuccessful = True; - print(); - print("Waiting for tx:", txHash); - try: - receipt = self.web3.eth.wait_for_transaction_receipt(txHash, timeout=timeOutSec); - if not receipt["status"] == 1: - txSuccessful = False; - except BaseException as error: - print('Transaction timeout: {}'.format(error)) - return(False); - - # Race condition: add a small delay to avoid getting the last nonce - time.sleep(0.5); - - print("\tTransaction accepted by network!"); - if not txSuccessful: - self.ERROR("Transaction failed!") - return(False) - print("\tTransaction was successful!\n"); - - return(True); - - def getTxReceipt(self, txHash, delay, maxRetries): - for i in range(maxRetries): - try: - receipt = self.web3.eth.getTransactionReceipt(txHash); - print("Retrieved receipt!"); - return(receipt); - except Exception as e: - print(e); - print("Transaction not found yet, will check again in", delay, "seconds"); - time.sleep(delay); - self.ERROR("Transaction not found in" + str(maxRetries) + "retries."); - return(False); - - # ===================== - # ====ERC20 methods==== - # ===================== - @cache - def erc20GetContract(self, tokenAddress): - # Read files packaged in module - abiPath = os.path.join('abi/ERC20.json'); - f = pkgutil.get_data(__name__, abiPath).decode(); - abi = json.loads(f); - - token = self.web3.eth.contract(self.web3.toChecksumAddress(tokenAddress), abi=abi) - return(token); - - @cache - def erc20GetDecimals(self, tokenAddress): - - # keep the manually maintained cache since the - # multicaller function can populate it too - if tokenAddress in self.decimals.keys(): - return(self.decimals[tokenAddress]); - - if tokenAddress == self.ZERO_ADDRESS: - self.decimals[tokenAddress] = 18; - return(18); - - token = self.erc20GetContract(tokenAddress); - decimals = token.functions.decimals().call(); - return(decimals); - - def erc20GetBalanceStandard(self, tokenAddress, address=None): - if address is None: - address = self.address; - token = self.erc20GetContract(tokenAddress); - decimals = self.erc20GetDecimals(tokenAddress); - standardBalance = Decimal(token.functions.balanceOf(address).call()) * Decimal(10**(-decimals)); - return(standardBalance); - - def erc20GetAllowanceStandard(self, tokenAddress, allowedAddress): - token = self.erc20GetContract(tokenAddress); - decimals = self.erc20GetDecimals(tokenAddress); - standardAllowance = Decimal(token.functions.allowance(self.address,allowedAddress).call()) * Decimal(10**(-decimals)); - return(standardAllowance); - - def erc20BuildFunctionSetAllowance(self, tokenAddress, allowedAddress, allowance): - token = self.erc20GetContract(tokenAddress); - approveFunction = token.functions.approve(allowedAddress, allowance); - return(approveFunction); - - def erc20SignAndSendNewAllowance( self, - tokenAddress, - allowedAddress, - allowance, - gasFactor, - gasSpeed, - nonceOverride=-1, - gasEstimateOverride=-1, - gasPriceGweiOverride=-1, - isAsync=False): - fn = self.erc20BuildFunctionSetAllowance(tokenAddress, allowedAddress, allowance); - tx = self.buildTx(fn, gasFactor, gasSpeed, nonceOverride, gasEstimateOverride, gasPriceGweiOverride); - txHash = self.sendTx(tx, isAsync); - return(txHash); - - def erc20HasSufficientBalance(self, tokenAddress, amountToUse): - balance = self.erc20GetBalanceStandard(tokenAddress); - - print("Token:", tokenAddress); - print("\tNeed:", float(amountToUse)); - print("\tWallet has:", float(balance)); - - sufficient = (float(balance) >= float(amountToUse)); - if not sufficient: - self.ERROR("Insufficient Balance!"); - else: - print("\tWallet has sufficient balance."); - print(); - return(sufficient); - - def erc20HasSufficientBalances(self, tokens, amounts): - if not len(tokens) == len(amounts): - self.ERROR("Array length mismatch with " + str(len(tokens)) + " tokens and " + str(len(amounts)) + " amounts."); - return(False); - numElements = len(tokens); - sufficientBalance = True; - for i in range(numElements): - token = tokens[i]; - amount = amounts[i]; - currentHasSufficientBalance = self.erc20HasSufficientBalance(token, amount); - sufficientBalance &= currentHasSufficientBalance; - return(sufficientBalance); - - def erc20HasSufficientAllowance(self, tokenAddress, allowedAddress, amount): - currentAllowance = self.erc20GetAllowanceStandard(tokenAddress, allowedAddress); - balance = self.erc20GetBalanceStandard(tokenAddress); - - print("Token:", tokenAddress); - print("\tCurrent Allowance:", currentAllowance); - print("\tCurrent Balance:", balance); - print("\tAmount to Spend:", amount); - - sufficient = (currentAllowance >= Decimal(amount)); - - if not sufficient: - print("\tInsufficient allowance!"); - print("\tWill need to unlock", tokenAddress); - else: - print("\tWallet has sufficient allowance."); - print(); - return(sufficient); - - def erc20EnforceSufficientAllowance(self, - tokenAddress, - allowedAddress, - targetAllowance, - amount, - gasFactor, - gasSpeed, - nonceOverride, - gasEstimateOverride, - gasPriceGweiOverride, - isAsync): - if not self.erc20HasSufficientAllowance(tokenAddress, allowedAddress, amount): - if targetAllowance == -1 or targetAllowance == self.INFINITE: - targetAllowance = self.INFINITE; - else: - decimals = self.erc20GetDecimals(tokenAddress); - targetAllowance = Decimal(targetAllowance) * Decimal(10**decimals); - targetAllowance = int(targetAllowance); - print("Insufficient Allowance: Increasing to", targetAllowance); - txHash = self.erc20SignAndSendNewAllowance(tokenAddress, allowedAddress, targetAllowance, gasFactor, gasSpeed, nonceOverride=nonceOverride, isAsync=isAsync, gasPriceGweiOverride=gasPriceGweiOverride); - return(txHash); - return(None); - - def erc20EnforceSufficientVaultAllowance(self, tokenAddress, targetAllowance, amount, gasFactor, gasSpeed, nonceOverride=-1, gasEstimateOverride=-1, gasPriceGweiOverride=-1, isAsync=False): - return(self.erc20EnforceSufficientAllowance(tokenAddress, self.deploymentAddresses["Vault"], targetAllowance, amount, gasFactor, gasSpeed, nonceOverride, gasEstimateOverride, gasPriceGweiOverride, isAsync)); - - def erc20GetTargetAllowancesFromPoolData(self, poolDescription): - (tokens, checksumTokens) = self.balSortTokens(list(poolDescription["tokens"].keys())); - allowances = []; - for token in tokens: - targetAllowance = -1; - if "allowance" in poolDescription["tokens"][token].keys(): - targetAllowance = poolDescription["tokens"][token]["allowance"]; - if targetAllowance == -1: - targetAllowance = self.INFINITE; - allowances.append(targetAllowance); - return(tokens, allowances); - - def erc20AsyncEnforceSufficientVaultAllowances(self, tokens, targetAllowances, amounts, gasFactor, gasSpeed, nonceOverride=-1, gasEstimateOverride=-1, gasPriceGweiOverride=-1): - if not len(tokens) == len(targetAllowances): - self.ERROR("Array length mismatch with " + str(len(tokens)) + " tokens and " + str(len(targetAllowances)) + " targetAllowances."); - return(False); - - nonce = self.web3.eth.get_transaction_count(self.web3.eth.default_account); - txHashes = []; - numElements = len(tokens); - for i in range(numElements): - token = tokens[i]; - targetAllowance = targetAllowances[i]; - amount = amounts[i]; - txHash = self.erc20EnforceSufficientVaultAllowance(token, targetAllowance, amount, gasFactor, gasSpeed, nonceOverride=nonce, isAsync=True); - if not txHash is None: - txHashes.append(txHash); - nonce += 1; - - for txHash in txHashes: - self.waitForTx(txHash) - return(True) - - # ===================== - # ======Etherscan====== - # ===================== - def generateEtherscanApiUrl(self): - etherscanUrl = self.networkParams[self.network]["blockExplorerUrl"] - separator = "."; - if self.network in ["kovan", "rinkeby","goerli","optimism"]: - separator = "-"; - urlFront = "https://api" + separator + etherscanUrl; - return(urlFront); - - def callEtherscan(self, url, maxRetries=3, verbose=False): - urlFront = self.generateEtherscanApiUrl(); - url = urlFront + url + self.etherscanApiKey; - if verbose: - print("Calling:", url); - - count = 0; - while count < maxRetries: - try: - dt = (time.time() - self.lastEtherscanCallTime); - if dt < 1.0/self.etherscanMaxRate: - time.sleep((1.0/self.etherscanMaxRate - dt) * 1.1); - - # faking a user-agent resolves the 403 (forbidden) errors on api-kovan.etherscan.io - headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.76 Safari/537.36', "Upgrade-Insecure-Requests": "1","DNT": "1","Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8","Accept-Language": "en-US,en;q=0.5","Accept-Encoding": "gzip, deflate"}; - r = requests.get(url, headers=headers); - if verbose: - print("\t", r); - self.lastEtherscanCallTime = time.time(); - data = r.json(); - if verbose: - print("\t", data); - return(data); - except Exception as e: - print("Exception:", e); - count += 1; - delaySec = 2; - if verbose: - self.WARN("Etherscan failed " + str(count) + " times. Retrying in " + str(delaySec) + " seconds..."); - time.sleep(delaySec); - self.ERROR("Etherscan failed " + str(count) + " times."); - return(False); - - def getGasPriceEtherscanGwei(self, speed, verbose=False): - if not speed in self.etherscanSpeedDict.keys(): - self.ERROR("Speed entered is:" + speed); - self.ERROR("Speed must be one of the following options:"); - for s in self.etherscanSpeedDict.keys(): - print("\t" + s); - return(False); - - urlString = "/api?module=gastracker&action=gasoracle&apikey=";# + self.etherscanApiKey; - response = self.callEtherscan(urlString, verbose=verbose); - return(response["result"][self.etherscanSpeedDict[speed]]); - - def getTransactionsByAddress(self, address, internal=False, startblock=0, verbose=False): - if verbose: - print("\tQuerying data after block", startblock); - - internalString = ""; - if internal: - internalString = "internal"; - - url = []; - url.append("/api?module=account&action=txlist{}&address=".format(internalString)); - url.append(address); - url.append("&startblock={}&endblock=99999999&sort=asc&apikey=".format(startblock)); - urlString = "".join(url); - txns = self.callEtherscan(urlString, verbose=verbose); - - if int(txns["status"]) == 0: - self.ERROR("Etherscan query failed. Please try again."); - return(False); - elif int(txns["status"]) == 1: - return(txns["result"]); - - def getTransactionByHash(self, txHash, verbose=False): - urlString = "/api?module=proxy&action=eth_getTransactionByHash&txhash={}&apikey=".format(txHash); - txns = self.callEtherscan(urlString, verbose=verbose); - - if verbose: - print(txns) - - if txns == False: - return(False); - return(txns); - - def isContractVerified(self, poolId, verbose=False): - address = self.balPooldIdToAddress(poolId); - url = "/api?module=contract&action=getabi&address={}&apikey=".format(address); - results = self.callEtherscan(url, verbose=verbose); - if verbose: - print(results); - isUnverified = (results["result"] == "Contract source code not verified"); - return(not isUnverified); - - def getGasPricePolygon(self, speed): - if speed in self.etherscanSpeedDict.keys(): - etherscanGasSpeedNamesToPolygon = { - "slow":"safeLow", - "average":"standard", - "fast":"fast" - }; - speed = etherscanGasSpeedNamesToPolygon[speed]; - - allowedSpeeds = ["safeLow","standard","fast","fastest"]; - if speed not in allowedSpeeds: - self.ERROR("Speed entered is:" + speed); - self.ERROR("Speed must be one of the following options:"); - for s in allowedSpeeds: - print("\t" + s); - return(False); - - r = requests.get("https://gasstation-mainnet.matic.network/") - prices = r.json(); - return(prices[speed]); - - def getGasPrice(self, speed): - allowedSpeeds = list(self.speedDict.keys()); - if speed not in allowedSpeeds: - self.ERROR("Speed entered is:" + speed); - self.ERROR("Speed must be one of the following options:"); - for s in allowedSpeeds: - print("\t" + s); - return(False); - - if not speed == self.currGasPriceSpeed: - self.currGasPriceSpeed = speed; - self.web3.eth.set_gas_price_strategy(self.speedDict[speed]); - - gasPrice = self.web3.eth.generateGasPrice() * 1e-9; - return(gasPrice) - - def balSortTokens(self, tokensIn): - # tokens need to be sorted as lowercase, but if they're provided as checksum, then - # the checksum format strings are still the keys outside of this function, so they - # must be preserved as they're input - lowerTokens = [t.lower() for t in tokensIn]; - lowerToOriginal = {}; - for i in range(len(tokensIn)): - lowerToOriginal[lowerTokens[i]] = tokensIn[i]; - lowerTokens.sort(); - - # get checksum tokens, translated sorted lower tokens back to their original format - checksumTokens = [self.web3.toChecksumAddress(t) for t in lowerTokens]; - sortedInputTokens = [lowerToOriginal[f] for f in lowerTokens] - - return(sortedInputTokens, checksumTokens); - - def balWeightsEqualOne(self, poolData): - tokenData = poolData["tokens"]; - tokens = tokenData.keys(); - - weightSum = Decimal(0.0); - for token in tokens: - weightSum += Decimal(tokenData[token]["weight"]); - - weightEqualsOne = (weightSum == Decimal(1.0)); - if not weightEqualsOne: - self.ERROR("Token weights add up to " + str(weightSum) + ", but they must add up to 1.0"); - self.ERROR("If you are passing more than 16 digits of precision, you must pass the value as a string") - return(weightEqualsOne); - - def balConvertTokensToWei(self, tokens, amounts): - rawTokens = []; - if not len(tokens) == len(amounts): - self.ERROR("Array length mismatch with " + str(len(tokens)) + " tokens and " + str(len(amounts)) + " amounts."); - return(False); - numElements = len(tokens); - for i in range(numElements): - token = tokens[i]; - rawValue = amounts[i]; - decimals = self.erc20GetDecimals(token); - if rawValue == self.INFINITE or rawValue == self.MAX_UINT_112: - decimals = 0; - raw = int(Decimal(rawValue) * Decimal(10**decimals)); - rawTokens.append(raw); - return(rawTokens); - - def balSetOwner(self, poolData): - owner = self.ZERO_ADDRESS; - if "owner" in poolData.keys(): - ownerAddress = poolData["owner"]; - if not len(ownerAddress) == 42: - self.ERROR("Entry for \"owner\" must be a 42 character Ethereum address beginning with \"0x\""); - return(False); - owner = self.web3.toChecksumAddress(ownerAddress); - return(owner); - - def balCreateFnWeightedPoolFactory(self, poolData): - factory = self.balLoadContract("WeightedPoolFactory"); - (tokens, checksumTokens) = self.balSortTokens(list(poolData["tokens"].keys())); - - intWithDecimalsWeights = [int(Decimal(poolData["tokens"][t]["weight"]) * Decimal(1e18)) for t in tokens]; - swapFeePercentage = int(Decimal(poolData["swapFeePercent"]) * Decimal(1e16)); - rateProviders = [self.web3.toChecksumAddress(poolData["tokens"][token]["rateProvider"]) for token in tokens]; - - if not self.balWeightsEqualOne(poolData): - return(False); - - owner = self.balSetOwner(poolData); - - createFunction = factory.functions.create( poolData["name"], - poolData["symbol"], - checksumTokens, - intWithDecimalsWeights, - rateProviders, - swapFeePercentage, - owner); - return(createFunction); - - def balCreateFnWeightedPool2TokensFactory(self, poolData): - factory = self.balLoadContract("WeightedPool2TokensFactory"); - (tokens, checksumTokens) = self.balSortTokens(list(poolData["tokens"].keys())); - - if not len(tokens) == 2: - self.ERROR("WeightedPool2TokensFactory requires 2 tokens, but", len(tokens), "were given."); - return(False); - - if not self.balWeightsEqualOne(poolData): - return(False); - - intWithDecimalsWeights = [int(Decimal(poolData["tokens"][t]["weight"]) * Decimal(1e18)) for t in tokens]; - swapFeePercentage = int(Decimal(poolData["swapFeePercent"]) * Decimal(1e16)); - - owner = self.balSetOwner(poolData); - - oracleEnabled = False; - if "oracleEnabled" in poolData.keys(): - oracleEnabled = poolData["oracleEnabled"]; - if isinstance(oracleEnabled, str): - if oracleEnabled.lower() == "true": - oracleEnabled = True; - else: - oracleEnabled = False; - - createFunction = factory.functions.create( poolData["name"], - poolData["symbol"], - checksumTokens, - intWithDecimalsWeights, - swapFeePercentage, - oracleEnabled, - owner); - return(createFunction); - - def balCreateFnStablePoolFactory(self, poolData): - factory = self.balLoadContract("StablePoolFactory"); - (tokens, checksumTokens) = self.balSortTokens(list(poolData["tokens"].keys())); - swapFeePercentage = int(Decimal(poolData["swapFeePercent"]) * Decimal(1e16)); - - owner = self.balSetOwner(poolData); - - createFunction = factory.functions.create( poolData["name"], - poolData["symbol"], - checksumTokens, - int(poolData["amplificationParameter"]), - swapFeePercentage, - owner); - return(createFunction); - - - def balCreateFnLBPoolFactory(self, poolData): - return(self.balCreateFnLBPFactory(poolData, "LiquidityBootstrappingPoolFactory")); - - def balCreateFnNoProtocolFeeLiquidityBootstrappingPoolFactory(self, poolData): - return(self.balCreateFnLBPFactory(poolData, "NoProtocolFeeLiquidityBootstrappingPoolFactory")); - - def balCreateFnLBPFactory(self, poolData, factoryName): - factory = self.balLoadContract(factoryName); - (tokens, checksumTokens) = self.balSortTokens(list(poolData["tokens"].keys())); - - if not self.balWeightsEqualOne(poolData): - return(False); - - swapFeePercentage = int(Decimal(poolData["swapFeePercent"]) * Decimal(1e16)); - intWithDecimalsWeights = [int(Decimal(poolData["tokens"][t]["weight"]) * Decimal(1e18)) for t in tokens]; - owner = self.balSetOwner(poolData); - - if not owner == self.address: - self.WARN("!!! You are not the owner for your LBP !!!") - self.WARN("You:\t\t" + self.address) - self.WARN("Pool Owner:\t" + owner) - - print(); - self.WARN("Only the pool owner can add liquidity. If you do not control " + owner + " then you will not be able to add liquidity!") - self.WARN("If you DO control " + owner + ", you will need to use the \"INIT\" join type from that address") - cancelTimeSec = 30; - self.WARN("If the owner mismatch is was unintentional, you have " + str(cancelTimeSec) + " seconds to cancel with Ctrl+C.") - time.sleep(cancelTimeSec); - - createFunction = factory.functions.create( poolData["name"], - poolData["symbol"], - checksumTokens, - intWithDecimalsWeights, - swapFeePercentage, - owner, - poolData["swapEnabledOnStart"]); - return(createFunction); - - def balCreateFnMetaStablePoolFactory(self, poolData): - factory = self.balLoadContract("MetaStablePoolFactory"); - (tokens, checksumTokens) = self.balSortTokens(list(poolData["tokens"].keys())); - swapFeePercentage = int(Decimal(poolData["swapFeePercent"]) * Decimal(1e16)); - owner = self.balSetOwner(poolData); - - rateProviders = [self.web3.toChecksumAddress(poolData["tokens"][token]["rateProvider"]) for token in tokens] - priceRateCacheDurations = [int(poolData["tokens"][token]["priceRateCacheDuration"]) for token in tokens] - - createFunction = factory.functions.create( poolData["name"], - poolData["symbol"], - checksumTokens, - int(poolData["amplificationParameter"]), - rateProviders, - priceRateCacheDurations, - swapFeePercentage, - poolData["oracleEnabled"], - owner); - return(createFunction); - - def balCreateFnInvestmentPoolFactory(self, poolData): - factory = self.balLoadContract("InvestmentPoolFactory"); - (tokens, checksumTokens) = self.balSortTokens(list(poolData["tokens"].keys())); - swapFeePercentage = int(Decimal(poolData["swapFeePercent"]) * Decimal(1e16)); - intWithDecimalsWeights = [int(Decimal(poolData["tokens"][t]["weight"]) * Decimal(1e18)) for t in tokens]; - managementFeePercentage = int(Decimal(poolData["managementFeePercent"]) * Decimal(1e16)); - # Deployed factory doesn't allow asset managers - # assetManagers = [0 for i in range(0,len(tokens))] - owner = self.balSetOwner(poolData); - if not owner == self.address: - self.WARN("!!! You are not the owner for your Investment Pool !!!") - self.WARN("You:\t\t" + self.address) - self.WARN("Pool Owner:\t" + owner) - - print(); - self.WARN("Only the pool owner can call permissioned functions, such as changing weights or the management fee.") - self.WARN(owner + " should either be you, or a multi-sig or other contract that you control and can call permissioned functions from.") - cancelTimeSec = 30; - self.WARN("If the owner mismatch is was unintentional, you have " + str(cancelTimeSec) + " seconds to cancel with Ctrl+C.") - time.sleep(cancelTimeSec); - - createFunction = factory.functions.create( poolData["name"], - poolData["symbol"], - checksumTokens, - intWithDecimalsWeights, - swapFeePercentage, - owner, - poolData["swapEnabledOnStart"], - managementFeePercentage); - return(createFunction); - - def balCreateFnManagedPoolFactory(self, poolData): - self.WARN("!!! You are using the Managed Pool Factory without a controller !!!") - self.WARN("You are currently using a factory to deploy a managed pool without a factory-provided controller contract.") - self.WARN("It is highly recommended that you use a factory that pairs a controller with a pool") - self.WARN("While this will be a valid pool, the owner will have a dangerous level of power over the pool") - self.WARN("It *is* technically possible to add a controller contract as `owner`, but using a factory-paired one provides more guarantees") - - factory = self.balLoadContract("ManagedPoolFactory"); - (tokens, checksumTokens) = self.balSortTokens(list(poolData["tokens"].keys())); - swapFeePercentage = int(Decimal(poolData["swapFeePercent"]) * Decimal(1e16)); - intWithDecimalsWeights = [int(Decimal(poolData["tokens"][t]["weight"]) * Decimal(1e18)) for t in tokens]; - assetManagers = [poolData["tokens"][t]["assetManager"] for t in tokens]; - managementAumFeePercentage = int(Decimal(poolData["managementAumFeePercentage"]) * Decimal(1e16)); - - owner = self.balSetOwner(poolData); - if not owner == self.address: - self.WARN("!!! You are not the owner for your Managed Pool !!!") - self.WARN("You:\t\t" + self.address) - self.WARN("Pool Owner:\t" + owner) - - print(); - self.WARN("Only the pool owner can call permissioned functions, such as changing weights or the management fee.") - self.WARN(owner + " should either be you, or a multi-sig or other contract that you control and can call permissioned functions from.") - cancelTimeSec = 30; - self.WARN("If the owner mismatch is was unintentional, you have " + str(cancelTimeSec) + " seconds to cancel with Ctrl+C.") - time.sleep(cancelTimeSec); - - createFunction = factory.functions.create( - ( - poolData["name"], - poolData["symbol"], - checksumTokens, - intWithDecimalsWeights, - assetManagers, - swapFeePercentage, - poolData["swapEnabledOnStart"], - poolData["mustAllowlistLPs"], - managementAumFeePercentage, - int(poolData["aumFeeId"]) - ), - owner - ); - return(createFunction); - - def balCreateFnStablePhantomPoolFactory(self, poolData): - factory = self.balLoadContract("StablePhantomPoolFactory"); - (tokens, checksumTokens) = self.balSortTokens(list(poolData["tokens"].keys())); - swapFeePercentage = int(Decimal(poolData["swapFeePercent"]) * Decimal(1e16)); - owner = self.balSetOwner(poolData); - - rateProviders = [self.web3.toChecksumAddress(poolData["tokens"][token]["rateProvider"]) for token in tokens] - tokenRateCacheDurations = [int(poolData["tokens"][token]["tokenRateCacheDuration"]) for token in tokens] - - createFunction = factory.functions.create( poolData["name"], - poolData["symbol"], - checksumTokens, - int(poolData["amplificationParameter"]), - rateProviders, - tokenRateCacheDurations, - swapFeePercentage, - owner); - return(createFunction); - - def balCreateFnComposableStablePoolFactory(self, poolData): - factory = self.balLoadContract("ComposableStablePoolFactory"); - (tokens, checksumTokens) = self.balSortTokens(list(poolData["tokens"].keys())); - swapFeePercentage = int(Decimal(poolData["swapFeePercent"]) * Decimal(1e16)); - owner = self.balSetOwner(poolData); - - rateProviders = [self.web3.toChecksumAddress(poolData["tokens"][token]["rateProvider"]) for token in tokens] - tokenRateCacheDurations = [int(poolData["tokens"][token]["tokenRateCacheDuration"]) for token in tokens] - exemptFromYieldProtocolFeeFlags = [bool(poolData["tokens"][token]["exemptFromYieldProtocolFeeFlags"]) for token in tokens] - - createFunction = factory.functions.create( poolData["name"], - poolData["symbol"], - checksumTokens, - int(poolData["amplificationParameter"]), - rateProviders, - tokenRateCacheDurations, - exemptFromYieldProtocolFeeFlags, - swapFeePercentage, - owner); - return(createFunction); - - def balCreateFnLinearPoolFactory(self, poolData, factoryName): - factory = self.balLoadContract(factoryName); - (tokens, checksumTokens) = self.balSortTokens(list(poolData["tokens"].keys())); - swapFeePercentage = int(Decimal(poolData["swapFeePercent"]) * Decimal(1e16)); - owner = self.balSetOwner(poolData); - - mainToken = None; - wrappedToken = None; - for token in poolData["tokens"].keys(): - if poolData["tokens"][token]["isWrappedToken"]: - wrappedToken = token; - else: - mainToken = token; - - if mainToken == wrappedToken: - self.ERROR("AaveLinearPool must have one wrappedToken and one mainToken. Please check your inputs. Quitting..."); - return(False); - - upperTarget = int(poolData["upperTarget"]); - createFunction = factory.functions.create( poolData["name"], - poolData["symbol"], - self.web3.toChecksumAddress(mainToken), - self.web3.toChecksumAddress(wrappedToken), - upperTarget, - swapFeePercentage, - owner); - return(createFunction); - - def balCreateFnAaveLinearPoolFactory(self, poolData): - return(self.balCreateFnLinearPoolFactory(poolData, "AaveLinearPoolFactory")); - - def balCreateFnERC4626LinearPoolFactory(self, poolData): - return(self.balCreateFnLinearPoolFactory(poolData, "ERC4626LinearPoolFactory")); - - def balCreatePoolInFactory(self, poolDescription, gasFactor, gasPriceSpeed, nonceOverride=-1, gasEstimateOverride=-1, gasPriceGweiOverride=-1): - createFunction = None; - poolFactoryName = poolDescription["poolType"] + "Factory"; - - # list of all supported pool factories - # NOTE: when you add a pool factory to this list, be sure to - # add it to the printout of supported factories below - if poolFactoryName == "WeightedPoolFactory": - createFunction = self.balCreateFnWeightedPoolFactory(poolDescription); - if poolFactoryName == "WeightedPool2TokensFactory": - createFunction = self.balCreateFnWeightedPool2TokensFactory(poolDescription); - if poolFactoryName == "StablePoolFactory": - createFunction = self.balCreateFnStablePoolFactory(poolDescription); - if poolFactoryName == "LiquidityBootstrappingPoolFactory": - createFunction = self.balCreateFnLBPoolFactory(poolDescription); - if poolFactoryName == "MetaStablePoolFactory": - createFunction = self.balCreateFnMetaStablePoolFactory(poolDescription); - if poolFactoryName == "InvestmentPoolFactory": - createFunction = self.balCreateFnInvestmentPoolFactory(poolDescription); - if poolFactoryName == "ManagedPoolFactory": - createFunction = self.balCreateFnManagedPoolFactory(poolDescription); - if poolFactoryName == "StablePhantomPoolFactory": - createFunction = self.balCreateFnStablePhantomPoolFactory(poolDescription); - if poolFactoryName == "ComposableStablePoolFactory": - createFunction = self.balCreateFnComposableStablePoolFactory(poolDescription); - if poolFactoryName == "AaveLinearPoolFactory": - createFunction = self.balCreateFnAaveLinearPoolFactory(poolDescription); - if poolFactoryName == "ERC4626LinearPoolFactory": - createFunction = self.balCreateFnERC4626LinearPoolFactory(poolDescription); - if poolFactoryName == "NoProtocolFeeLiquidityBootstrappingPoolFactory": - createFunction = self.balCreateFnNoProtocolFeeLiquidityBootstrappingPoolFactory(poolDescription); - if createFunction is None: - print("No pool factory found with name:", poolFactoryName); - print("Currently supported pool types are:"); - print("\tWeightedPool"); - print("\tWeightedPool2Token"); - print("\tStablePool"); - print("\tLiquidityBootstrappingPool"); - print("\tMetaStablePool"); - print("\tInvestmentPool"); - print("\tStablePhantomPool"); - print("\tComposableStablePoolFactory"); - print("\tAaveLinearPool"); - print("\tERC4626LinearPoolFactory"); - print("\tNoProtocolFeeLiquidityBootstrappingPoolFactory"); - return(False); - - if not createFunction: - self.ERROR("Pool creation failed.") - return(False) - print("Pool function created, generating transaction..."); - tx = self.buildTx(createFunction, gasFactor, gasPriceSpeed, nonceOverride, gasEstimateOverride, gasPriceGweiOverride); - print("Transaction Generated!"); - txHash = self.sendTx(tx); - return(txHash); - - def balGetPoolIdFromHash(self, txHash): - receipt = self.getTxReceipt(txHash, delay=2, maxRetries=5); - - # PoolRegistered event lives in the Vault - vault = self.balLoadContract("Vault"); - - with Suppressor(): - logs = vault.events.PoolRegistered().processReceipt(receipt); - poolId = logs[0]['args']['poolId'].hex(); - - print("Your pool ID is:"); - print("\t0x" + str(poolId)); - return(poolId); - - def balFindPoolFactory(self, poolId): - contractNames = self.deploymentAddresses.keys(); - factoryNames = [c for c in contractNames if ("Factory" in c) and ("Pool" in c)]; #can't simply use "PoolFactory" b/c of WeightedPool2TokensFactory - - self.mc.reset(); - for factoryName in factoryNames: - factory = self.balLoadContract(factoryName); - poolAddress = self.balPooldIdToAddress(poolId); - self.mc.addCall(factory.address, factory.abi, "isPoolFromFactory", args=[poolAddress]); - data = self.mc.execute(); - - foundFactoryName = None; - numFound = 0; - for f,d in zip(factoryNames, data): - if d[0]: - foundFactoryName = f; - numFound += 1; - - if numFound == 1: - return(foundFactoryName); - else: - self.ERROR("Was expecting 1 factory, got " + str(numFound)); - self.ERROR("Checked the following factories:\n\t\t" + "\n\t\t".join(factoryNames)); - return(None); - - def balGetJoinKindEnum(self, poolId, joinKind): - factoryName = self.balFindPoolFactory(poolId); - - usingWeighted = factoryName in ["WeightedPoolFactory", "WeightedPool2TokensFactory", "LiquidityBootstrappingPoolFactory", "InvestmentPoolFactory", "NoProtocolFeeLiquidityBootstrappingPoolFactory", "ManagedPoolFactory"]; - usingStable = factoryName in ["StablePoolFactory", "MetaStablePoolFactory"]; - usingStablePhantom = factoryName in ["StablePhantomPoolFactory", "ComposableStablePoolFactory"]; - - if usingWeighted: - joinKindEnum = WeightedPoolJoinKind[joinKind]; - elif usingStable: - joinKindEnum = StablePoolJoinKind[joinKind]; - elif usingStablePhantom: - joinKindEnum = StablePhantomPoolJoinKind[joinKind]; - else: - self.ERROR("PoolType " + str(factoryName) + " not supported for JoinKind: " + joinKind) - return(None); - return(joinKindEnum); - - def balGetTokensAndAmounts(self, joinDescription): - (sortedTokens, checksumTokens) = self.balSortTokens(list(joinDescription["tokens"].keys())); - amountKey = "amount"; - if not amountKey in joinDescription["tokens"][list(joinDescription["tokens"].keys())[0]].keys(): - amountKey = "initialBalance"; - amountsBySortedTokens = [joinDescription["tokens"][token][amountKey] for token in sortedTokens]; - maxAmountsIn = self.balConvertTokensToWei(sortedTokens, amountsBySortedTokens); - return(checksumTokens, maxAmountsIn); - - def balGetTokensAndAmountsComposable(self, joinDescription): - (checksumTokens, maxAmountsIn) = self.balGetTokensAndAmounts(joinDescription); - - poolAddress = self.balPooldIdToAddress(joinDescription["poolId"]); - poolAddress = self.web3.toChecksumAddress(poolAddress); - - composableAmount = None; - - userDataMaxAmountIn = copy.deepcopy(maxAmountsIn); - - for i in range(len(checksumTokens)): - if checksumTokens[i] == poolAddress: - composableAmount = maxAmountsIn[i]; - del checksumTokens[i]; - del maxAmountsIn[i]; - del userDataMaxAmountIn[i]; - break; - - checksumTokens.insert(0, poolAddress); - maxAmountsIn.insert(0, composableAmount); - - return(checksumTokens, maxAmountsIn, userDataMaxAmountIn); - - def balDoJoinPool(self, poolId, address, joinPoolRequestTuple, gasFactor=1.05, gasPriceSpeed="average", nonceOverride=-1, gasEstimateOverride=-1, gasPriceGweiOverride=-1): - vault = self.balLoadContract("Vault"); - joinPoolFunction = vault.functions.joinPool(poolId, address, address, joinPoolRequestTuple); - tx = self.buildTx(joinPoolFunction, gasFactor, gasPriceSpeed, nonceOverride, gasEstimateOverride, gasPriceGweiOverride); - print("Transaction Generated!"); - txHash = self.sendTx(tx); - return(txHash); - - def balDoQueryJoinPool(self, poolId, address, joinPoolRequestTuple): - bh = self.balLoadContract("BalancerHelpers"); - (bptOut, amountsIn) = bh.functions.queryJoin(poolId, address, address, joinPoolRequestTuple).call(); - return((bptOut, amountsIn)); - - def balFormatJoinPoolInit(self, joinDescription): - (checksumTokens, maxAmountsIn) = self.balGetTokensAndAmounts(copy.deepcopy(joinDescription)); - - poolId = joinDescription["poolId"]; - factory = self.balFindPoolFactory(poolId); - - userDataMaxAmountsIn = maxAmountsIn; - if factory in ["ManagedPoolFactory"]: - (checksumTokens, maxAmountsIn, userDataMaxAmountsIn) = self.balGetTokensAndAmountsComposable(copy.deepcopy(joinDescription)); - - joinKindEnum = self.balGetJoinKindEnum(poolId, joinDescription["joinKind"]); - userDataEncoded = eth_abi.encode_abi( ['uint256', 'uint256[]'], - [int(joinKindEnum), userDataMaxAmountsIn]); - address = self.web3.toChecksumAddress(self.web3.eth.default_account); - joinPoolRequestTuple = (checksumTokens, maxAmountsIn, userDataEncoded.hex(), joinDescription["fromInternalBalance"]); - return(poolId, address, joinPoolRequestTuple); - - def balFormatJoinPoolExactTokensInForBptOut(self, joinDescription): - (checksumTokens, maxAmountsIn) = self.balGetTokensAndAmounts(copy.deepcopy(joinDescription)); - poolId = joinDescription["poolId"]; - joinKindEnum = self.balGetJoinKindEnum(poolId, joinDescription["joinKind"]); - userDataEncoded = eth_abi.encode_abi( ['uint256', 'uint256[]'], - [int(joinKindEnum), maxAmountsIn]); - address = self.web3.toChecksumAddress(self.web3.eth.default_account); - joinPoolRequestTuple = (checksumTokens, maxAmountsIn, userDataEncoded.hex(), joinDescription["fromInternalBalance"]); - return(poolId, address, joinPoolRequestTuple); - - def balFormatJoinPoolAllTokensInForExactBptOut(self, joinDescription): - (checksumTokens, maxAmountsIn) = self.balGetTokensAndAmounts(copy.deepcopy(joinDescription)); - poolId = joinDescription["poolId"]; - poolAddress = self.balPooldIdToAddress(poolId); - bptAmountOut = self.balConvertTokensToWei([poolAddress], [joinDescription["bptAmountOut"]])[0]; - - joinKindEnum = self.balGetJoinKindEnum(poolId, joinDescription["joinKind"]); - userDataEncoded = eth_abi.encode_abi( ['uint256', 'uint256'], - [int(joinKindEnum), bptAmountOut]); - address = self.web3.toChecksumAddress(self.web3.eth.default_account); - joinPoolRequestTuple = (checksumTokens, maxAmountsIn, userDataEncoded.hex(), joinDescription["fromInternalBalance"]); - return(poolId, address, joinPoolRequestTuple); - - def balFormatJoinPoolTokenInForExactBptOut(self, joinDescription): - (checksumTokens, maxAmountsIn) = self.balGetTokensAndAmounts(copy.deepcopy(joinDescription)); - - index = -1; - counter = -1; - for amt in maxAmountsIn: - counter += 1; - if amt > 0: - if index == -1: - index = counter; - else: - self.ERROR("Multiple tokens have amounts for a single token join! Only one token can have a non-zero amount!"); - return(False); - if index == -1: - self.ERROR("No tokens have amounts. You must have one token with a non-zero amount!"); - return(False); - - poolId = joinDescription["poolId"]; - poolAddress = self.balPooldIdToAddress(poolId); - bptAmountOut = self.balConvertTokensToWei([poolAddress], [joinDescription["bptAmountOut"]])[0]; - - joinKindEnum = self.balGetJoinKindEnum(poolId, joinDescription["joinKind"]); - userDataEncoded = eth_abi.encode_abi( ['uint256', 'uint256', 'uint256'], - [int(joinKindEnum), bptAmountOut, index]); - - address = self.web3.toChecksumAddress(self.web3.eth.default_account); - joinPoolRequestTuple = (checksumTokens, maxAmountsIn, userDataEncoded.hex(), joinDescription["fromInternalBalance"]); - - return(poolId, address, joinPoolRequestTuple); - - def balJoinPool(self, joinDescription, query=False, gasFactor=1.05, gasPriceSpeed="average", nonceOverride=-1, gasEstimateOverride=-1, gasPriceGweiOverride=-1): - joinKind = joinDescription["joinKind"]; - poolId = None; - address = None; - joinPoolRequestTuple = None; - - if joinKind == "INIT": - (poolId, address, joinPoolRequestTuple) = self.balFormatJoinPoolInit(joinDescription); - elif joinKind == "EXACT_TOKENS_IN_FOR_BPT_OUT": - (poolId, address, joinPoolRequestTuple) = self.balFormatJoinPoolExactTokensInForBptOut(joinDescription); - elif joinKind == "TOKEN_IN_FOR_EXACT_BPT_OUT": - tempJoinDescription = copy.deepcopy(joinDescription); - if query: - for token in tempJoinDescription["tokens"].keys(): - if tempJoinDescription["tokens"][token]["amount"] == 0.0: - tempJoinDescription["tokens"][token]["amount"] = self.INFINITE; - (poolId, address, joinPoolRequestTuple) = self.balFormatJoinPoolTokenInForExactBptOut(tempJoinDescription); - elif joinKind == "ALL_TOKENS_IN_FOR_EXACT_BPT_OUT": - tempJoinDescription = copy.deepcopy(joinDescription); - if query: - for token in tempJoinDescription["tokens"].keys(): - tempJoinDescription["tokens"][token]["amount"] = self.INFINITE; - (poolId, address, joinPoolRequestTuple) = self.balFormatJoinPoolAllTokensInForExactBptOut(tempJoinDescription); - print((poolId, address, joinPoolRequestTuple)) - - if query: - (bptOut, amountsIn) = self.balDoQueryJoinPool(poolId, address, joinPoolRequestTuple); - bptAddress = self.balPooldIdToAddress(poolId); - outputData = {}; - outputData["bptOut"] = { - "token":bptAddress, - "decimals":self.erc20GetDecimals(bptAddress), - "amount":bptOut - } - outputData["amountsIn"] = {}; - for token, amount in zip(joinPoolRequestTuple[0], amountsIn): - outputData["amountsIn"][token] = { - "token":token, - "decimals":self.erc20GetDecimals(token), - "amount":amount - } - return(outputData); - else: - txHash = self.balDoJoinPool(poolId, address, joinPoolRequestTuple, gasFactor=gasFactor, gasPriceSpeed=gasPriceSpeed, nonceOverride=nonceOverride, gasEstimateOverride=gasEstimateOverride, gasPriceGweiOverride=gasPriceGweiOverride); - return(txHash); - - def balRegisterPoolWithVault(self, poolDescription, poolId, gasFactor=1.05, gasPriceSpeed="average", nonceOverride=-1, gasEstimateOverride=-1, gasPriceGweiOverride=-1): - self.WARN("\"balRegisterPoolWithVault\" is deprecated. Please use \"balJoinPoolInit\".") - self.balJoinPoolInit(poolDescription, poolId, gasFactor, gasPriceSpeed, nonceOverride, gasEstimateOverride, gasPriceGweiOverride) - - def balJoinPoolInit(self, poolDescription, poolId, gasFactor=1.05, gasPriceSpeed="average", nonceOverride=-1, gasEstimateOverride=-1, gasPriceGweiOverride=-1): - - if poolDescription["poolType"] in ["AaveLinearPool"]: - slippageTolerancePercent = 1; - txHash = self.balLinearPoolInitJoin(poolDescription, poolId, slippageTolerancePercent=slippageTolerancePercent, gasFactor=gasFactor, gasPriceSpeed=gasPriceSpeed, nonceOverride=nonceOverride, gasEstimateOverride=gasEstimateOverride, gasPriceGweiOverride=gasPriceGweiOverride); - return(txHash) - - # StablePhantomPools need their own BPT as one of the provided tokens with a limit of MAX_UINT_112 - if poolDescription["poolType"] in ["StablePhantomPool", "ComposableStablePool", "ManagedPool"]: - initialBalancesNoBpt = [poolDescription["tokens"][token]["initialBalance"] for token in poolDescription["tokens"].keys()]; - phantomBptAddress = self.balPooldIdToAddress(poolId); - poolDescription["tokens"][phantomBptAddress] = {"initialBalance":self.MAX_UINT_112} - - poolDescription["joinKind"] = "INIT"; - return(self.balJoinPool(poolDescription, False, gasFactor, gasPriceSpeed, nonceOverride, gasEstimateOverride, gasPriceGweiOverride)); - - def balLinearPoolInitJoin(self, poolDescription, poolId, slippageTolerancePercent=1, gasFactor=1.05, gasPriceSpeed="average", nonceOverride=-1, gasEstimateOverride=-1, gasPriceGweiOverride=-1): - - phantomBptAddress = self.balPooldIdToAddress(poolId); - - batchSwap = {}; - batchSwap["kind"] = 0; - batchSwap["assets"] = list(poolDescription["tokens"].keys()); - batchSwap["swaps"] = []; - - numTokens = len(batchSwap["assets"]); - for i in range(numTokens): - token = batchSwap["assets"][i]; - swap = {}; - swap["poolId"] = "0x" + poolId; - swap["assetInIndex"] = i; - swap["assetOutIndex"] = numTokens; - swap["amount"] = poolDescription["tokens"][token]["initialBalance"]; - if float(swap["amount"]) > 0.0: - batchSwap["swaps"].append(swap); - - # add the phantomBpt to the assets/limits list now that we've crafted the swap steps - batchSwap["assets"].append(phantomBptAddress); - batchSwap["limits"] = [0] * len(batchSwap["assets"]); #for now - - batchSwap["funds"] = {}; - batchSwap["funds"]["sender"] = self.address; - batchSwap["funds"]["recipient"] = self.address; - batchSwap["funds"]["fromInternalBalance"] = False; - batchSwap["funds"]["toInternalBalance"] = False; - batchSwap["deadline"] = "999999999999999999"; - - estimates = self.balQueryBatchSwap(batchSwap); - - checksumTokens = [self.web3.toChecksumAddress(t) for t in batchSwap["assets"]]; - for i in range(len(batchSwap["assets"])): - asset = checksumTokens[i]; - slippageToleranceFactor = slippageTolerancePercent/100.0; - if estimates[asset] < 0: - slippageToleranceFactor *= -1.0; - batchSwap["limits"][i] = estimates[asset] * (1.0 + slippageToleranceFactor); - - txHash = self.balDoBatchSwap(batchSwap, isAsync=False, gasFactor=gasFactor, gasPriceSpeed=gasPriceSpeed, nonceOverride=nonceOverride, gasEstimateOverride=gasEstimateOverride, gasPriceGweiOverride=gasPriceGweiOverride); - return(txHash) - - def balVaultWeth(self): - vault = self.balLoadContract("Vault"); - wethAddress = vault.functions.WETH().call(); - return(wethAddress); - - def balBalancerHelpersGetVault(self): - bh = self.balLoadContract("BalancerHelpers"); - vaultAddress = bh.functions.vault().call(); - return(vaultAddress); - - def balVaultGetPoolTokens(self, poolId): - vault = self.balLoadContract("Vault"); - output = vault.functions.getPoolTokens(poolId).call(); - tokens = output[0]; - balances = output[1]; - lastChangeBlock = output[2]; - return (tokens, balances, lastChangeBlock); - - def balVaultGetInternalBalance(self, tokens, address=None): - if address is None: - address = self.web3.eth.default_account; - - vault = self.balLoadContract("Vault"); - (sortedTokens, checksumTokens) = self.balSortTokens(tokens); - balances = vault.functions.getInternalBalance(address, checksumTokens).call(); - numElements = len(sortedTokens); - internalBalances = {}; - for i in range(numElements): - token = checksumTokens[i]; - decimals = self.erc20GetDecimals(token); - internalBalances[token] = Decimal(balances[i]) * Decimal(10**(-decimals)); - return(internalBalances); - - def balVaultDoManageUserBalance(self, kind, token, amount, sender, recipient, isAsync=False, gasFactor=1.05, gasPriceSpeed="average", nonceOverride=-1, gasEstimateOverride=-1, gasPriceGweiOverride=-1): - if self.verbose: - print("Managing User Balance"); - print("\tKind:\t\t", self.inverseUserBalanceOpKind[kind]); - print("\tToken:\t\t", str(token)); - print("\tAmount:\t\t", str(amount)); - print("\tSender:\t\t", str(sender)); - print("\tRecipient:\t", str(recipient)); - manageUserBalanceFn = self.balVaultBuildManageUserBalanceFn(kind, token, amount, sender, recipient); - - print(); - print("Building ManageUserBalance"); - tx = self.buildTx(manageUserBalanceFn, gasFactor, gasPriceSpeed, nonceOverride, gasEstimateOverride, gasPriceGweiOverride); - txHash = self.sendTx(tx, isAsync); - return(txHash); - - def balVaultBuildManageUserBalanceFn(self, kind, token, amount, sender, recipient): - kind = kind; - asset = self.web3.toChecksumAddress(token); - amount = self.balConvertTokensToWei([token],[amount])[0]; - sender = self.web3.toChecksumAddress(sender); - recipient = self.web3.toChecksumAddress(recipient); - inputTupleList = [(kind, asset, amount, sender, recipient)]; - - vault = self.balLoadContract("Vault"); - manageUserBalanceFn = vault.functions.manageUserBalance(inputTupleList); - return(manageUserBalanceFn); - - @cache - def balLoadContract(self, contractName): - contract = self.web3.eth.contract(address=self.deploymentAddresses[contractName], abi=self.abis[contractName]); - return(contract) - - @cache - def balLoadArbitraryContract(self, address, abi): - contract = self.web3.eth.contract(address=address, abi=self.mc.stringToList(abi)); - return(contract); - - @cache - def balPoolGetAbi(self, poolType): - - if not "Pool" in poolType: - poolType = poolType + "Pool" - - deploymentFolder = self.contractDirectories[poolType + "Factory"]["directory"]; - abiPath = os.path.join("deployments/", deploymentFolder, "abi", poolType + ".json"); - f = pkgutil.get_data(__name__, abiPath).decode(); - poolAbi = json.loads(f); - return(poolAbi); - - @cache - def balPooldIdToAddress(self, poolId): - if not "0x" in poolId: - poolId = "0x" + poolId; - poolAddress = self.web3.toChecksumAddress(poolId[:42]); - return(poolAddress); - - def balGetPoolCreationData(self, poolId, verbose=False, inputHash=None): - address = self.balPooldIdToAddress(poolId); - if inputHash is None: - txns = self.getTransactionsByAddress(address, internal=True, verbose=verbose); - else: - txns = self.getTransactionByHash(inputHash, verbose=verbose); - - poolTypeByContract = {}; - for poolType in self.deploymentAddresses.keys(): - deploymentAddress = self.deploymentAddresses[poolType].lower(); - poolTypeByContract[deploymentAddress] = poolType; - - poolFactoryType = None; - if inputHash is None: - for txn in txns: - if txn["from"].lower() in poolTypeByContract.keys(): - poolFactoryType = poolTypeByContract[txn["from"].lower()]; - txHash = txn["hash"]; - stamp = txn["timeStamp"]; - break; - else: - txn = txns["result"]; - if verbose: - print(); - print(txn); - poolFactoryType = poolTypeByContract[txn["to"].lower()]; - txHash = txn["hash"]; - stamp = self.web3.eth.get_block(int(txn["blockNumber"],16))["timestamp"]; - - return(address, poolFactoryType, txHash, stamp); - - def balGetPoolFactoryCreationTime(self, address): - txns = self.getTransactionsByAddress(address); - return(txns[0]["timeStamp"]); - - def getInputData(self, txHash): - transaction = self.web3.eth.get_transaction(txHash); - return(transaction.input) - - def balGeneratePoolCreationArguments(self, poolId, verbose=False, creationHash=None): - if self.network in ["arbitrum"]: - self.ERROR("Automated pool verification doesn't work on " + self.network + " yet. Please try the method outlined in the docs using Tenderly."); - return(False); - - # query etherscan for internal transactions to find pool factory, pool creation time, and creation hash - (address, poolFactoryType, txHash, stampPool) = self.balGetPoolCreationData(poolId, verbose=verbose, inputHash=creationHash); - - # get the input data used to generate the pool - inputData = self.getInputData(txHash); - - # decode those ^ inputs according to the relevant pool factory ABI - poolFactoryContract = self.balLoadContract(poolFactoryType) - decodedPoolData = poolFactoryContract.decode_function_input(inputData)[1]; - - # get pool factory creation time to calculate pauseWindowDuration - stampFactory = self.balGetPoolFactoryCreationTime(poolFactoryContract.address); - - # make sure arguments exist/are proper types to be encoded - if "weights" in decodedPoolData.keys(): - for i in range(len(decodedPoolData["weights"])): - decodedPoolData["weights"][i] = int(decodedPoolData["weights"][i]); - if "priceRateCacheDuration" in decodedPoolData.keys(): - for i in range(len(decodedPoolData["priceRateCacheDuration"])): - decodedPoolData["priceRateCacheDuration"][i] = int(decodedPoolData["priceRateCacheDuration"][i]); - if poolFactoryType == "InvestmentPoolFactory" and not "assetManagers" in decodedPoolData.keys(): - decodedPoolData["assetManagers"] = []; - for i in range(len(decodedPoolData["weights"])): - decodedPoolData["assetManagers"].append(self.ZERO_ADDRESS); - - # times for pause/buffer - daysToSec = 24*60*60; # hr * min * sec - pauseDays = 90; - bufferPeriodDays = 30; - - # calculate proper durations - pauseWindowDurationSec = max( (pauseDays*daysToSec) - (int(stampPool) - int(stampFactory)), 0); - bufferPeriodDurationSec = bufferPeriodDays * daysToSec; - if pauseWindowDurationSec == 0: - bufferPeriodDurationSec = 0; - - poolType = poolFactoryType.replace("Factory",""); - poolAbi = self.balPoolGetAbi(poolType); - - structInConstructor = False; - if poolType == "WeightedPool": - zero_ams = [self.ZERO_ADDRESS] * len(decodedPoolData["tokens"]); - args = [(decodedPoolData["name"], - decodedPoolData["symbol"], - decodedPoolData["tokens"], - decodedPoolData["normalizedWeights"], - decodedPoolData["rateProviders"], - zero_ams, - int(decodedPoolData["swapFeePercentage"])), - self.deploymentAddresses["Vault"], - self.deploymentAddresses["ProtocolFeePercentagesProvider"], - int(pauseWindowDurationSec), - int(bufferPeriodDurationSec), - decodedPoolData["owner"]]; - elif poolType == "WeightedPool2Tokens": - args = [self.deploymentAddresses["Vault"], - decodedPoolData["name"], - decodedPoolData["symbol"], - decodedPoolData["tokens"][0], - decodedPoolData["tokens"][1], - int(decodedPoolData["weights"][0]), - int(decodedPoolData["weights"][1]), - int(decodedPoolData["swapFeePercentage"]), - int(pauseWindowDurationSec), - int(bufferPeriodDurationSec), - decodedPoolData["oracleEnabled"], - decodedPoolData["owner"]]; - structInConstructor = True; - elif poolType == "StablePool": - args = [self.deploymentAddresses["Vault"], - decodedPoolData["name"], - decodedPoolData["symbol"], - decodedPoolData["tokens"], - int(decodedPoolData["amplificationParameter"]), - int(decodedPoolData["swapFeePercentage"]), - int(pauseWindowDurationSec), - int(bufferPeriodDurationSec), - decodedPoolData["owner"]]; - elif poolType == "MetaStablePool": - args = [self.deploymentAddresses["Vault"], - decodedPoolData["name"], - decodedPoolData["symbol"], - decodedPoolData["tokens"], - decodedPoolData["rateProviders"], - decodedPoolData["priceRateCacheDuration"], - int(decodedPoolData["amplificationParameter"]), - int(decodedPoolData["swapFeePercentage"]), - int(pauseWindowDurationSec), - int(bufferPeriodDurationSec), - decodedPoolData["oracleEnabled"], - decodedPoolData["owner"]]; - structInConstructor = True; - elif poolType == "LiquidityBootstrappingPool": - args = [self.deploymentAddresses["Vault"], - decodedPoolData["name"], - decodedPoolData["symbol"], - decodedPoolData["tokens"], - decodedPoolData["weights"], - int(decodedPoolData["swapFeePercentage"]), - int(pauseWindowDurationSec), - int(bufferPeriodDurationSec), - decodedPoolData["owner"], - decodedPoolData["swapEnabledOnStart"]]; - elif poolType == "InvestmentPool": - args = [self.deploymentAddresses["Vault"], - decodedPoolData["name"], - decodedPoolData["symbol"], - decodedPoolData["tokens"], - decodedPoolData["weights"], - decodedPoolData["assetManagers"], - int(decodedPoolData["swapFeePercentage"]), - int(pauseWindowDurationSec), - int(bufferPeriodDurationSec), - decodedPoolData["owner"], - decodedPoolData["swapEnabledOnStart"], - int(decodedPoolData["managementSwapFeePercentage"])]; - structInConstructor = True; - elif poolType == "StablePhantomPool": - args = [self.deploymentAddresses["Vault"], - decodedPoolData["name"], - decodedPoolData["symbol"], - decodedPoolData["tokens"], - decodedPoolData["rateProviders"], - decodedPoolData["tokenRateCacheDurations"], - int(decodedPoolData["amplificationParameter"]), - int(decodedPoolData["swapFeePercentage"]), - int(pauseWindowDurationSec), - int(bufferPeriodDurationSec), - decodedPoolData["owner"]]; - structInConstructor = True; - elif poolType == "AaveLinearPool": - args = [self.deploymentAddresses["Vault"], - decodedPoolData["name"], - decodedPoolData["symbol"], - decodedPoolData["mainToken"], - decodedPoolData["wrappedToken"], - int(decodedPoolData["upperTarget"]), - int(decodedPoolData["swapFeePercentage"]), - int(pauseWindowDurationSec), - int(bufferPeriodDurationSec), - decodedPoolData["owner"]]; - elif poolType == "ERC4626LinearPool": - args = [self.deploymentAddresses["Vault"], - decodedPoolData["name"], - decodedPoolData["symbol"], - decodedPoolData["mainToken"], - decodedPoolData["wrappedToken"], - int(decodedPoolData["upperTarget"]), - int(decodedPoolData["swapFeePercentage"]), - int(pauseWindowDurationSec), - int(bufferPeriodDurationSec), - decodedPoolData["owner"]]; - else: - self.ERROR("PoolType " + poolType + " not found!") - return(False); - - # encode constructor data - poolContract = self.balLoadArbitraryContract(address, self.mc.listToString(poolAbi)); - if structInConstructor: - args = (tuple(args),) - data = poolContract._encode_constructor_data(args=args); - encodedData = data[2:]; #cut off the 0x - - command = "yarn hardhat verify-contract --id {} --name {} --address {} --network {} --key {} --args {}" - output = command.format(self.contractDirectories[poolFactoryType]["directory"], - poolType, - address, - self.network, - self.etherscanApiKey, - encodedData) - return(output); - - def balStablePoolGetAmplificationParameter(self, poolId): - poolAddress = self.web3.toChecksumAddress(poolId[:42]); - pool = self.web3.eth.contract(address=poolAddress, abi=self.balPoolGetAbi("StablePool")); - (value, isUpdating, precision) = pool.functions.getAmplificationParameter().call(); - return(value, isUpdating, precision); - - def balStablePoolStartAmplificationParameterUpdate(self, poolId, rawEndValue, endTime, isAsync=False, gasFactor=1.05, gasPriceSpeed="average", nonceOverride=-1, gasEstimateOverride=-1, gasPriceGweiOverride=-1): - poolAddress = self.web3.toChecksumAddress(poolId[:42]); - pool = self.web3.eth.contract(address=poolAddress, abi=self.balPoolGetAbi("StablePool")); - - owner = pool.functions.getOwner().call(); - if not self.address == owner: - self.ERROR("You are not the pool owner; this transaction will fail."); - return(False); - - fn = pool.functions.startAmplificationParameterUpdate(rawEndValue, endTime); - tx = self.buildTx(fn, gasFactor, gasPriceSpeed, nonceOverride, gasEstimateOverride, gasPriceGweiOverride); - txHash = self.sendTx(tx, isAsync); - return(txHash); - - # https://dev.balancer.fi/references/contracts/apis/pools/weightedpool2tokens#gettimeweightedaverage - def balOraclePoolGetTimeWeightedAverage(self, poolId, queries): - poolAddress = self.web3.toChecksumAddress(poolId[:42]); - pool = self.web3.eth.contract(address=poolAddress, abi=self.balPoolGetAbi("WeightedPool2Tokens")); - results = pool.functions.getTimeWeightedAverage(queries).call(); - return(results); - - def balSwapIsFlashSwap(self, swapDescription): - for amount in swapDescription["limits"]: - if not float(amount) == 0.0: - return(False); - return(True); - - def balReorderTokenDicts(self, tokens): - originalIdxToSortedIdx = {}; - sortedIdxToOriginalIdx = {}; - tokenAddressToIdx = {}; - for i in range(len(tokens)): - tokenAddressToIdx[tokens[i]] = i; - sortedTokens = tokens; - sortedTokens.sort(); - for i in range(len(sortedTokens)): - originalIdxToSortedIdx[tokenAddressToIdx[sortedTokens[i]]] = i; - sortedIdxToOriginalIdx[i] = tokenAddressToIdx[sortedTokens[i]]; - return(sortedTokens, originalIdxToSortedIdx, sortedIdxToOriginalIdx); - - def balSwapGetUserData(self, poolType): - userDataNull = eth_abi.encode_abi(['uint256'], [0]); - userData = userDataNull; - #for weightedPools, user data is just null, but in the future there may be userData to pass to pools for swaps - # if poolType == "someFuturePool": - # userData = "something else"; - return(userData); - - def balDoSwap(self, swapDescription, isAsync=False, gasFactor=1.05, gasPriceSpeed="average", nonceOverride=-1, gasEstimateOverride=-1, gasPriceGweiOverride=-1): - swapFn = self.balCreateFnSwap(swapDescription); - tx = self.buildTx(swapFn, gasFactor, gasPriceSpeed, nonceOverride, gasEstimateOverride, gasPriceGweiOverride); - txHash = self.sendTx(tx, isAsync); - return(txHash); - - def balDoBatchSwap(self, swapDescription, isAsync=False, gasFactor=1.05, gasPriceSpeed="average", nonceOverride=-1, gasEstimateOverride=-1, gasPriceGweiOverride=-1): - batchSwapFn = self.balCreateFnBatchSwap(swapDescription); - tx = self.buildTx(batchSwapFn, gasFactor, gasPriceSpeed, nonceOverride, gasEstimateOverride, gasPriceGweiOverride); - txHash = self.sendTx(tx, isAsync); - return(txHash); - - def balCreateFnSwap(self, swapDescription): - kind = int(swapDescription["kind"]) - limitedToken = None; - amountToken = None; - if kind == 0: #GIVEN_IN - amountToken = swapDescription["assetIn"]; - limitedToken = swapDescription["assetOut"]; - elif kind == 1: #GIVEN_OUT - amountToken = swapDescription["assetOut"]; - limitedToken = swapDescription["assetIn"]; - - amountWei = int(Decimal(swapDescription["amount"]) * 10 ** Decimal(self.erc20GetDecimals(amountToken))); - limitWei = int(Decimal(swapDescription["limit"]) * 10 ** Decimal(self.erc20GetDecimals(limitedToken))); - - swapStruct = ( - swapDescription["poolId"], - kind, - self.web3.toChecksumAddress(swapDescription["assetIn"]), - self.web3.toChecksumAddress(swapDescription["assetOut"]), - amountWei, - self.balSwapGetUserData(None) - ) - fundStruct = ( - self.web3.toChecksumAddress(swapDescription["fund"]["sender"]), - swapDescription["fund"]["fromInternalBalance"], - self.web3.toChecksumAddress(swapDescription["fund"]["recipient"]), - swapDescription["fund"]["toInternalBalance"] - ) - vault = self.balLoadContract("Vault"); - singleSwapFunction = vault.functions.swap( - swapStruct, - fundStruct, - limitWei, - int(swapDescription["deadline"]) - ) - return(singleSwapFunction); - - def balFormatBatchSwapData(self, swapDescription): - (sortedTokens, originalIdxToSortedIdx, sortedIdxToOriginalIdx) = self.balReorderTokenDicts(swapDescription["assets"]); - numTokens = len(sortedTokens); - - # reorder the limits to refer to properly sorted tokens - reorderedLimits = []; - for i in range(numTokens): - currLimitStandard = float(swapDescription["limits"][sortedIdxToOriginalIdx[i]]); - decimals = self.erc20GetDecimals(sortedTokens[i]); - currLimitRaw = int(Decimal(currLimitStandard) * Decimal(10**(decimals))) - reorderedLimits.append(currLimitRaw) - - kind = int(swapDescription["kind"]); - assets = [self.web3.toChecksumAddress(token) for token in sortedTokens]; - - swapsTuples = []; - for swap in swapDescription["swaps"]: - idxSortedIn = originalIdxToSortedIdx[int(swap["assetInIndex"])]; - idxSortedOut = originalIdxToSortedIdx[int(swap["assetOutIndex"])]; - - idxTokenAmount = idxSortedIn; - if kind == 1: - idxTokenAmount = idxSortedOut; - - decimals = self.erc20GetDecimals(sortedTokens[idxTokenAmount]); - amount = int( Decimal(swap["amount"]) * Decimal(10**(decimals))); - - swapsTuple = ( swap["poolId"], - idxSortedIn, - idxSortedOut, - amount, - self.balSwapGetUserData(None)); - swapsTuples.append(swapsTuple); - - funds = ( self.web3.toChecksumAddress(swapDescription["funds"]["sender"]), - swapDescription["funds"]["fromInternalBalance"], - self.web3.toChecksumAddress(swapDescription["funds"]["recipient"]), - swapDescription["funds"]["toInternalBalance"]); - intReorderedLimits = [int(element) for element in reorderedLimits]; - deadline = int(swapDescription["deadline"]); - return(kind, swapsTuples, assets, funds, intReorderedLimits, deadline); - - def balCreateFnBatchSwap(self, swapDescription): - (kind, swapsTuples, assets, funds, intReorderedLimits, deadline) = self.balFormatBatchSwapData(swapDescription); - vault = self.balLoadContract("Vault"); - batchSwapFunction = vault.functions.batchSwap( kind, - swapsTuples, - assets, - funds, - intReorderedLimits, - deadline); - return(batchSwapFunction); - - def balQueryBatchSwaps(self, originalSwapsDescription): - swapsDescription = copy.deepcopy(originalSwapsDescription); - vault = self.balLoadContract("Vault"); - for swapDescription in swapsDescription: - - # do deep copy to avoid modifying the swapDescription in place, breaking index remappings - deepCopySwapDescription = copy.deepcopy(swapDescription); - (kind, swapsTuples, assets, funds, intReorderedLimits, deadline) = self.balFormatBatchSwapData(deepCopySwapDescription); - args = [kind, swapsTuples, assets, funds]; - self.mc.addCall(vault.address, vault.abi, "queryBatchSwap", args=args); - data = self.mc.execute(); - - outputs = []; - for swapDescription, outputData in zip(swapsDescription, data): - amounts = list(outputData[0]); - output = {}; - for asset, amount in zip(assets, amounts): - decimals = self.erc20GetDecimals(asset); - output[asset] = amount * 10**(-decimals); - outputs.append(output); - return(outputs); - - def balQueryBatchSwap(self, originalSwapDescription): - swapDescription = copy.deepcopy(originalSwapDescription); - (kind, swapsTuples, assets, funds, intReorderedLimits, deadline) = self.balFormatBatchSwapData(swapDescription); - vault = self.balLoadContract("Vault"); - amounts = vault.functions.queryBatchSwap( kind, - swapsTuples, - assets, - funds).call(); - - output = {}; - for asset, amount in zip(assets, amounts): - decimals = self.erc20GetDecimals(asset); - output[asset] = amount * 10**(-decimals); - return(output); - - def balGetLinkToFrontend(self, poolId): - if "balFrontend" in self.networkParams[self.network].keys(): - return("https://" + self.networkParams[self.network]["balFrontend"] + "pool/0x" + poolId); - else: - return("") - - def multiCallErc20BatchDecimals(self, tokens): - self.mc.reset(); - payload = []; - for token in tokens: - currTokenContract = self.erc20GetContract(token); - self.mc.addCall(currTokenContract.address, currTokenContract.abi, 'decimals'); - - # make the actual call to MultiCall - outputData = self.mc.execute(); - tokensToDecimals = {}; - - for token, odBytes in zip(tokens, outputData): - decimals = odBytes[0]; - tokensToDecimals[token] = decimals; - self.decimals[token] = decimals; - return(tokensToDecimals); - - def getOnchainData(self, pools): - # reset multicaller - self.mc.reset(); - - # load the vault contract - vault = self.balLoadContract("Vault"); - target = vault.address; - - poolAbis = {}; - for poolType in pools.keys(): - poolAbis[poolType] = self.balPoolGetAbi(poolType); - - payload = []; - pidAndFns = []; - outputAbis = {}; - - poolToType = {}; - for poolType in pools.keys(): - poolIds = pools[poolType]; - poolAbi = poolAbis[poolType]; - - # construct all the calls in format (VaultAddress, encodedCallData) - for poolId in poolIds: - poolToType[poolId] = poolType; - - poolAddress = self.balPooldIdToAddress(poolId); - currPool = self.balLoadArbitraryContract(poolAddress, self.mc.listToString(poolAbi)) - - # === all pools have tokens and swap fee, pausedState === - self.mc.addCall(vault.address, vault.abi, 'getPoolTokens', args=[poolId]); - self.mc.addCall(currPool.address, currPool.abi, 'getSwapFeePercentage'); - self.mc.addCall(currPool.address, currPool.abi, 'getPausedState'); - pidAndFns.append((poolId, "getPoolTokens")); - pidAndFns.append((poolId, "getSwapFeePercentage")); - pidAndFns.append((poolId, "getPausedState")); - - # === using weighted math === - if poolType in ["Weighted", "LiquidityBootstrapping", "Investment"]: - self.mc.addCall(currPool.address, currPool.abi, 'getNormalizedWeights'); - pidAndFns.append((poolId, "getNormalizedWeights")); - - # === using stable math === - if poolType in ["Stable", "MetaStable"]: - self.mc.addCall(currPool.address, currPool.abi, 'getAmplificationParameter'); - pidAndFns.append((poolId, "getAmplificationParameter")); - - # === have pausable swaps by pool owner === - if poolType in [ "LiquidityBootstrapping", "Investment"]: - self.mc.addCall(currPool.address, currPool.abi, 'getSwapEnabled'); - pidAndFns.append((poolId, "getSwapEnabled")); - - data = self.mc.execute(); - - chainDataOut = {}; - chainDataBookkeeping = {}; - - for decodedOutputData, pidAndFn in zip(data, pidAndFns): - poolId = pidAndFn[0]; - decoder = pidAndFn[1]; - - if not poolId in chainDataOut.keys(): - chainDataOut[poolId] = {"poolType":poolToType[poolId]}; - chainDataBookkeeping[poolId] = {}; - - if decoder == "getPoolTokens": - addresses = list(decodedOutputData[0]); - balances = list(decodedOutputData[1]); - lastChangeBlock = decodedOutputData[2]; - - chainDataOut[poolId]["lastChangeBlock"] = lastChangeBlock; - chainDataBookkeeping[poolId]["orderedAddresses"] = addresses; - - chainDataOut[poolId]["tokens"] = {}; - for address, balance in zip(addresses, balances): - chainDataOut[poolId]["tokens"][address] = {"rawBalance":str(balance)}; - - elif decoder == "getNormalizedWeights": - normalizedWeights = list(decodedOutputData[0]); - addresses = chainDataBookkeeping[poolId]["orderedAddresses"]; - for address, normalizedWeight in zip(addresses, normalizedWeights): - weight = Decimal(str(normalizedWeight)) * Decimal(str(1e-18)); - chainDataOut[poolId]["tokens"][address]["weight"] = str(weight); - - elif decoder == "getSwapFeePercentage": - swapFee = Decimal(decodedOutputData[0]) * Decimal(str(1e-18)); - chainDataOut[poolId]["swapFee"] = str(swapFee); - - elif decoder == "getAmplificationParameter": - rawAmp = Decimal(decodedOutputData[0]); - scaling = Decimal(decodedOutputData[2]); - chainDataOut[poolId]["amp"] = str(rawAmp/scaling); - - elif decoder == "getSwapEnabled": - chainDataOut[poolId]["swapEnabled"] = decodedOutputData[0]; - - elif decoder == "getPausedState": - chainDataOut[poolId]["pausedState"] = decodedOutputData[0]; - - #find tokens for which decimals have not been cached - tokens = set(); - for pool in chainDataOut.keys(): - for token in chainDataOut[pool]["tokens"].keys(): - tokens.add(token); - haveDecimalsFor = set(self.decimals.keys()); - - # set subtraction (A - B) gives "What is in A that isn't in B?" - needDecimalsFor = tokens - haveDecimalsFor; - - #if there are any, query them onchain using multicall - if len(needDecimalsFor) > 0: - print("New tokens found. Caching decimals for", len(needDecimalsFor), "tokens...") - self.multiCallErc20BatchDecimals(needDecimalsFor); - - # update rawBalances to have decimal adjusted values - for pool in chainDataOut.keys(): - for token in chainDataOut[pool]["tokens"].keys(): - rawBalance = chainDataOut[pool]["tokens"][token]["rawBalance"]; - decimals = self.erc20GetDecimals(token); - chainDataOut[pool]["tokens"][token]["balance"] = str(Decimal(rawBalance) * Decimal(10**(-decimals))); - - return(chainDataOut); - - def generateDeploymentsDocsTable(self): - outputString = ""; - contracts = []; - addresses = []; - - network = self.network; - blockExplorer = "https://" + self.networkParams[network]["blockExplorerUrl"]; - outputString += "{% tab title=\"" + network.title() + "\" %}\n" - - for contractType in self.deploymentAddresses: - contracts.append(contractType); - - address = self.deploymentAddresses[contractType]; - etherscanPage = blockExplorer + "/address/" + address; - addresses.append("[" + address + "](" + etherscanPage + ")"); - - longestContractStringLength = getLongestStringLength(contracts); - longestAddressStringLength = getLongestStringLength(addresses); - - contractDashLine = "".join(["-"]*longestContractStringLength); - addressDashLine = "".join(["-"]*longestAddressStringLength); - - contracts = ["Contracts", contractDashLine] + contracts; - addresses = ["Addresses", addressDashLine] + addresses; - - for (c,a) in zip(contracts, addresses): - cPadded = padWithSpaces(c, longestContractStringLength); - aPadded = padWithSpaces(a, longestAddressStringLength); - line = "| " + cPadded + " | " + aPadded + " |\n" - outputString += line - outputString += "\n{% endtab %}" - return(outputString) + + """ + Balancer Protocol Python API + Interface with Balancer V2 Smart contracts directly from Python + """ + + DELEGATE_OWNER = "0xBA1BA1ba1BA1bA1bA1Ba1BA1ba1BA1bA1ba1ba1B" + ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" + + # Constants + INFINITE = 2**256 - 1 + # for infinite unlock + MAX_UINT_112 = 2**112 - 1 + # for stablephantom max bpt + + # Environment variable names + envVarEtherscan = "KEY_API_ETHERSCAN" + envVarInfura = "KEY_API_INFURA" + envVarPrivate = "KEY_PRIVATE" + envVarCustomRPC = "BALPY_CUSTOM_RPC" + + # Etherscan API call management + lastEtherscanCallTime = 0 + etherscanMaxRate = 5.0 + # hz + etherscanSpeedDict = { + "slow": "SafeGasPrice", + "average": "ProposeGasPrice", + "fast": "FastGasPrice", + } + speedDict = { + "glacial": glacial_gas_price_strategy, + "slow": slow_gas_price_strategy, + "average": medium_gas_price_strategy, + "fast": fast_gas_price_strategy, + } + + # Network parameters + networkParams = { + "mainnet": { + "id": 1, + "blockExplorerUrl": "etherscan.io", + "balFrontend": "app.balancer.fi/#/", + }, + "ropsten": {"id": 3, "blockExplorerUrl": "ropsten.etherscan.io"}, + "rinkeby": {"id": 4, "blockExplorerUrl": "rinkeby.etherscan.io"}, + "goerli": {"id": 5, "blockExplorerUrl": "goerli.etherscan.io"}, + "optimism": {"id": 10, "blockExplorerUrl": "optimistic.etherscan.io"}, + "kovan": { + "id": 42, + "blockExplorerUrl": "kovan.etherscan.io", + "balFrontend": "kovan.balancer.fi/#/", + }, + "polygon": { + "id": 137, + "blockExplorerUrl": "polygonscan.com", + "balFrontend": "polygon.balancer.fi/#/", + }, + "fantom": { + "id": 250, + "blockExplorerUrl": "ftmscan.com", + "balFrontend": "app.beets.fi/#/", + }, + "arbitrum": { + "id": 42161, + "blockExplorerUrl": "arbiscan.io", + "balFrontend": "arbitrum.balancer.fi/#/", + }, + } + + # ABIs and Deployment Addresses + abis = {} + deploymentAddresses = {} + contractDirectories = { + # ===== Vault Infra ===== + "Vault": {"directory": "20210418-vault"}, + "BalancerHelpers": {"directory": "20210418-vault"}, + "Authorizer": {"directory": "20210418-authorizer"}, + # ====== Pools and Associated Contracts ====== + "WeightedPoolFactory": {"directory": "20220908-weighted-pool-v2"}, + "WeightedPool2TokensFactory": {"directory": "20210418-weighted-pool"}, + "StablePoolFactory": {"directory": "20220609-stable-pool-v2"}, + "LiquidityBootstrappingPoolFactory": { + "directory": "20210721-liquidity-bootstrapping-pool" + }, + "MetaStablePoolFactory": {"directory": "20210727-meta-stable-pool"}, + "WstETHRateProvider": {"directory": "20210812-wsteth-rate-provider"}, + "InvestmentPoolFactory": {"directory": "20210907-investment-pool"}, + "StablePhantomPoolFactory": {"directory": "20211208-stable-phantom-pool"}, + "ComposableStablePoolFactory": {"directory": "20220906-composable-stable-pool"}, + "AaveLinearPoolFactory": {"directory": "20220817-aave-rebalanced-linear-pool"}, + "ERC4626LinearPoolFactory": {"directory": "20220304-erc4626-linear-pool"}, + "NoProtocolFeeLiquidityBootstrappingPoolFactory": { + "directory": "20211202-no-protocol-fee-lbp" + }, + "ManagedPoolFactory": {"directory": "20221021-managed-pool"}, + # ===== Relayers and Infra ===== + # TODO: update dir to 20220318-batch-relayer-v2 once deployed on all networks + "BalancerRelayer": {"directory": "20211203-batch-relayer"}, + "BatchRelayerLibrary": {"directory": "20211203-batch-relayer"}, + "LidoRelayer": {"directory": "20210812-lido-relayer"}, + # ===== Liquidity Mining Infra ===== + "MerkleRedeem": {"directory": "20210811-ldo-merkle"}, + "MerkleOrchard": {"directory": "20211012-merkle-orchard"}, + # ===== Gauges and Infra ===== + "AuthorizerAdaptor": {"directory": "20220325-authorizer-adaptor"}, + "BALTokenHolderFactory": {"directory": "20220325-bal-token-holder-factory"}, + "BalancerTokenAdmin": {"directory": "20220325-balancer-token-admin"}, + "GaugeAdder": {"directory": "20220325-gauge-adder"}, + "VotingEscrow": {"directory": "20220325-gauge-controller"}, + "GaugeController": {"directory": "20220325-gauge-controller"}, + "BalancerMinter": {"directory": "20220325-gauge-controller"}, + "LiquidityGaugeFactory": {"directory": "20220325-mainnet-gauge-factory"}, + "SingleRecipientGaugeFactory": { + "directory": "20220325-single-recipient-gauge-factory" + }, + "VotingEscrowDelegation": {"directory": "20220325-ve-delegation"}, + "VotingEscrowDelegationProxy": {"directory": "20220325-ve-delegation"}, + "veBALDeploymentCoordinator": { + "directory": "20220325-veBAL-deployment-coordinator" + }, + "ArbitrumRootGaugeFactory": { + "directory": "20220413-arbitrum-root-gauge-factory" + }, + "PolygonRootGaugeFactory": {"directory": "20220413-polygon-root-gauge-factory"}, + "ChildChainStreamer": {"directory": "20220413-child-chain-gauge-factory"}, + "ChildChainLiquidityGaugeFactory": { + "directory": "20220413-child-chain-gauge-factory" + }, + "veBALL2GaugeSetupCoordinator": { + "directory": "20220415-veBAL-L2-gauge-setup-coordinator" + }, + "veBALGaugeFixCoordinator": { + "directory": "20220418-veBAL-gauge-fix-coordinator" + }, + "FeeDistributor": {"directory": "20220420-fee-distributor"}, + "SmartWalletChecker": {"directory": "20220420-smart-wallet-checker"}, + "SmartWalletCheckerCoordinator": { + "directory": "20220421-smart-wallet-checker-coordinator" + }, + "DistributionScheduler": {"directory": "20220422-distribution-scheduler"}, + "ProtocolFeePercentagesProvider": { + "directory": "20220725-protocol-fee-percentages-provider" + }, + } + + decimals = {} + + UserBalanceOpKind = { + "DEPOSIT_INTERNAL": 0, + "WITHDRAW_INTERNAL": 1, + "TRANSFER_INTERNAL": 2, + "TRANSFER_EXTERNAL": 3, + } + inverseUserBalanceOpKind = { + 0: "DEPOSIT_INTERNAL", + 1: "WITHDRAW_INTERNAL", + 2: "TRANSFER_INTERNAL", + 3: "TRANSFER_EXTERNAL", + } + + def __init__(self, network=None, verbose=True, customConfigFile=None, manualEnv={}): + super(balpy, self).__init__() + + self.verbose = verbose + if self.verbose: + print() + print("==============================================================") + print("============== Initializing Balancer Python API ==============") + print("==============================================================") + print() + + if network is None: + print("No network set. Defaulting to kovan") + network = "kovan" + else: + print("Network is set to", network) + self.network = network.lower() + + # set high decimal precision + getcontext().prec = 28 + + # grab parameters from env vars if they exist + self.infuraApiKey = os.environ.get(self.envVarInfura) + self.customRPC = os.environ.get(self.envVarCustomRPC) + self.etherscanApiKey = os.environ.get(self.envVarEtherscan) + self.privateKey = os.environ.get(self.envVarPrivate) + + # override params with manually passed args if they exist + if "infuraApiKey" in manualEnv.keys(): + self.infuraApiKey = manualEnv["infuraApiKey"] + if "customRPC" in manualEnv.keys(): + self.customRPC = manualEnv["customRPC"] + if "etherscanApiKey" in manualEnv.keys(): + self.etherscanApiKey = manualEnv["etherscanApiKey"] + if "privateKey" in manualEnv.keys(): + self.privateKey = manualEnv["privateKey"] + + if self.infuraApiKey is None and self.customRPC is None: + self.ERROR( + "You need to add your KEY_API_INFURA or BALPY_CUSTOM_RPC environment variables\n" + ) + self.ERROR("!! If you are using L2, you must use BALPY_CUSTOM_RPC !!") + print("\t\texport " + self.envVarInfura + "=") + print("\t\t\tOR") + print("\t\texport " + self.envVarCustomRPC + "=") + print( + "\n\t\tNOTE: if you set " + + self.envVarCustomRPC + + ", it will override your Infura API key!" + ) + quit() + + if self.etherscanApiKey is None or self.privateKey is None: + self.ERROR("You need to add your keys to the your environment variables") + print("\t\texport " + self.envVarEtherscan + "=") + print("\t\texport " + self.envVarPrivate + "=") + quit() + + endpoint = self.customRPC + if endpoint is None: + endpoint = "https://" + self.network + ".infura.io/v3/" + self.infuraApiKey + + self.endpoint = endpoint + self.web3 = Web3(Web3.HTTPProvider(endpoint)) + + acct = self.web3.eth.account.privateKeyToAccount(self.privateKey) + self.web3.eth.default_account = acct.address + self.address = acct.address + + # initialize gas block caches + self.currGasPriceSpeed = None + self.web3.middleware_onion.add(middleware.time_based_cache_middleware) + self.web3.middleware_onion.add(middleware.latest_block_based_cache_middleware) + self.web3.middleware_onion.add(middleware.simple_cache_middleware) + + # add support for PoA chains + self.web3.middleware_onion.inject(geth_poa_middleware, layer=0) + + if self.verbose: + print("Initialized account", self.web3.eth.default_account) + print("Connected to web3 at", endpoint) + + usingCustomConfig = not customConfigFile is None + customConfig = None + if usingCustomConfig: + + # load custom config file if it exists, quit if not + if not os.path.isfile(customConfigFile): + self.ERROR("Custom config file" + customConfigFile + " not found!") + quit() + else: + with open(customConfigFile, "r") as f: + customConfig = json.load(f) + + # ensure all required fields are in the customConfig + requiredFields = ["contracts", "networkParams"] + hasAllRequirements = True + for req in requiredFields: + if not req in customConfig.keys(): + hasAllRequirements = False + if not hasAllRequirements: + self.ERROR("Not all custom fields are in the custom config!") + print("You must include:") + for req in requiredFields: + print("\t" + req) + print() + quit() + + # add network params for network + currNetworkParams = { + "id": customConfig["networkParams"]["id"], + "blockExplorerUrl": customConfig["networkParams"]["blockExplorerUrl"], + } + + if "balFrontend" in customConfig["networkParams"].keys(): + currNetworkParams["balFrontend"] = customConfig["networkParams"][ + "balFrontend" + ] + self.networkParams[self.network] = currNetworkParams + + self.mc = multicaller.multicaller( + _chainId=self.networkParams[self.network]["id"], + _web3=self.web3, + _maxRetries=5, + _verbose=False, + ) + + # reset for the edge case in which we're iterating through multiple networks + self.deploymentAddresses = {} + missingContracts = [] + for contractType in self.contractDirectories.keys(): + subdir = self.contractDirectories[contractType]["directory"] + + # get contract abi from deployment + abiPath = os.path.join("deployments", subdir, "abi", contractType + ".json") + try: + f = pkgutil.get_data(__name__, abiPath).decode() + currAbi = json.loads(f) + self.abis[contractType] = currAbi + except BaseException as error: + self.ERROR("Error accessing file: {}".format(abiPath)) + self.ERROR("{}".format(error)) + + # get deployment address for given network + try: + if usingCustomConfig: + currAddress = self.web3.toChecksumAddress( + customConfig["contracts"][contractType] + ) + else: + deploymentPath = os.path.join( + "deployments", subdir, "output", self.network + ".json" + ) + f = pkgutil.get_data(__name__, deploymentPath).decode() + currData = json.loads(f) + currAddress = self.web3.toChecksumAddress(currData[contractType]) + self.deploymentAddresses[contractType] = currAddress + except BaseException as error: + missingContracts.append(contractType) + + print("Available contracts on", self.network) + for element in self.deploymentAddresses.keys(): + address = self.deploymentAddresses[element] + print("\t" + address + "\t" + element) + + print() + print( + "Missing contracts on", + self.network + ": [" + ", ".join(missingContracts) + "]", + ) + + print() + print("==============================================================") + + # ====================== + # ====Color Printing==== + # ====================== + def WARN(self, text): + WARNING_BEGIN = "\033[93m" + WARNING_END = "\033[0m" + print(WARNING_BEGIN + "[WARNING] " + text + WARNING_END) + + def ERROR(self, text): + ERROR_BEGIN = "\033[91m" + ERROR_END = "\033[0m" + print(ERROR_BEGIN + "[ERROR] " + text + ERROR_END) + + def GOOD(self, text): + GOOD_BEGIN = "\033[92m" + GOOD_END = "\033[0m" + print(GOOD_BEGIN + text + GOOD_END) + + # ===================== + # ===Transaction Fns=== + # ===================== + def buildTx( + self, + fn, + gasFactor, + gasSpeed="average", + nonceOverride=-1, + gasEstimateOverride=-1, + gasPriceGweiOverride=-1, + ): + chainIdNetwork = self.networkParams[self.network]["id"] + + # Get nonce if not overridden + if nonceOverride > -1: + nonce = nonceOverride + else: + nonce = self.web3.eth.get_transaction_count(self.web3.eth.default_account) + + # Calculate gas estimate if not overridden + if gasEstimateOverride > -1: + gasEstimate = gasEstimateOverride + else: + try: + gasEstimate = int(fn.estimateGas() * gasFactor) + except BaseException as error: + descriptiveError = be.handleException(error) + self.ERROR("Transaction failed at gas estimation!") + self.ERROR(descriptiveError) + return None + + # Get gas price from Etherscan if not overridden + if gasPriceGweiOverride > -1: + gasPriceGwei = gasPriceGweiOverride + else: + gasPriceGwei = self.getGasPrice(gasSpeed) + + print("\tGas Estimate:\t", gasEstimate) + print("\tGas Price:\t", gasPriceGwei, "Gwei") + print("\tNonce:\t\t", nonce) + + # build transaction + data = fn.buildTransaction( + { + "chainId": chainIdNetwork, + "gas": gasEstimate, + "gasPrice": self.web3.toWei(gasPriceGwei, "gwei"), + "nonce": nonce, + } + ) + return data + + def sendTx(self, tx, isAsync=False): + signedTx = self.web3.eth.account.sign_transaction(tx, self.privateKey) + txHash = self.web3.eth.send_raw_transaction(signedTx.rawTransaction).hex() + + print() + print("Sending transaction, view progress at:") + print( + "\thttps://" + + self.networkParams[self.network]["blockExplorerUrl"] + + "/tx/" + + txHash + ) + + if not isAsync: + self.waitForTx(txHash) + return txHash + + def waitForTx(self, txHash, timeOutSec=120): + txSuccessful = True + print() + print("Waiting for tx:", txHash) + try: + receipt = self.web3.eth.wait_for_transaction_receipt( + txHash, timeout=timeOutSec + ) + if not receipt["status"] == 1: + txSuccessful = False + except BaseException as error: + print("Transaction timeout: {}".format(error)) + return False + + # Race condition: add a small delay to avoid getting the last nonce + time.sleep(0.5) + + print("\tTransaction accepted by network!") + if not txSuccessful: + self.ERROR("Transaction failed!") + return False + print("\tTransaction was successful!\n") + + return True + + def getTxReceipt(self, txHash, delay, maxRetries): + for i in range(maxRetries): + try: + receipt = self.web3.eth.getTransactionReceipt(txHash) + print("Retrieved receipt!") + return receipt + except Exception as e: + print(e) + print( + "Transaction not found yet, will check again in", delay, "seconds" + ) + time.sleep(delay) + self.ERROR("Transaction not found in" + str(maxRetries) + "retries.") + return False + + # ===================== + # ====ERC20 methods==== + # ===================== + @cache + def erc20GetContract(self, tokenAddress): + # Read files packaged in module + abiPath = os.path.join("abi/ERC20.json") + f = pkgutil.get_data(__name__, abiPath).decode() + abi = json.loads(f) + + token = self.web3.eth.contract( + self.web3.toChecksumAddress(tokenAddress), abi=abi + ) + return token + + @cache + def erc20GetDecimals(self, tokenAddress): + + # keep the manually maintained cache since the + # multicaller function can populate it too + if tokenAddress in self.decimals.keys(): + return self.decimals[tokenAddress] + + if tokenAddress == self.ZERO_ADDRESS: + self.decimals[tokenAddress] = 18 + return 18 + + token = self.erc20GetContract(tokenAddress) + decimals = token.functions.decimals().call() + return decimals + + def erc20GetBalanceStandard(self, tokenAddress, address=None): + if address is None: + address = self.address + token = self.erc20GetContract(tokenAddress) + decimals = self.erc20GetDecimals(tokenAddress) + standardBalance = Decimal(token.functions.balanceOf(address).call()) * Decimal( + 10 ** (-decimals) + ) + return standardBalance + + def erc20GetAllowanceStandard(self, tokenAddress, allowedAddress): + token = self.erc20GetContract(tokenAddress) + decimals = self.erc20GetDecimals(tokenAddress) + standardAllowance = Decimal( + token.functions.allowance(self.address, allowedAddress).call() + ) * Decimal(10 ** (-decimals)) + return standardAllowance + + def erc20BuildFunctionSetAllowance(self, tokenAddress, allowedAddress, allowance): + token = self.erc20GetContract(tokenAddress) + approveFunction = token.functions.approve(allowedAddress, allowance) + return approveFunction + + def erc20SignAndSendNewAllowance( + self, + tokenAddress, + allowedAddress, + allowance, + gasFactor, + gasSpeed, + nonceOverride=-1, + gasEstimateOverride=-1, + gasPriceGweiOverride=-1, + isAsync=False, + ): + fn = self.erc20BuildFunctionSetAllowance( + tokenAddress, allowedAddress, allowance + ) + tx = self.buildTx( + fn, + gasFactor, + gasSpeed, + nonceOverride, + gasEstimateOverride, + gasPriceGweiOverride, + ) + txHash = self.sendTx(tx, isAsync) + return txHash + + def erc20HasSufficientBalance(self, tokenAddress, amountToUse): + balance = self.erc20GetBalanceStandard(tokenAddress) + + print("Token:", tokenAddress) + print("\tNeed:", float(amountToUse)) + print("\tWallet has:", float(balance)) + + sufficient = float(balance) >= float(amountToUse) + if not sufficient: + self.ERROR("Insufficient Balance!") + else: + print("\tWallet has sufficient balance.") + print() + return sufficient + + def erc20HasSufficientBalances(self, tokens, amounts): + if not len(tokens) == len(amounts): + self.ERROR( + "Array length mismatch with " + + str(len(tokens)) + + " tokens and " + + str(len(amounts)) + + " amounts." + ) + return False + numElements = len(tokens) + sufficientBalance = True + for i in range(numElements): + token = tokens[i] + amount = amounts[i] + currentHasSufficientBalance = self.erc20HasSufficientBalance(token, amount) + sufficientBalance &= currentHasSufficientBalance + return sufficientBalance + + def erc20HasSufficientAllowance(self, tokenAddress, allowedAddress, amount): + currentAllowance = self.erc20GetAllowanceStandard(tokenAddress, allowedAddress) + balance = self.erc20GetBalanceStandard(tokenAddress) + + print("Token:", tokenAddress) + print("\tCurrent Allowance:", currentAllowance) + print("\tCurrent Balance:", balance) + print("\tAmount to Spend:", amount) + + sufficient = currentAllowance >= Decimal(amount) + + if not sufficient: + print("\tInsufficient allowance!") + print("\tWill need to unlock", tokenAddress) + else: + print("\tWallet has sufficient allowance.") + print() + return sufficient + + def erc20EnforceSufficientAllowance( + self, + tokenAddress, + allowedAddress, + targetAllowance, + amount, + gasFactor, + gasSpeed, + nonceOverride, + gasEstimateOverride, + gasPriceGweiOverride, + isAsync, + ): + if not self.erc20HasSufficientAllowance(tokenAddress, allowedAddress, amount): + if targetAllowance == -1 or targetAllowance == self.INFINITE: + targetAllowance = self.INFINITE + else: + decimals = self.erc20GetDecimals(tokenAddress) + targetAllowance = Decimal(targetAllowance) * Decimal(10**decimals) + targetAllowance = int(targetAllowance) + print("Insufficient Allowance: Increasing to", targetAllowance) + txHash = self.erc20SignAndSendNewAllowance( + tokenAddress, + allowedAddress, + targetAllowance, + gasFactor, + gasSpeed, + nonceOverride=nonceOverride, + isAsync=isAsync, + gasPriceGweiOverride=gasPriceGweiOverride, + ) + return txHash + return None + + def erc20EnforceSufficientVaultAllowance( + self, + tokenAddress, + targetAllowance, + amount, + gasFactor, + gasSpeed, + nonceOverride=-1, + gasEstimateOverride=-1, + gasPriceGweiOverride=-1, + isAsync=False, + ): + return self.erc20EnforceSufficientAllowance( + tokenAddress, + self.deploymentAddresses["Vault"], + targetAllowance, + amount, + gasFactor, + gasSpeed, + nonceOverride, + gasEstimateOverride, + gasPriceGweiOverride, + isAsync, + ) + + def erc20GetTargetAllowancesFromPoolData(self, poolDescription): + (tokens, checksumTokens) = self.balSortTokens( + list(poolDescription["tokens"].keys()) + ) + allowances = [] + for token in tokens: + targetAllowance = -1 + if "allowance" in poolDescription["tokens"][token].keys(): + targetAllowance = poolDescription["tokens"][token]["allowance"] + if targetAllowance == -1: + targetAllowance = self.INFINITE + allowances.append(targetAllowance) + return (tokens, allowances) + + def erc20AsyncEnforceSufficientVaultAllowances( + self, + tokens, + targetAllowances, + amounts, + gasFactor, + gasSpeed, + nonceOverride=-1, + gasEstimateOverride=-1, + gasPriceGweiOverride=-1, + ): + if not len(tokens) == len(targetAllowances): + self.ERROR( + "Array length mismatch with " + + str(len(tokens)) + + " tokens and " + + str(len(targetAllowances)) + + " targetAllowances." + ) + return False + + nonce = self.web3.eth.get_transaction_count(self.web3.eth.default_account) + txHashes = [] + numElements = len(tokens) + for i in range(numElements): + token = tokens[i] + targetAllowance = targetAllowances[i] + amount = amounts[i] + txHash = self.erc20EnforceSufficientVaultAllowance( + token, + targetAllowance, + amount, + gasFactor, + gasSpeed, + nonceOverride=nonce, + isAsync=True, + ) + if not txHash is None: + txHashes.append(txHash) + nonce += 1 + + for txHash in txHashes: + self.waitForTx(txHash) + return True + + # ===================== + # ======Etherscan====== + # ===================== + def generateEtherscanApiUrl(self): + etherscanUrl = self.networkParams[self.network]["blockExplorerUrl"] + separator = "." + if self.network in ["kovan", "rinkeby", "goerli", "optimism"]: + separator = "-" + urlFront = "https://api" + separator + etherscanUrl + return urlFront + + def callEtherscan(self, url, maxRetries=3, verbose=False): + urlFront = self.generateEtherscanApiUrl() + url = urlFront + url + self.etherscanApiKey + if verbose: + print("Calling:", url) + + count = 0 + while count < maxRetries: + try: + dt = time.time() - self.lastEtherscanCallTime + if dt < 1.0 / self.etherscanMaxRate: + time.sleep((1.0 / self.etherscanMaxRate - dt) * 1.1) + + # faking a user-agent resolves the 403 (forbidden) errors on api-kovan.etherscan.io + headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.76 Safari/537.36", + "Upgrade-Insecure-Requests": "1", + "DNT": "1", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", + "Accept-Language": "en-US,en;q=0.5", + "Accept-Encoding": "gzip, deflate", + } + r = requests.get(url, headers=headers) + if verbose: + print("\t", r) + self.lastEtherscanCallTime = time.time() + data = r.json() + if verbose: + print("\t", data) + return data + except Exception as e: + print("Exception:", e) + count += 1 + delaySec = 2 + if verbose: + self.WARN( + "Etherscan failed " + + str(count) + + " times. Retrying in " + + str(delaySec) + + " seconds..." + ) + time.sleep(delaySec) + self.ERROR("Etherscan failed " + str(count) + " times.") + return False + + def getGasPriceEtherscanGwei(self, speed, verbose=False): + if not speed in self.etherscanSpeedDict.keys(): + self.ERROR("Speed entered is:" + speed) + self.ERROR("Speed must be one of the following options:") + for s in self.etherscanSpeedDict.keys(): + print("\t" + s) + return False + + urlString = "/api?module=gastracker&action=gasoracle&apikey=" + # + self.etherscanApiKey; + response = self.callEtherscan(urlString, verbose=verbose) + return response["result"][self.etherscanSpeedDict[speed]] + + def getTransactionsByAddress( + self, address, internal=False, startblock=0, verbose=False + ): + if verbose: + print("\tQuerying data after block", startblock) + + internalString = "" + if internal: + internalString = "internal" + + url = [] + url.append( + "/api?module=account&action=txlist{}&address=".format(internalString) + ) + url.append(address) + url.append( + "&startblock={}&endblock=99999999&sort=asc&apikey=".format(startblock) + ) + urlString = "".join(url) + txns = self.callEtherscan(urlString, verbose=verbose) + + if int(txns["status"]) == 0: + self.ERROR("Etherscan query failed. Please try again.") + return False + elif int(txns["status"]) == 1: + return txns["result"] + + def getTransactionByHash(self, txHash, verbose=False): + urlString = "/api?module=proxy&action=eth_getTransactionByHash&txhash={}&apikey=".format( + txHash + ) + txns = self.callEtherscan(urlString, verbose=verbose) + + if verbose: + print(txns) + + if txns == False: + return False + return txns + + def isContractVerified(self, poolId, verbose=False): + address = self.balPooldIdToAddress(poolId) + url = "/api?module=contract&action=getabi&address={}&apikey=".format(address) + results = self.callEtherscan(url, verbose=verbose) + if verbose: + print(results) + isUnverified = results["result"] == "Contract source code not verified" + return not isUnverified + + def getGasPricePolygon(self, speed): + if speed in self.etherscanSpeedDict.keys(): + etherscanGasSpeedNamesToPolygon = { + "slow": "safeLow", + "average": "standard", + "fast": "fast", + } + speed = etherscanGasSpeedNamesToPolygon[speed] + + allowedSpeeds = ["safeLow", "standard", "fast", "fastest"] + if speed not in allowedSpeeds: + self.ERROR("Speed entered is:" + speed) + self.ERROR("Speed must be one of the following options:") + for s in allowedSpeeds: + print("\t" + s) + return False + + r = requests.get("https://gasstation-mainnet.matic.network/") + prices = r.json() + return prices[speed] + + def getGasPrice(self, speed): + allowedSpeeds = list(self.speedDict.keys()) + if speed not in allowedSpeeds: + self.ERROR("Speed entered is:" + speed) + self.ERROR("Speed must be one of the following options:") + for s in allowedSpeeds: + print("\t" + s) + return False + + if not speed == self.currGasPriceSpeed: + self.currGasPriceSpeed = speed + self.web3.eth.set_gas_price_strategy(self.speedDict[speed]) + + gasPrice = self.web3.eth.generateGasPrice() * 1e-9 + return gasPrice + + def balSortTokens(self, tokensIn): + # tokens need to be sorted as lowercase, but if they're provided as checksum, then + # the checksum format strings are still the keys outside of this function, so they + # must be preserved as they're input + lowerTokens = [t.lower() for t in tokensIn] + lowerToOriginal = {} + for i in range(len(tokensIn)): + lowerToOriginal[lowerTokens[i]] = tokensIn[i] + lowerTokens.sort() + + # get checksum tokens, translated sorted lower tokens back to their original format + checksumTokens = [self.web3.toChecksumAddress(t) for t in lowerTokens] + sortedInputTokens = [lowerToOriginal[f] for f in lowerTokens] + + return (sortedInputTokens, checksumTokens) + + def balWeightsEqualOne(self, poolData): + tokenData = poolData["tokens"] + tokens = tokenData.keys() + + weightSum = Decimal(0.0) + for token in tokens: + weightSum += Decimal(tokenData[token]["weight"]) + + weightEqualsOne = weightSum == Decimal(1.0) + if not weightEqualsOne: + self.ERROR( + "Token weights add up to " + + str(weightSum) + + ", but they must add up to 1.0" + ) + self.ERROR( + "If you are passing more than 16 digits of precision, you must pass the value as a string" + ) + return weightEqualsOne + + def balConvertTokensToWei(self, tokens, amounts): + rawTokens = [] + if not len(tokens) == len(amounts): + self.ERROR( + "Array length mismatch with " + + str(len(tokens)) + + " tokens and " + + str(len(amounts)) + + " amounts." + ) + return False + numElements = len(tokens) + for i in range(numElements): + token = tokens[i] + rawValue = amounts[i] + decimals = self.erc20GetDecimals(token) + if rawValue == self.INFINITE or rawValue == self.MAX_UINT_112: + decimals = 0 + raw = int(Decimal(rawValue) * Decimal(10**decimals)) + rawTokens.append(raw) + return rawTokens + + def balSetOwner(self, poolData): + owner = self.ZERO_ADDRESS + if "owner" in poolData.keys(): + ownerAddress = poolData["owner"] + if not len(ownerAddress) == 42: + self.ERROR( + 'Entry for "owner" must be a 42 character Ethereum address beginning with "0x"' + ) + return False + owner = self.web3.toChecksumAddress(ownerAddress) + return owner + + def balCreateFnWeightedPoolFactory(self, poolData): + factory = self.balLoadContract("WeightedPoolFactory") + (tokens, checksumTokens) = self.balSortTokens(list(poolData["tokens"].keys())) + + intWithDecimalsWeights = [ + int(Decimal(poolData["tokens"][t]["weight"]) * Decimal(1e18)) + for t in tokens + ] + swapFeePercentage = int(Decimal(poolData["swapFeePercent"]) * Decimal(1e16)) + rateProviders = [ + self.web3.toChecksumAddress(poolData["tokens"][token]["rateProvider"]) + for token in tokens + ] + + if not self.balWeightsEqualOne(poolData): + return False + + owner = self.balSetOwner(poolData) + + createFunction = factory.functions.create( + poolData["name"], + poolData["symbol"], + checksumTokens, + intWithDecimalsWeights, + rateProviders, + swapFeePercentage, + owner, + ) + return createFunction + + def balCreateFnWeightedPool2TokensFactory(self, poolData): + factory = self.balLoadContract("WeightedPool2TokensFactory") + (tokens, checksumTokens) = self.balSortTokens(list(poolData["tokens"].keys())) + + if not len(tokens) == 2: + self.ERROR( + "WeightedPool2TokensFactory requires 2 tokens, but", + len(tokens), + "were given.", + ) + return False + + if not self.balWeightsEqualOne(poolData): + return False + + intWithDecimalsWeights = [ + int(Decimal(poolData["tokens"][t]["weight"]) * Decimal(1e18)) + for t in tokens + ] + swapFeePercentage = int(Decimal(poolData["swapFeePercent"]) * Decimal(1e16)) + + owner = self.balSetOwner(poolData) + + oracleEnabled = False + if "oracleEnabled" in poolData.keys(): + oracleEnabled = poolData["oracleEnabled"] + if isinstance(oracleEnabled, str): + if oracleEnabled.lower() == "true": + oracleEnabled = True + else: + oracleEnabled = False + + createFunction = factory.functions.create( + poolData["name"], + poolData["symbol"], + checksumTokens, + intWithDecimalsWeights, + swapFeePercentage, + oracleEnabled, + owner, + ) + return createFunction + + def balCreateFnStablePoolFactory(self, poolData): + factory = self.balLoadContract("StablePoolFactory") + (tokens, checksumTokens) = self.balSortTokens(list(poolData["tokens"].keys())) + swapFeePercentage = int(Decimal(poolData["swapFeePercent"]) * Decimal(1e16)) + + owner = self.balSetOwner(poolData) + + createFunction = factory.functions.create( + poolData["name"], + poolData["symbol"], + checksumTokens, + int(poolData["amplificationParameter"]), + swapFeePercentage, + owner, + ) + return createFunction + + def balCreateFnLBPoolFactory(self, poolData): + return self.balCreateFnLBPFactory(poolData, "LiquidityBootstrappingPoolFactory") + + def balCreateFnNoProtocolFeeLiquidityBootstrappingPoolFactory(self, poolData): + return self.balCreateFnLBPFactory( + poolData, "NoProtocolFeeLiquidityBootstrappingPoolFactory" + ) + + def balCreateFnLBPFactory(self, poolData, factoryName): + factory = self.balLoadContract(factoryName) + (tokens, checksumTokens) = self.balSortTokens(list(poolData["tokens"].keys())) + + if not self.balWeightsEqualOne(poolData): + return False + + swapFeePercentage = int(Decimal(poolData["swapFeePercent"]) * Decimal(1e16)) + intWithDecimalsWeights = [ + int(Decimal(poolData["tokens"][t]["weight"]) * Decimal(1e18)) + for t in tokens + ] + owner = self.balSetOwner(poolData) + + if not owner == self.address: + self.WARN("!!! You are not the owner for your LBP !!!") + self.WARN("You:\t\t" + self.address) + self.WARN("Pool Owner:\t" + owner) + + print() + self.WARN( + "Only the pool owner can add liquidity. If you do not control " + + owner + + " then you will not be able to add liquidity!" + ) + self.WARN( + "If you DO control " + + owner + + ', you will need to use the "INIT" join type from that address' + ) + cancelTimeSec = 30 + self.WARN( + "If the owner mismatch is was unintentional, you have " + + str(cancelTimeSec) + + " seconds to cancel with Ctrl+C." + ) + time.sleep(cancelTimeSec) + + createFunction = factory.functions.create( + poolData["name"], + poolData["symbol"], + checksumTokens, + intWithDecimalsWeights, + swapFeePercentage, + owner, + poolData["swapEnabledOnStart"], + ) + return createFunction + + def balCreateFnMetaStablePoolFactory(self, poolData): + factory = self.balLoadContract("MetaStablePoolFactory") + (tokens, checksumTokens) = self.balSortTokens(list(poolData["tokens"].keys())) + swapFeePercentage = int(Decimal(poolData["swapFeePercent"]) * Decimal(1e16)) + owner = self.balSetOwner(poolData) + + rateProviders = [ + self.web3.toChecksumAddress(poolData["tokens"][token]["rateProvider"]) + for token in tokens + ] + priceRateCacheDurations = [ + int(poolData["tokens"][token]["priceRateCacheDuration"]) for token in tokens + ] + + createFunction = factory.functions.create( + poolData["name"], + poolData["symbol"], + checksumTokens, + int(poolData["amplificationParameter"]), + rateProviders, + priceRateCacheDurations, + swapFeePercentage, + poolData["oracleEnabled"], + owner, + ) + return createFunction + + def balCreateFnInvestmentPoolFactory(self, poolData): + factory = self.balLoadContract("InvestmentPoolFactory") + (tokens, checksumTokens) = self.balSortTokens(list(poolData["tokens"].keys())) + swapFeePercentage = int(Decimal(poolData["swapFeePercent"]) * Decimal(1e16)) + intWithDecimalsWeights = [ + int(Decimal(poolData["tokens"][t]["weight"]) * Decimal(1e18)) + for t in tokens + ] + managementFeePercentage = int( + Decimal(poolData["managementFeePercent"]) * Decimal(1e16) + ) + # Deployed factory doesn't allow asset managers + # assetManagers = [0 for i in range(0,len(tokens))] + owner = self.balSetOwner(poolData) + if not owner == self.address: + self.WARN("!!! You are not the owner for your Investment Pool !!!") + self.WARN("You:\t\t" + self.address) + self.WARN("Pool Owner:\t" + owner) + + print() + self.WARN( + "Only the pool owner can call permissioned functions, such as changing weights or the management fee." + ) + self.WARN( + owner + + " should either be you, or a multi-sig or other contract that you control and can call permissioned functions from." + ) + cancelTimeSec = 30 + self.WARN( + "If the owner mismatch is was unintentional, you have " + + str(cancelTimeSec) + + " seconds to cancel with Ctrl+C." + ) + time.sleep(cancelTimeSec) + + createFunction = factory.functions.create( + poolData["name"], + poolData["symbol"], + checksumTokens, + intWithDecimalsWeights, + swapFeePercentage, + owner, + poolData["swapEnabledOnStart"], + managementFeePercentage, + ) + return createFunction + + def balCreateFnManagedPoolFactory(self, poolData): + self.WARN("!!! You are using the Managed Pool Factory without a controller !!!") + self.WARN( + "You are currently using a factory to deploy a managed pool without a factory-provided controller contract." + ) + self.WARN( + "It is highly recommended that you use a factory that pairs a controller with a pool" + ) + self.WARN( + "While this will be a valid pool, the owner will have a dangerous level of power over the pool" + ) + self.WARN( + "It *is* technically possible to add a controller contract as `owner`, but using a factory-paired one provides more guarantees" + ) + + factory = self.balLoadContract("ManagedPoolFactory") + (tokens, checksumTokens) = self.balSortTokens(list(poolData["tokens"].keys())) + swapFeePercentage = int(Decimal(poolData["swapFeePercent"]) * Decimal(1e16)) + intWithDecimalsWeights = [ + int(Decimal(poolData["tokens"][t]["weight"]) * Decimal(1e18)) + for t in tokens + ] + assetManagers = [poolData["tokens"][t]["assetManager"] for t in tokens] + managementAumFeePercentage = int( + Decimal(poolData["managementAumFeePercentage"]) * Decimal(1e16) + ) + + owner = self.balSetOwner(poolData) + if not owner == self.address: + self.WARN("!!! You are not the owner for your Managed Pool !!!") + self.WARN("You:\t\t" + self.address) + self.WARN("Pool Owner:\t" + owner) + + print() + self.WARN( + "Only the pool owner can call permissioned functions, such as changing weights or the management fee." + ) + self.WARN( + owner + + " should either be you, or a multi-sig or other contract that you control and can call permissioned functions from." + ) + cancelTimeSec = 30 + self.WARN( + "If the owner mismatch is was unintentional, you have " + + str(cancelTimeSec) + + " seconds to cancel with Ctrl+C." + ) + time.sleep(cancelTimeSec) + + createFunction = factory.functions.create( + ( + poolData["name"], + poolData["symbol"], + checksumTokens, + intWithDecimalsWeights, + assetManagers, + swapFeePercentage, + poolData["swapEnabledOnStart"], + poolData["mustAllowlistLPs"], + managementAumFeePercentage, + int(poolData["aumFeeId"]), + ), + owner, + ) + return createFunction + + def balCreateFnStablePhantomPoolFactory(self, poolData): + factory = self.balLoadContract("StablePhantomPoolFactory") + (tokens, checksumTokens) = self.balSortTokens(list(poolData["tokens"].keys())) + swapFeePercentage = int(Decimal(poolData["swapFeePercent"]) * Decimal(1e16)) + owner = self.balSetOwner(poolData) + + rateProviders = [ + self.web3.toChecksumAddress(poolData["tokens"][token]["rateProvider"]) + for token in tokens + ] + tokenRateCacheDurations = [ + int(poolData["tokens"][token]["tokenRateCacheDuration"]) for token in tokens + ] + + createFunction = factory.functions.create( + poolData["name"], + poolData["symbol"], + checksumTokens, + int(poolData["amplificationParameter"]), + rateProviders, + tokenRateCacheDurations, + swapFeePercentage, + owner, + ) + return createFunction + + def balCreateFnComposableStablePoolFactory(self, poolData): + factory = self.balLoadContract("ComposableStablePoolFactory") + (tokens, checksumTokens) = self.balSortTokens(list(poolData["tokens"].keys())) + swapFeePercentage = int(Decimal(poolData["swapFeePercent"]) * Decimal(1e16)) + owner = self.balSetOwner(poolData) + + rateProviders = [ + self.web3.toChecksumAddress(poolData["tokens"][token]["rateProvider"]) + for token in tokens + ] + tokenRateCacheDurations = [ + int(poolData["tokens"][token]["tokenRateCacheDuration"]) for token in tokens + ] + exemptFromYieldProtocolFeeFlags = [ + bool(poolData["tokens"][token]["exemptFromYieldProtocolFeeFlags"]) + for token in tokens + ] + + createFunction = factory.functions.create( + poolData["name"], + poolData["symbol"], + checksumTokens, + int(poolData["amplificationParameter"]), + rateProviders, + tokenRateCacheDurations, + exemptFromYieldProtocolFeeFlags, + swapFeePercentage, + owner, + ) + return createFunction + + def balCreateFnLinearPoolFactory(self, poolData, factoryName): + factory = self.balLoadContract(factoryName) + (tokens, checksumTokens) = self.balSortTokens(list(poolData["tokens"].keys())) + swapFeePercentage = int(Decimal(poolData["swapFeePercent"]) * Decimal(1e16)) + owner = self.balSetOwner(poolData) + + mainToken = None + wrappedToken = None + for token in poolData["tokens"].keys(): + if poolData["tokens"][token]["isWrappedToken"]: + wrappedToken = token + else: + mainToken = token + + if mainToken == wrappedToken: + self.ERROR( + "AaveLinearPool must have one wrappedToken and one mainToken. Please check your inputs. Quitting..." + ) + return False + + upperTarget = int(poolData["upperTarget"]) + createFunction = factory.functions.create( + poolData["name"], + poolData["symbol"], + self.web3.toChecksumAddress(mainToken), + self.web3.toChecksumAddress(wrappedToken), + upperTarget, + swapFeePercentage, + owner, + ) + return createFunction + + def balCreateFnAaveLinearPoolFactory(self, poolData): + return self.balCreateFnLinearPoolFactory(poolData, "AaveLinearPoolFactory") + + def balCreateFnERC4626LinearPoolFactory(self, poolData): + return self.balCreateFnLinearPoolFactory(poolData, "ERC4626LinearPoolFactory") + + def balCreatePoolInFactory( + self, + poolDescription, + gasFactor, + gasPriceSpeed, + nonceOverride=-1, + gasEstimateOverride=-1, + gasPriceGweiOverride=-1, + ): + createFunction = None + poolFactoryName = poolDescription["poolType"] + "Factory" + + # list of all supported pool factories + # NOTE: when you add a pool factory to this list, be sure to + # add it to the printout of supported factories below + if poolFactoryName == "WeightedPoolFactory": + createFunction = self.balCreateFnWeightedPoolFactory(poolDescription) + if poolFactoryName == "WeightedPool2TokensFactory": + createFunction = self.balCreateFnWeightedPool2TokensFactory(poolDescription) + if poolFactoryName == "StablePoolFactory": + createFunction = self.balCreateFnStablePoolFactory(poolDescription) + if poolFactoryName == "LiquidityBootstrappingPoolFactory": + createFunction = self.balCreateFnLBPoolFactory(poolDescription) + if poolFactoryName == "MetaStablePoolFactory": + createFunction = self.balCreateFnMetaStablePoolFactory(poolDescription) + if poolFactoryName == "InvestmentPoolFactory": + createFunction = self.balCreateFnInvestmentPoolFactory(poolDescription) + if poolFactoryName == "ManagedPoolFactory": + createFunction = self.balCreateFnManagedPoolFactory(poolDescription) + if poolFactoryName == "StablePhantomPoolFactory": + createFunction = self.balCreateFnStablePhantomPoolFactory(poolDescription) + if poolFactoryName == "ComposableStablePoolFactory": + createFunction = self.balCreateFnComposableStablePoolFactory( + poolDescription + ) + if poolFactoryName == "AaveLinearPoolFactory": + createFunction = self.balCreateFnAaveLinearPoolFactory(poolDescription) + if poolFactoryName == "ERC4626LinearPoolFactory": + createFunction = self.balCreateFnERC4626LinearPoolFactory(poolDescription) + if poolFactoryName == "NoProtocolFeeLiquidityBootstrappingPoolFactory": + createFunction = ( + self.balCreateFnNoProtocolFeeLiquidityBootstrappingPoolFactory( + poolDescription + ) + ) + if createFunction is None: + print("No pool factory found with name:", poolFactoryName) + print("Currently supported pool types are:") + print("\tWeightedPool") + print("\tWeightedPool2Token") + print("\tStablePool") + print("\tLiquidityBootstrappingPool") + print("\tMetaStablePool") + print("\tInvestmentPool") + print("\tStablePhantomPool") + print("\tComposableStablePoolFactory") + print("\tAaveLinearPool") + print("\tERC4626LinearPoolFactory") + print("\tNoProtocolFeeLiquidityBootstrappingPoolFactory") + return False + + if not createFunction: + self.ERROR("Pool creation failed.") + return False + print("Pool function created, generating transaction...") + tx = self.buildTx( + createFunction, + gasFactor, + gasPriceSpeed, + nonceOverride, + gasEstimateOverride, + gasPriceGweiOverride, + ) + print("Transaction Generated!") + txHash = self.sendTx(tx) + return txHash + + def balGetPoolIdFromHash(self, txHash): + receipt = self.getTxReceipt(txHash, delay=2, maxRetries=5) + + # PoolRegistered event lives in the Vault + vault = self.balLoadContract("Vault") + + with Suppressor(): + logs = vault.events.PoolRegistered().processReceipt(receipt) + poolId = logs[0]["args"]["poolId"].hex() + + print("Your pool ID is:") + print("\t0x" + str(poolId)) + return poolId + + def balFindPoolFactory(self, poolId): + contractNames = self.deploymentAddresses.keys() + factoryNames = [c for c in contractNames if ("Factory" in c) and ("Pool" in c)] + # can't simply use "PoolFactory" b/c of WeightedPool2TokensFactory + + self.mc.reset() + for factoryName in factoryNames: + factory = self.balLoadContract(factoryName) + poolAddress = self.balPooldIdToAddress(poolId) + self.mc.addCall( + factory.address, factory.abi, "isPoolFromFactory", args=[poolAddress] + ) + data = self.mc.execute() + + foundFactoryName = None + numFound = 0 + for f, d in zip(factoryNames, data): + if d[0]: + foundFactoryName = f + numFound += 1 + + if numFound == 1: + return foundFactoryName + else: + self.ERROR("Was expecting 1 factory, got " + str(numFound)) + self.ERROR( + "Checked the following factories:\n\t\t" + "\n\t\t".join(factoryNames) + ) + return None + + def balGetJoinKindEnum(self, poolId, joinKind): + factoryName = self.balFindPoolFactory(poolId) + + usingWeighted = factoryName in [ + "WeightedPoolFactory", + "WeightedPool2TokensFactory", + "LiquidityBootstrappingPoolFactory", + "InvestmentPoolFactory", + "NoProtocolFeeLiquidityBootstrappingPoolFactory", + "ManagedPoolFactory", + ] + usingStable = factoryName in ["StablePoolFactory", "MetaStablePoolFactory"] + usingStablePhantom = factoryName in [ + "StablePhantomPoolFactory", + "ComposableStablePoolFactory", + ] + + if usingWeighted: + joinKindEnum = WeightedPoolJoinKind[joinKind] + elif usingStable: + joinKindEnum = StablePoolJoinKind[joinKind] + elif usingStablePhantom: + joinKindEnum = StablePhantomPoolJoinKind[joinKind] + else: + self.ERROR( + "PoolType " + + str(factoryName) + + " not supported for JoinKind: " + + joinKind + ) + return None + return joinKindEnum + + def balGetTokensAndAmounts(self, joinDescription): + (sortedTokens, checksumTokens) = self.balSortTokens( + list(joinDescription["tokens"].keys()) + ) + amountKey = "amount" + if ( + not amountKey + in joinDescription["tokens"][ + list(joinDescription["tokens"].keys())[0] + ].keys() + ): + amountKey = "initialBalance" + amountsBySortedTokens = [ + joinDescription["tokens"][token][amountKey] for token in sortedTokens + ] + maxAmountsIn = self.balConvertTokensToWei(sortedTokens, amountsBySortedTokens) + return (checksumTokens, maxAmountsIn) + + def balGetTokensAndAmountsComposable(self, joinDescription): + (checksumTokens, maxAmountsIn) = self.balGetTokensAndAmounts(joinDescription) + + poolAddress = self.balPooldIdToAddress(joinDescription["poolId"]) + poolAddress = self.web3.toChecksumAddress(poolAddress) + + composableAmount = None + + userDataMaxAmountIn = copy.deepcopy(maxAmountsIn) + + for i in range(len(checksumTokens)): + if checksumTokens[i] == poolAddress: + composableAmount = maxAmountsIn[i] + del checksumTokens[i] + del maxAmountsIn[i] + del userDataMaxAmountIn[i] + break + + checksumTokens.insert(0, poolAddress) + maxAmountsIn.insert(0, composableAmount) + + return (checksumTokens, maxAmountsIn, userDataMaxAmountIn) + + def balDoJoinPool( + self, + poolId, + address, + joinPoolRequestTuple, + gasFactor=1.05, + gasPriceSpeed="average", + nonceOverride=-1, + gasEstimateOverride=-1, + gasPriceGweiOverride=-1, + ): + vault = self.balLoadContract("Vault") + joinPoolFunction = vault.functions.joinPool( + poolId, address, address, joinPoolRequestTuple + ) + tx = self.buildTx( + joinPoolFunction, + gasFactor, + gasPriceSpeed, + nonceOverride, + gasEstimateOverride, + gasPriceGweiOverride, + ) + print("Transaction Generated!") + txHash = self.sendTx(tx) + return txHash + + def balDoQueryJoinPool(self, poolId, address, joinPoolRequestTuple): + bh = self.balLoadContract("BalancerHelpers") + (bptOut, amountsIn) = bh.functions.queryJoin( + poolId, address, address, joinPoolRequestTuple + ).call() + return (bptOut, amountsIn) + + def balFormatJoinPoolInit(self, joinDescription): + (checksumTokens, maxAmountsIn) = self.balGetTokensAndAmounts( + copy.deepcopy(joinDescription) + ) + + poolId = joinDescription["poolId"] + factory = self.balFindPoolFactory(poolId) + + userDataMaxAmountsIn = maxAmountsIn + if factory in ["ManagedPoolFactory"]: + ( + checksumTokens, + maxAmountsIn, + userDataMaxAmountsIn, + ) = self.balGetTokensAndAmountsComposable(copy.deepcopy(joinDescription)) + + joinKindEnum = self.balGetJoinKindEnum(poolId, joinDescription["joinKind"]) + userDataEncoded = eth_abi.encode_abi( + ["uint256", "uint256[]"], [int(joinKindEnum), userDataMaxAmountsIn] + ) + address = self.web3.toChecksumAddress(self.web3.eth.default_account) + joinPoolRequestTuple = ( + checksumTokens, + maxAmountsIn, + userDataEncoded.hex(), + joinDescription["fromInternalBalance"], + ) + return (poolId, address, joinPoolRequestTuple) + + def balFormatJoinPoolExactTokensInForBptOut(self, joinDescription): + (checksumTokens, maxAmountsIn) = self.balGetTokensAndAmounts( + copy.deepcopy(joinDescription) + ) + poolId = joinDescription["poolId"] + joinKindEnum = self.balGetJoinKindEnum(poolId, joinDescription["joinKind"]) + userDataEncoded = eth_abi.encode_abi( + ["uint256", "uint256[]"], [int(joinKindEnum), maxAmountsIn] + ) + address = self.web3.toChecksumAddress(self.web3.eth.default_account) + joinPoolRequestTuple = ( + checksumTokens, + maxAmountsIn, + userDataEncoded.hex(), + joinDescription["fromInternalBalance"], + ) + return (poolId, address, joinPoolRequestTuple) + + def balFormatJoinPoolAllTokensInForExactBptOut(self, joinDescription): + (checksumTokens, maxAmountsIn) = self.balGetTokensAndAmounts( + copy.deepcopy(joinDescription) + ) + poolId = joinDescription["poolId"] + poolAddress = self.balPooldIdToAddress(poolId) + bptAmountOut = self.balConvertTokensToWei( + [poolAddress], [joinDescription["bptAmountOut"]] + )[0] + + joinKindEnum = self.balGetJoinKindEnum(poolId, joinDescription["joinKind"]) + userDataEncoded = eth_abi.encode_abi( + ["uint256", "uint256"], [int(joinKindEnum), bptAmountOut] + ) + address = self.web3.toChecksumAddress(self.web3.eth.default_account) + joinPoolRequestTuple = ( + checksumTokens, + maxAmountsIn, + userDataEncoded.hex(), + joinDescription["fromInternalBalance"], + ) + return (poolId, address, joinPoolRequestTuple) + + def balFormatJoinPoolTokenInForExactBptOut(self, joinDescription): + (checksumTokens, maxAmountsIn) = self.balGetTokensAndAmounts( + copy.deepcopy(joinDescription) + ) + + index = -1 + counter = -1 + for amt in maxAmountsIn: + counter += 1 + if amt > 0: + if index == -1: + index = counter + else: + self.ERROR( + "Multiple tokens have amounts for a single token join! Only one token can have a non-zero amount!" + ) + return False + if index == -1: + self.ERROR( + "No tokens have amounts. You must have one token with a non-zero amount!" + ) + return False + + poolId = joinDescription["poolId"] + poolAddress = self.balPooldIdToAddress(poolId) + bptAmountOut = self.balConvertTokensToWei( + [poolAddress], [joinDescription["bptAmountOut"]] + )[0] + + joinKindEnum = self.balGetJoinKindEnum(poolId, joinDescription["joinKind"]) + userDataEncoded = eth_abi.encode_abi( + ["uint256", "uint256", "uint256"], [int(joinKindEnum), bptAmountOut, index] + ) + + address = self.web3.toChecksumAddress(self.web3.eth.default_account) + joinPoolRequestTuple = ( + checksumTokens, + maxAmountsIn, + userDataEncoded.hex(), + joinDescription["fromInternalBalance"], + ) + + return (poolId, address, joinPoolRequestTuple) + + def balJoinPool( + self, + joinDescription, + query=False, + gasFactor=1.05, + gasPriceSpeed="average", + nonceOverride=-1, + gasEstimateOverride=-1, + gasPriceGweiOverride=-1, + ): + joinKind = joinDescription["joinKind"] + poolId = None + address = None + joinPoolRequestTuple = None + + if joinKind == "INIT": + (poolId, address, joinPoolRequestTuple) = self.balFormatJoinPoolInit( + joinDescription + ) + elif joinKind == "EXACT_TOKENS_IN_FOR_BPT_OUT": + ( + poolId, + address, + joinPoolRequestTuple, + ) = self.balFormatJoinPoolExactTokensInForBptOut(joinDescription) + elif joinKind == "TOKEN_IN_FOR_EXACT_BPT_OUT": + tempJoinDescription = copy.deepcopy(joinDescription) + if query: + for token in tempJoinDescription["tokens"].keys(): + if tempJoinDescription["tokens"][token]["amount"] == 0.0: + tempJoinDescription["tokens"][token]["amount"] = self.INFINITE + ( + poolId, + address, + joinPoolRequestTuple, + ) = self.balFormatJoinPoolTokenInForExactBptOut(tempJoinDescription) + elif joinKind == "ALL_TOKENS_IN_FOR_EXACT_BPT_OUT": + tempJoinDescription = copy.deepcopy(joinDescription) + if query: + for token in tempJoinDescription["tokens"].keys(): + tempJoinDescription["tokens"][token]["amount"] = self.INFINITE + ( + poolId, + address, + joinPoolRequestTuple, + ) = self.balFormatJoinPoolAllTokensInForExactBptOut(tempJoinDescription) + print((poolId, address, joinPoolRequestTuple)) + + if query: + (bptOut, amountsIn) = self.balDoQueryJoinPool( + poolId, address, joinPoolRequestTuple + ) + bptAddress = self.balPooldIdToAddress(poolId) + outputData = {} + outputData["bptOut"] = { + "token": bptAddress, + "decimals": self.erc20GetDecimals(bptAddress), + "amount": bptOut, + } + outputData["amountsIn"] = {} + for token, amount in zip(joinPoolRequestTuple[0], amountsIn): + outputData["amountsIn"][token] = { + "token": token, + "decimals": self.erc20GetDecimals(token), + "amount": amount, + } + return outputData + else: + txHash = self.balDoJoinPool( + poolId, + address, + joinPoolRequestTuple, + gasFactor=gasFactor, + gasPriceSpeed=gasPriceSpeed, + nonceOverride=nonceOverride, + gasEstimateOverride=gasEstimateOverride, + gasPriceGweiOverride=gasPriceGweiOverride, + ) + return txHash + + def balRegisterPoolWithVault( + self, + poolDescription, + poolId, + gasFactor=1.05, + gasPriceSpeed="average", + nonceOverride=-1, + gasEstimateOverride=-1, + gasPriceGweiOverride=-1, + ): + self.WARN( + '"balRegisterPoolWithVault" is deprecated. Please use "balJoinPoolInit".' + ) + self.balJoinPoolInit( + poolDescription, + poolId, + gasFactor, + gasPriceSpeed, + nonceOverride, + gasEstimateOverride, + gasPriceGweiOverride, + ) + + def balJoinPoolInit( + self, + poolDescription, + poolId, + gasFactor=1.05, + gasPriceSpeed="average", + nonceOverride=-1, + gasEstimateOverride=-1, + gasPriceGweiOverride=-1, + ): + + if poolDescription["poolType"] in ["AaveLinearPool"]: + slippageTolerancePercent = 1 + txHash = self.balLinearPoolInitJoin( + poolDescription, + poolId, + slippageTolerancePercent=slippageTolerancePercent, + gasFactor=gasFactor, + gasPriceSpeed=gasPriceSpeed, + nonceOverride=nonceOverride, + gasEstimateOverride=gasEstimateOverride, + gasPriceGweiOverride=gasPriceGweiOverride, + ) + return txHash + + # StablePhantomPools need their own BPT as one of the provided tokens with a limit of MAX_UINT_112 + if poolDescription["poolType"] in [ + "StablePhantomPool", + "ComposableStablePool", + "ManagedPool", + ]: + initialBalancesNoBpt = [ + poolDescription["tokens"][token]["initialBalance"] + for token in poolDescription["tokens"].keys() + ] + phantomBptAddress = self.balPooldIdToAddress(poolId) + poolDescription["tokens"][phantomBptAddress] = { + "initialBalance": self.MAX_UINT_112 + } + + poolDescription["joinKind"] = "INIT" + return self.balJoinPool( + poolDescription, + False, + gasFactor, + gasPriceSpeed, + nonceOverride, + gasEstimateOverride, + gasPriceGweiOverride, + ) + + def balLinearPoolInitJoin( + self, + poolDescription, + poolId, + slippageTolerancePercent=1, + gasFactor=1.05, + gasPriceSpeed="average", + nonceOverride=-1, + gasEstimateOverride=-1, + gasPriceGweiOverride=-1, + ): + + phantomBptAddress = self.balPooldIdToAddress(poolId) + + batchSwap = {} + batchSwap["kind"] = 0 + batchSwap["assets"] = list(poolDescription["tokens"].keys()) + batchSwap["swaps"] = [] + + numTokens = len(batchSwap["assets"]) + for i in range(numTokens): + token = batchSwap["assets"][i] + swap = {} + swap["poolId"] = "0x" + poolId + swap["assetInIndex"] = i + swap["assetOutIndex"] = numTokens + swap["amount"] = poolDescription["tokens"][token]["initialBalance"] + if float(swap["amount"]) > 0.0: + batchSwap["swaps"].append(swap) + + # add the phantomBpt to the assets/limits list now that we've crafted the swap steps + batchSwap["assets"].append(phantomBptAddress) + batchSwap["limits"] = [0] * len(batchSwap["assets"]) + # for now + + batchSwap["funds"] = {} + batchSwap["funds"]["sender"] = self.address + batchSwap["funds"]["recipient"] = self.address + batchSwap["funds"]["fromInternalBalance"] = False + batchSwap["funds"]["toInternalBalance"] = False + batchSwap["deadline"] = "999999999999999999" + + estimates = self.balQueryBatchSwap(batchSwap) + + checksumTokens = [self.web3.toChecksumAddress(t) for t in batchSwap["assets"]] + for i in range(len(batchSwap["assets"])): + asset = checksumTokens[i] + slippageToleranceFactor = slippageTolerancePercent / 100.0 + if estimates[asset] < 0: + slippageToleranceFactor *= -1.0 + batchSwap["limits"][i] = estimates[asset] * (1.0 + slippageToleranceFactor) + + txHash = self.balDoBatchSwap( + batchSwap, + isAsync=False, + gasFactor=gasFactor, + gasPriceSpeed=gasPriceSpeed, + nonceOverride=nonceOverride, + gasEstimateOverride=gasEstimateOverride, + gasPriceGweiOverride=gasPriceGweiOverride, + ) + return txHash + + def balVaultWeth(self): + vault = self.balLoadContract("Vault") + wethAddress = vault.functions.WETH().call() + return wethAddress + + def balBalancerHelpersGetVault(self): + bh = self.balLoadContract("BalancerHelpers") + vaultAddress = bh.functions.vault().call() + return vaultAddress + + def balVaultGetPoolTokens(self, poolId): + vault = self.balLoadContract("Vault") + output = vault.functions.getPoolTokens(poolId).call() + tokens = output[0] + balances = output[1] + lastChangeBlock = output[2] + return (tokens, balances, lastChangeBlock) + + def balVaultGetInternalBalance(self, tokens, address=None): + if address is None: + address = self.web3.eth.default_account + + vault = self.balLoadContract("Vault") + (sortedTokens, checksumTokens) = self.balSortTokens(tokens) + balances = vault.functions.getInternalBalance(address, checksumTokens).call() + numElements = len(sortedTokens) + internalBalances = {} + for i in range(numElements): + token = checksumTokens[i] + decimals = self.erc20GetDecimals(token) + internalBalances[token] = Decimal(balances[i]) * Decimal(10 ** (-decimals)) + return internalBalances + + def balVaultDoManageUserBalance( + self, + kind, + token, + amount, + sender, + recipient, + isAsync=False, + gasFactor=1.05, + gasPriceSpeed="average", + nonceOverride=-1, + gasEstimateOverride=-1, + gasPriceGweiOverride=-1, + ): + if self.verbose: + print("Managing User Balance") + print("\tKind:\t\t", self.inverseUserBalanceOpKind[kind]) + print("\tToken:\t\t", str(token)) + print("\tAmount:\t\t", str(amount)) + print("\tSender:\t\t", str(sender)) + print("\tRecipient:\t", str(recipient)) + manageUserBalanceFn = self.balVaultBuildManageUserBalanceFn( + kind, token, amount, sender, recipient + ) + + print() + print("Building ManageUserBalance") + tx = self.buildTx( + manageUserBalanceFn, + gasFactor, + gasPriceSpeed, + nonceOverride, + gasEstimateOverride, + gasPriceGweiOverride, + ) + txHash = self.sendTx(tx, isAsync) + return txHash + + def balVaultBuildManageUserBalanceFn(self, kind, token, amount, sender, recipient): + kind = kind + asset = self.web3.toChecksumAddress(token) + amount = self.balConvertTokensToWei([token], [amount])[0] + sender = self.web3.toChecksumAddress(sender) + recipient = self.web3.toChecksumAddress(recipient) + inputTupleList = [(kind, asset, amount, sender, recipient)] + + vault = self.balLoadContract("Vault") + manageUserBalanceFn = vault.functions.manageUserBalance(inputTupleList) + return manageUserBalanceFn + + @cache + def balLoadContract(self, contractName): + contract = self.web3.eth.contract( + address=self.deploymentAddresses[contractName], abi=self.abis[contractName] + ) + return contract + + @cache + def balLoadArbitraryContract(self, address, abi): + contract = self.web3.eth.contract( + address=address, abi=self.mc.stringToList(abi) + ) + return contract + + @cache + def balPoolGetAbi(self, poolType): + + if not "Pool" in poolType: + poolType = poolType + "Pool" + + deploymentFolder = self.contractDirectories[poolType + "Factory"]["directory"] + abiPath = os.path.join( + "deployments/", deploymentFolder, "abi", poolType + ".json" + ) + f = pkgutil.get_data(__name__, abiPath).decode() + poolAbi = json.loads(f) + return poolAbi + + @cache + def balPooldIdToAddress(self, poolId): + if not "0x" in poolId: + poolId = "0x" + poolId + poolAddress = self.web3.toChecksumAddress(poolId[:42]) + return poolAddress + + def balGetPoolCreationData(self, poolId, verbose=False, inputHash=None): + address = self.balPooldIdToAddress(poolId) + if inputHash is None: + txns = self.getTransactionsByAddress( + address, internal=True, verbose=verbose + ) + else: + txns = self.getTransactionByHash(inputHash, verbose=verbose) + + poolTypeByContract = {} + for poolType in self.deploymentAddresses.keys(): + deploymentAddress = self.deploymentAddresses[poolType].lower() + poolTypeByContract[deploymentAddress] = poolType + + poolFactoryType = None + if inputHash is None: + for txn in txns: + if txn["from"].lower() in poolTypeByContract.keys(): + poolFactoryType = poolTypeByContract[txn["from"].lower()] + txHash = txn["hash"] + stamp = txn["timeStamp"] + break + else: + txn = txns["result"] + if verbose: + print() + print(txn) + poolFactoryType = poolTypeByContract[txn["to"].lower()] + txHash = txn["hash"] + stamp = self.web3.eth.get_block(int(txn["blockNumber"], 16))["timestamp"] + + return (address, poolFactoryType, txHash, stamp) + + def balGetPoolFactoryCreationTime(self, address): + txns = self.getTransactionsByAddress(address) + return txns[0]["timeStamp"] + + def getInputData(self, txHash): + transaction = self.web3.eth.get_transaction(txHash) + return transaction.input + + def balGeneratePoolCreationArguments( + self, poolId, verbose=False, creationHash=None + ): + if self.network in ["arbitrum"]: + self.ERROR( + "Automated pool verification doesn't work on " + + self.network + + " yet. Please try the method outlined in the docs using Tenderly." + ) + return False + + # query etherscan for internal transactions to find pool factory, pool creation time, and creation hash + (address, poolFactoryType, txHash, stampPool) = self.balGetPoolCreationData( + poolId, verbose=verbose, inputHash=creationHash + ) + + # get the input data used to generate the pool + inputData = self.getInputData(txHash) + + # decode those ^ inputs according to the relevant pool factory ABI + poolFactoryContract = self.balLoadContract(poolFactoryType) + decodedPoolData = poolFactoryContract.decode_function_input(inputData)[1] + + # get pool factory creation time to calculate pauseWindowDuration + stampFactory = self.balGetPoolFactoryCreationTime(poolFactoryContract.address) + + # make sure arguments exist/are proper types to be encoded + if "weights" in decodedPoolData.keys(): + for i in range(len(decodedPoolData["weights"])): + decodedPoolData["weights"][i] = int(decodedPoolData["weights"][i]) + if "priceRateCacheDuration" in decodedPoolData.keys(): + for i in range(len(decodedPoolData["priceRateCacheDuration"])): + decodedPoolData["priceRateCacheDuration"][i] = int( + decodedPoolData["priceRateCacheDuration"][i] + ) + if ( + poolFactoryType == "InvestmentPoolFactory" + and not "assetManagers" in decodedPoolData.keys() + ): + decodedPoolData["assetManagers"] = [] + for i in range(len(decodedPoolData["weights"])): + decodedPoolData["assetManagers"].append(self.ZERO_ADDRESS) + + # times for pause/buffer + daysToSec = 24 * 60 * 60 + # hr * min * sec + pauseDays = 90 + bufferPeriodDays = 30 + + # calculate proper durations + pauseWindowDurationSec = max( + (pauseDays * daysToSec) - (int(stampPool) - int(stampFactory)), 0 + ) + bufferPeriodDurationSec = bufferPeriodDays * daysToSec + if pauseWindowDurationSec == 0: + bufferPeriodDurationSec = 0 + + poolType = poolFactoryType.replace("Factory", "") + poolAbi = self.balPoolGetAbi(poolType) + + structInConstructor = False + if poolType == "WeightedPool": + zero_ams = [self.ZERO_ADDRESS] * len(decodedPoolData["tokens"]) + args = [ + ( + decodedPoolData["name"], + decodedPoolData["symbol"], + decodedPoolData["tokens"], + decodedPoolData["normalizedWeights"], + decodedPoolData["rateProviders"], + zero_ams, + int(decodedPoolData["swapFeePercentage"]), + ), + self.deploymentAddresses["Vault"], + self.deploymentAddresses["ProtocolFeePercentagesProvider"], + int(pauseWindowDurationSec), + int(bufferPeriodDurationSec), + decodedPoolData["owner"], + ] + elif poolType == "WeightedPool2Tokens": + args = [ + self.deploymentAddresses["Vault"], + decodedPoolData["name"], + decodedPoolData["symbol"], + decodedPoolData["tokens"][0], + decodedPoolData["tokens"][1], + int(decodedPoolData["weights"][0]), + int(decodedPoolData["weights"][1]), + int(decodedPoolData["swapFeePercentage"]), + int(pauseWindowDurationSec), + int(bufferPeriodDurationSec), + decodedPoolData["oracleEnabled"], + decodedPoolData["owner"], + ] + structInConstructor = True + elif poolType == "StablePool": + args = [ + self.deploymentAddresses["Vault"], + decodedPoolData["name"], + decodedPoolData["symbol"], + decodedPoolData["tokens"], + int(decodedPoolData["amplificationParameter"]), + int(decodedPoolData["swapFeePercentage"]), + int(pauseWindowDurationSec), + int(bufferPeriodDurationSec), + decodedPoolData["owner"], + ] + elif poolType == "MetaStablePool": + args = [ + self.deploymentAddresses["Vault"], + decodedPoolData["name"], + decodedPoolData["symbol"], + decodedPoolData["tokens"], + decodedPoolData["rateProviders"], + decodedPoolData["priceRateCacheDuration"], + int(decodedPoolData["amplificationParameter"]), + int(decodedPoolData["swapFeePercentage"]), + int(pauseWindowDurationSec), + int(bufferPeriodDurationSec), + decodedPoolData["oracleEnabled"], + decodedPoolData["owner"], + ] + structInConstructor = True + elif poolType == "LiquidityBootstrappingPool": + args = [ + self.deploymentAddresses["Vault"], + decodedPoolData["name"], + decodedPoolData["symbol"], + decodedPoolData["tokens"], + decodedPoolData["weights"], + int(decodedPoolData["swapFeePercentage"]), + int(pauseWindowDurationSec), + int(bufferPeriodDurationSec), + decodedPoolData["owner"], + decodedPoolData["swapEnabledOnStart"], + ] + elif poolType == "InvestmentPool": + args = [ + self.deploymentAddresses["Vault"], + decodedPoolData["name"], + decodedPoolData["symbol"], + decodedPoolData["tokens"], + decodedPoolData["weights"], + decodedPoolData["assetManagers"], + int(decodedPoolData["swapFeePercentage"]), + int(pauseWindowDurationSec), + int(bufferPeriodDurationSec), + decodedPoolData["owner"], + decodedPoolData["swapEnabledOnStart"], + int(decodedPoolData["managementSwapFeePercentage"]), + ] + structInConstructor = True + elif poolType == "StablePhantomPool": + args = [ + self.deploymentAddresses["Vault"], + decodedPoolData["name"], + decodedPoolData["symbol"], + decodedPoolData["tokens"], + decodedPoolData["rateProviders"], + decodedPoolData["tokenRateCacheDurations"], + int(decodedPoolData["amplificationParameter"]), + int(decodedPoolData["swapFeePercentage"]), + int(pauseWindowDurationSec), + int(bufferPeriodDurationSec), + decodedPoolData["owner"], + ] + structInConstructor = True + elif poolType == "AaveLinearPool": + args = [ + self.deploymentAddresses["Vault"], + decodedPoolData["name"], + decodedPoolData["symbol"], + decodedPoolData["mainToken"], + decodedPoolData["wrappedToken"], + int(decodedPoolData["upperTarget"]), + int(decodedPoolData["swapFeePercentage"]), + int(pauseWindowDurationSec), + int(bufferPeriodDurationSec), + decodedPoolData["owner"], + ] + elif poolType == "ERC4626LinearPool": + args = [ + self.deploymentAddresses["Vault"], + decodedPoolData["name"], + decodedPoolData["symbol"], + decodedPoolData["mainToken"], + decodedPoolData["wrappedToken"], + int(decodedPoolData["upperTarget"]), + int(decodedPoolData["swapFeePercentage"]), + int(pauseWindowDurationSec), + int(bufferPeriodDurationSec), + decodedPoolData["owner"], + ] + else: + self.ERROR("PoolType " + poolType + " not found!") + return False + + # encode constructor data + poolContract = self.balLoadArbitraryContract( + address, self.mc.listToString(poolAbi) + ) + if structInConstructor: + args = (tuple(args),) + data = poolContract._encode_constructor_data(args=args) + encodedData = data[2:] + # cut off the 0x + + command = "yarn hardhat verify-contract --id {} --name {} --address {} --network {} --key {} --args {}" + output = command.format( + self.contractDirectories[poolFactoryType]["directory"], + poolType, + address, + self.network, + self.etherscanApiKey, + encodedData, + ) + return output + + def balStablePoolGetAmplificationParameter(self, poolId): + poolAddress = self.web3.toChecksumAddress(poolId[:42]) + pool = self.web3.eth.contract( + address=poolAddress, abi=self.balPoolGetAbi("StablePool") + ) + ( + value, + isUpdating, + precision, + ) = pool.functions.getAmplificationParameter().call() + return (value, isUpdating, precision) + + def balStablePoolStartAmplificationParameterUpdate( + self, + poolId, + rawEndValue, + endTime, + isAsync=False, + gasFactor=1.05, + gasPriceSpeed="average", + nonceOverride=-1, + gasEstimateOverride=-1, + gasPriceGweiOverride=-1, + ): + poolAddress = self.web3.toChecksumAddress(poolId[:42]) + pool = self.web3.eth.contract( + address=poolAddress, abi=self.balPoolGetAbi("StablePool") + ) + + owner = pool.functions.getOwner().call() + if not self.address == owner: + self.ERROR("You are not the pool owner; this transaction will fail.") + return False + + fn = pool.functions.startAmplificationParameterUpdate(rawEndValue, endTime) + tx = self.buildTx( + fn, + gasFactor, + gasPriceSpeed, + nonceOverride, + gasEstimateOverride, + gasPriceGweiOverride, + ) + txHash = self.sendTx(tx, isAsync) + return txHash + + # https://dev.balancer.fi/references/contracts/apis/pools/weightedpool2tokens#gettimeweightedaverage + def balOraclePoolGetTimeWeightedAverage(self, poolId, queries): + poolAddress = self.web3.toChecksumAddress(poolId[:42]) + pool = self.web3.eth.contract( + address=poolAddress, abi=self.balPoolGetAbi("WeightedPool2Tokens") + ) + results = pool.functions.getTimeWeightedAverage(queries).call() + return results + + def balSwapIsFlashSwap(self, swapDescription): + for amount in swapDescription["limits"]: + if not float(amount) == 0.0: + return False + return True + + def balReorderTokenDicts(self, tokens): + originalIdxToSortedIdx = {} + sortedIdxToOriginalIdx = {} + tokenAddressToIdx = {} + for i in range(len(tokens)): + tokenAddressToIdx[tokens[i]] = i + sortedTokens = tokens + sortedTokens.sort() + for i in range(len(sortedTokens)): + originalIdxToSortedIdx[tokenAddressToIdx[sortedTokens[i]]] = i + sortedIdxToOriginalIdx[i] = tokenAddressToIdx[sortedTokens[i]] + return (sortedTokens, originalIdxToSortedIdx, sortedIdxToOriginalIdx) + + def balSwapGetUserData(self, poolType): + userDataNull = eth_abi.encode_abi(["uint256"], [0]) + userData = userDataNull + # for weightedPools, user data is just null, but in the future there may be userData to pass to pools for swaps + # if poolType == "someFuturePool": + # userData = "something else"; + return userData + + def balDoSwap( + self, + swapDescription, + isAsync=False, + gasFactor=1.05, + gasPriceSpeed="average", + nonceOverride=-1, + gasEstimateOverride=-1, + gasPriceGweiOverride=-1, + ): + swapFn = self.balCreateFnSwap(swapDescription) + tx = self.buildTx( + swapFn, + gasFactor, + gasPriceSpeed, + nonceOverride, + gasEstimateOverride, + gasPriceGweiOverride, + ) + txHash = self.sendTx(tx, isAsync) + return txHash + + def balDoBatchSwap( + self, + swapDescription, + isAsync=False, + gasFactor=1.05, + gasPriceSpeed="average", + nonceOverride=-1, + gasEstimateOverride=-1, + gasPriceGweiOverride=-1, + ): + batchSwapFn = self.balCreateFnBatchSwap(swapDescription) + tx = self.buildTx( + batchSwapFn, + gasFactor, + gasPriceSpeed, + nonceOverride, + gasEstimateOverride, + gasPriceGweiOverride, + ) + txHash = self.sendTx(tx, isAsync) + return txHash + + def balCreateFnSwap(self, swapDescription): + kind = int(swapDescription["kind"]) + limitedToken = None + amountToken = None + if kind == 0: # GIVEN_IN + amountToken = swapDescription["assetIn"] + limitedToken = swapDescription["assetOut"] + elif kind == 1: # GIVEN_OUT + amountToken = swapDescription["assetOut"] + limitedToken = swapDescription["assetIn"] + + amountWei = int( + Decimal(swapDescription["amount"]) + * 10 ** Decimal(self.erc20GetDecimals(amountToken)) + ) + limitWei = int( + Decimal(swapDescription["limit"]) + * 10 ** Decimal(self.erc20GetDecimals(limitedToken)) + ) + + swapStruct = ( + swapDescription["poolId"], + kind, + self.web3.toChecksumAddress(swapDescription["assetIn"]), + self.web3.toChecksumAddress(swapDescription["assetOut"]), + amountWei, + self.balSwapGetUserData(None), + ) + fundStruct = ( + self.web3.toChecksumAddress(swapDescription["fund"]["sender"]), + swapDescription["fund"]["fromInternalBalance"], + self.web3.toChecksumAddress(swapDescription["fund"]["recipient"]), + swapDescription["fund"]["toInternalBalance"], + ) + vault = self.balLoadContract("Vault") + singleSwapFunction = vault.functions.swap( + swapStruct, fundStruct, limitWei, int(swapDescription["deadline"]) + ) + return singleSwapFunction + + def balFormatBatchSwapData(self, swapDescription): + ( + sortedTokens, + originalIdxToSortedIdx, + sortedIdxToOriginalIdx, + ) = self.balReorderTokenDicts(swapDescription["assets"]) + numTokens = len(sortedTokens) + + # reorder the limits to refer to properly sorted tokens + reorderedLimits = [] + for i in range(numTokens): + currLimitStandard = float( + swapDescription["limits"][sortedIdxToOriginalIdx[i]] + ) + decimals = self.erc20GetDecimals(sortedTokens[i]) + currLimitRaw = int(Decimal(currLimitStandard) * Decimal(10 ** (decimals))) + reorderedLimits.append(currLimitRaw) + + kind = int(swapDescription["kind"]) + assets = [self.web3.toChecksumAddress(token) for token in sortedTokens] + + swapsTuples = [] + for swap in swapDescription["swaps"]: + idxSortedIn = originalIdxToSortedIdx[int(swap["assetInIndex"])] + idxSortedOut = originalIdxToSortedIdx[int(swap["assetOutIndex"])] + + idxTokenAmount = idxSortedIn + if kind == 1: + idxTokenAmount = idxSortedOut + + decimals = self.erc20GetDecimals(sortedTokens[idxTokenAmount]) + amount = int(Decimal(swap["amount"]) * Decimal(10 ** (decimals))) + + swapsTuple = ( + swap["poolId"], + idxSortedIn, + idxSortedOut, + amount, + self.balSwapGetUserData(None), + ) + swapsTuples.append(swapsTuple) + + funds = ( + self.web3.toChecksumAddress(swapDescription["funds"]["sender"]), + swapDescription["funds"]["fromInternalBalance"], + self.web3.toChecksumAddress(swapDescription["funds"]["recipient"]), + swapDescription["funds"]["toInternalBalance"], + ) + intReorderedLimits = [int(element) for element in reorderedLimits] + deadline = int(swapDescription["deadline"]) + return (kind, swapsTuples, assets, funds, intReorderedLimits, deadline) + + def balCreateFnBatchSwap(self, swapDescription): + ( + kind, + swapsTuples, + assets, + funds, + intReorderedLimits, + deadline, + ) = self.balFormatBatchSwapData(swapDescription) + vault = self.balLoadContract("Vault") + batchSwapFunction = vault.functions.batchSwap( + kind, swapsTuples, assets, funds, intReorderedLimits, deadline + ) + return batchSwapFunction + + def balQueryBatchSwaps(self, originalSwapsDescription): + swapsDescription = copy.deepcopy(originalSwapsDescription) + vault = self.balLoadContract("Vault") + for swapDescription in swapsDescription: + + # do deep copy to avoid modifying the swapDescription in place, breaking index remappings + deepCopySwapDescription = copy.deepcopy(swapDescription) + ( + kind, + swapsTuples, + assets, + funds, + intReorderedLimits, + deadline, + ) = self.balFormatBatchSwapData(deepCopySwapDescription) + args = [kind, swapsTuples, assets, funds] + self.mc.addCall(vault.address, vault.abi, "queryBatchSwap", args=args) + data = self.mc.execute() + + outputs = [] + for swapDescription, outputData in zip(swapsDescription, data): + amounts = list(outputData[0]) + output = {} + for asset, amount in zip(assets, amounts): + decimals = self.erc20GetDecimals(asset) + output[asset] = amount * 10 ** (-decimals) + outputs.append(output) + return outputs + + def balQueryBatchSwap(self, originalSwapDescription): + swapDescription = copy.deepcopy(originalSwapDescription) + ( + kind, + swapsTuples, + assets, + funds, + intReorderedLimits, + deadline, + ) = self.balFormatBatchSwapData(swapDescription) + vault = self.balLoadContract("Vault") + amounts = vault.functions.queryBatchSwap( + kind, swapsTuples, assets, funds + ).call() + + output = {} + for asset, amount in zip(assets, amounts): + decimals = self.erc20GetDecimals(asset) + output[asset] = amount * 10 ** (-decimals) + return output + + def balGetLinkToFrontend(self, poolId): + if "balFrontend" in self.networkParams[self.network].keys(): + return ( + "https://" + + self.networkParams[self.network]["balFrontend"] + + "pool/0x" + + poolId + ) + else: + return "" + + def multiCallErc20BatchDecimals(self, tokens): + self.mc.reset() + payload = [] + for token in tokens: + currTokenContract = self.erc20GetContract(token) + self.mc.addCall( + currTokenContract.address, currTokenContract.abi, "decimals" + ) + + # make the actual call to MultiCall + outputData = self.mc.execute() + tokensToDecimals = {} + + for token, odBytes in zip(tokens, outputData): + decimals = odBytes[0] + tokensToDecimals[token] = decimals + self.decimals[token] = decimals + return tokensToDecimals + + def getOnchainData(self, pools): + # reset multicaller + self.mc.reset() + + # load the vault contract + vault = self.balLoadContract("Vault") + target = vault.address + + poolAbis = {} + for poolType in pools.keys(): + poolAbis[poolType] = self.balPoolGetAbi(poolType) + + payload = [] + pidAndFns = [] + outputAbis = {} + + poolToType = {} + for poolType in pools.keys(): + poolIds = pools[poolType] + poolAbi = poolAbis[poolType] + + # construct all the calls in format (VaultAddress, encodedCallData) + for poolId in poolIds: + poolToType[poolId] = poolType + + poolAddress = self.balPooldIdToAddress(poolId) + currPool = self.balLoadArbitraryContract( + poolAddress, self.mc.listToString(poolAbi) + ) + + # === all pools have tokens and swap fee, pausedState === + self.mc.addCall( + vault.address, vault.abi, "getPoolTokens", args=[poolId] + ) + self.mc.addCall(currPool.address, currPool.abi, "getSwapFeePercentage") + self.mc.addCall(currPool.address, currPool.abi, "getPausedState") + pidAndFns.append((poolId, "getPoolTokens")) + pidAndFns.append((poolId, "getSwapFeePercentage")) + pidAndFns.append((poolId, "getPausedState")) + + # === using weighted math === + if poolType in ["Weighted", "LiquidityBootstrapping", "Investment"]: + self.mc.addCall( + currPool.address, currPool.abi, "getNormalizedWeights" + ) + pidAndFns.append((poolId, "getNormalizedWeights")) + + # === using stable math === + if poolType in ["Stable", "MetaStable"]: + self.mc.addCall( + currPool.address, currPool.abi, "getAmplificationParameter" + ) + pidAndFns.append((poolId, "getAmplificationParameter")) + + # === have pausable swaps by pool owner === + if poolType in ["LiquidityBootstrapping", "Investment"]: + self.mc.addCall(currPool.address, currPool.abi, "getSwapEnabled") + pidAndFns.append((poolId, "getSwapEnabled")) + + data = self.mc.execute() + + chainDataOut = {} + chainDataBookkeeping = {} + + for decodedOutputData, pidAndFn in zip(data, pidAndFns): + poolId = pidAndFn[0] + decoder = pidAndFn[1] + + if not poolId in chainDataOut.keys(): + chainDataOut[poolId] = {"poolType": poolToType[poolId]} + chainDataBookkeeping[poolId] = {} + + if decoder == "getPoolTokens": + addresses = list(decodedOutputData[0]) + balances = list(decodedOutputData[1]) + lastChangeBlock = decodedOutputData[2] + + chainDataOut[poolId]["lastChangeBlock"] = lastChangeBlock + chainDataBookkeeping[poolId]["orderedAddresses"] = addresses + + chainDataOut[poolId]["tokens"] = {} + for address, balance in zip(addresses, balances): + chainDataOut[poolId]["tokens"][address] = { + "rawBalance": str(balance) + } + + elif decoder == "getNormalizedWeights": + normalizedWeights = list(decodedOutputData[0]) + addresses = chainDataBookkeeping[poolId]["orderedAddresses"] + for address, normalizedWeight in zip(addresses, normalizedWeights): + weight = Decimal(str(normalizedWeight)) * Decimal(str(1e-18)) + chainDataOut[poolId]["tokens"][address]["weight"] = str(weight) + + elif decoder == "getSwapFeePercentage": + swapFee = Decimal(decodedOutputData[0]) * Decimal(str(1e-18)) + chainDataOut[poolId]["swapFee"] = str(swapFee) + + elif decoder == "getAmplificationParameter": + rawAmp = Decimal(decodedOutputData[0]) + scaling = Decimal(decodedOutputData[2]) + chainDataOut[poolId]["amp"] = str(rawAmp / scaling) + + elif decoder == "getSwapEnabled": + chainDataOut[poolId]["swapEnabled"] = decodedOutputData[0] + + elif decoder == "getPausedState": + chainDataOut[poolId]["pausedState"] = decodedOutputData[0] + + # find tokens for which decimals have not been cached + tokens = set() + for pool in chainDataOut.keys(): + for token in chainDataOut[pool]["tokens"].keys(): + tokens.add(token) + haveDecimalsFor = set(self.decimals.keys()) + + # set subtraction (A - B) gives "What is in A that isn't in B?" + needDecimalsFor = tokens - haveDecimalsFor + + # if there are any, query them onchain using multicall + if len(needDecimalsFor) > 0: + print( + "New tokens found. Caching decimals for", + len(needDecimalsFor), + "tokens...", + ) + self.multiCallErc20BatchDecimals(needDecimalsFor) + + # update rawBalances to have decimal adjusted values + for pool in chainDataOut.keys(): + for token in chainDataOut[pool]["tokens"].keys(): + rawBalance = chainDataOut[pool]["tokens"][token]["rawBalance"] + decimals = self.erc20GetDecimals(token) + chainDataOut[pool]["tokens"][token]["balance"] = str( + Decimal(rawBalance) * Decimal(10 ** (-decimals)) + ) + + return chainDataOut + + def generateDeploymentsDocsTable(self): + outputString = "" + contracts = [] + addresses = [] + + network = self.network + blockExplorer = "https://" + self.networkParams[network]["blockExplorerUrl"] + outputString += '{% tab title="' + network.title() + '" %}\n' + + for contractType in self.deploymentAddresses: + contracts.append(contractType) + + address = self.deploymentAddresses[contractType] + etherscanPage = blockExplorer + "/address/" + address + addresses.append("[" + address + "](" + etherscanPage + ")") + + longestContractStringLength = getLongestStringLength(contracts) + longestAddressStringLength = getLongestStringLength(addresses) + + contractDashLine = "".join(["-"] * longestContractStringLength) + addressDashLine = "".join(["-"] * longestAddressStringLength) + + contracts = ["Contracts", contractDashLine] + contracts + addresses = ["Addresses", addressDashLine] + addresses + + for (c, a) in zip(contracts, addresses): + cPadded = padWithSpaces(c, longestContractStringLength) + aPadded = padWithSpaces(a, longestAddressStringLength) + line = "| " + cPadded + " | " + aPadded + " |\n" + outputString += line + outputString += "\n{% endtab %}" + return outputString diff --git a/balpy/enums/stablePoolJoinExitKind.py b/balpy/enums/stablePoolJoinExitKind.py index 4cee8b1..706f7df 100644 --- a/balpy/enums/stablePoolJoinExitKind.py +++ b/balpy/enums/stablePoolJoinExitKind.py @@ -1,15 +1,18 @@ from enum import IntEnum + class StablePoolJoinKind(IntEnum): - INIT = 0 - EXACT_TOKENS_IN_FOR_BPT_OUT = 1 - TOKEN_IN_FOR_EXACT_BPT_OUT = 2 + INIT = 0 + EXACT_TOKENS_IN_FOR_BPT_OUT = 1 + TOKEN_IN_FOR_EXACT_BPT_OUT = 2 + class StablePhantomPoolJoinKind(IntEnum): - INIT = 0 - COLLECT_PROTOCOL_FEES = 1 + INIT = 0 + COLLECT_PROTOCOL_FEES = 1 + class StablePoolExitKind(IntEnum): - EXACT_BPT_IN_FOR_ONE_TOKEN_OUT = 0 - EXACT_BPT_IN_FOR_TOKENS_OUT = 1 - BPT_IN_FOR_EXACT_TOKENS_OUT = 2 \ No newline at end of file + EXACT_BPT_IN_FOR_ONE_TOKEN_OUT = 0 + EXACT_BPT_IN_FOR_TOKENS_OUT = 1 + BPT_IN_FOR_EXACT_TOKENS_OUT = 2 diff --git a/balpy/enums/weightedPoolJoinExitKind.py b/balpy/enums/weightedPoolJoinExitKind.py index a65397d..6fdb1da 100644 --- a/balpy/enums/weightedPoolJoinExitKind.py +++ b/balpy/enums/weightedPoolJoinExitKind.py @@ -1,13 +1,15 @@ from enum import IntEnum + class WeightedPoolJoinKind(IntEnum): - INIT = 0 - EXACT_TOKENS_IN_FOR_BPT_OUT = 1 - TOKEN_IN_FOR_EXACT_BPT_OUT = 2 - ALL_TOKENS_IN_FOR_EXACT_BPT_OUT = 3 + INIT = 0 + EXACT_TOKENS_IN_FOR_BPT_OUT = 1 + TOKEN_IN_FOR_EXACT_BPT_OUT = 2 + ALL_TOKENS_IN_FOR_EXACT_BPT_OUT = 3 + class WeightedPoolExitKind(IntEnum): - EXACT_BPT_IN_FOR_ONE_TOKEN_OUT = 0 - EXACT_BPT_IN_FOR_TOKENS_OUT = 1 - BPT_IN_FOR_EXACT_TOKENS_OUT = 2 - MANAGEMENT_FEE_TOKENS_OUT = 3 \ No newline at end of file + EXACT_BPT_IN_FOR_ONE_TOKEN_OUT = 0 + EXACT_BPT_IN_FOR_TOKENS_OUT = 1 + BPT_IN_FOR_EXACT_TOKENS_OUT = 2 + MANAGEMENT_FEE_TOKENS_OUT = 3 diff --git a/balpy/graph/graph.py b/balpy/graph/graph.py index 6a3e2eb..3add8e1 100644 --- a/balpy/graph/graph.py +++ b/balpy/graph/graph.py @@ -11,10 +11,11 @@ # for customized endpoints import requests + class TheGraph(object): - client = None; - - """ + client = None + + """ A starting point for querying pool data from the Balancer Subgraph. At time of writing, this package does not cover all types of queries possible to the Balancer V2 Subgraph. It does, however, allow users @@ -25,68 +26,68 @@ class TheGraph(object): https://thegraph.com/legacy-explorer/subgraph/balancer-labs/balancer-v2 """ - def __init__(self, network="mainnet", customUrl=None, usingJsonEndpoint=False): - super(TheGraph, self).__init__() - self.network = network; - self.initBalV2Graph(customUrl=customUrl, usingJsonEndpoint=usingJsonEndpoint); - - def printJson(self, curr_dict): - print(json.dumps(curr_dict, indent=4)) - - def callCustomEndpoint(self, query): - - query = query.replace("\n"," "); - query = query.replace("\t",""); - queryDict = {"query":query}; - serializedData = json.dumps(queryDict); - headers = {"Content-Type":"application/json"}; - r = requests.post(self.graphUrl, data=serializedData, headers=headers); - response = r.json(); - return(response["data"]) - - def assertInit(self): - if self.client is None: - print() - print("[ERROR] Subgraph not initialized."); - print("Call \"initBalV2Graph(network)\" before calling this function.") - print() - return(None); - - def initBalV2Graph(self, customUrl, usingJsonEndpoint, verbose=False): - if not customUrl is None and usingJsonEndpoint: - self.client = "CUSTOM" - self.graphUrl = customUrl; - return(True); - - network_string = ""; - if not self.network == "mainnet": - network_string = "-" + self.network; - - if verbose: - print("Balancer V2 Subgraph initializing on:", self.network, "...") - - graphUrl = "https://api.thegraph.com/subgraphs/name/balancer-labs/balancer" + network_string + "-v2"; - if not customUrl is None and not usingJsonEndpoint: - graphUrl = customUrl; - - balancer_transport=RequestsHTTPTransport( - url=graphUrl, - verify=True, - retries=3 - ) - self.client = Client(transport=balancer_transport) - - if verbose: - print("Successfully initialized on network:", self.network); - - def getPoolTokens(self, pool_id, verbose=False): - - self.assertInit(); - - if verbose: - print("Querying tokens for pool with ID:", pool_id) - - pool_token_query = ''' + def __init__(self, network="mainnet", customUrl=None, usingJsonEndpoint=False): + super(TheGraph, self).__init__() + self.network = network + self.initBalV2Graph(customUrl=customUrl, usingJsonEndpoint=usingJsonEndpoint) + + def printJson(self, curr_dict): + print(json.dumps(curr_dict, indent=4)) + + def callCustomEndpoint(self, query): + + query = query.replace("\n", " ") + query = query.replace("\t", "") + queryDict = {"query": query} + serializedData = json.dumps(queryDict) + headers = {"Content-Type": "application/json"} + r = requests.post(self.graphUrl, data=serializedData, headers=headers) + response = r.json() + return response["data"] + + def assertInit(self): + if self.client is None: + print() + print("[ERROR] Subgraph not initialized.") + print('Call "initBalV2Graph(network)" before calling this function.') + print() + return None + + def initBalV2Graph(self, customUrl, usingJsonEndpoint, verbose=False): + if not customUrl is None and usingJsonEndpoint: + self.client = "CUSTOM" + self.graphUrl = customUrl + return True + + network_string = "" + if not self.network == "mainnet": + network_string = "-" + self.network + + if verbose: + print("Balancer V2 Subgraph initializing on:", self.network, "...") + + graphUrl = ( + "https://api.thegraph.com/subgraphs/name/balancer-labs/balancer" + + network_string + + "-v2" + ) + if not customUrl is None and not usingJsonEndpoint: + graphUrl = customUrl + + balancer_transport = RequestsHTTPTransport(url=graphUrl, verify=True, retries=3) + self.client = Client(transport=balancer_transport) + + if verbose: + print("Successfully initialized on network:", self.network) + + def getPoolTokens(self, pool_id, verbose=False): + + self.assertInit() + + if verbose: + print("Querying tokens for pool with ID:", pool_id) + + pool_token_query = """ query {{ poolTokens(first: 8, where: {{ poolId: "{pool_id}" }}) {{ id @@ -103,52 +104,52 @@ def getPoolTokens(self, pool_id, verbose=False): weight }} }} - '''; - formatted_query_string = pool_token_query.format(pool_id=pool_id) - if self.client == "CUSTOM": - response = self.callCustomEndpoint(formatted_query_string); - else: - response = self.client.execute(gql(formatted_query_string)) - if verbose: - print("Got pool tokens.") - return(response["poolTokens"]) - - def getNumPools(self, verbose=False): - - self.assertInit(); - if verbose: - print("Querying number of pools...") - - # get number of balancer pools on v2 - balancers_query = ''' + """ + formatted_query_string = pool_token_query.format(pool_id=pool_id) + if self.client == "CUSTOM": + response = self.callCustomEndpoint(formatted_query_string) + else: + response = self.client.execute(gql(formatted_query_string)) + if verbose: + print("Got pool tokens.") + return response["poolTokens"] + + def getNumPools(self, verbose=False): + + self.assertInit() + if verbose: + print("Querying number of pools...") + + # get number of balancer pools on v2 + balancers_query = """ query { balancers(first: 5) { id poolCount } } - ''' - if self.client == "CUSTOM": - response = self.callCustomEndpoint(balancers_query); - else: - response = self.client.execute(gql(balancers_query)); + """ + if self.client == "CUSTOM": + response = self.callCustomEndpoint(balancers_query) + else: + response = self.client.execute(gql(balancers_query)) - if verbose: - print("Got response from the Subgraph") + if verbose: + print("Got response from the Subgraph") - for balancer in response["balancers"]: - if balancer["id"] == "2": - num_pools = balancer["poolCount"] - return(num_pools) - return None; + for balancer in response["balancers"]: + if balancer["id"] == "2": + num_pools = balancer["poolCount"] + return num_pools + return None - def getPools(self, batch_size, skips, verbose=False): + def getPools(self, batch_size, skips, verbose=False): - self.assertInit(); - if verbose: - print("Querying pools #", skips, "through #", skips + batch_size, "...") + self.assertInit() + if verbose: + print("Querying pools #", skips, "through #", skips + batch_size, "...") - query_string = ''' + query_string = """ query {{ pools(first: {first}, skip: {skip}) {{ id @@ -158,101 +159,101 @@ def getPools(self, batch_size, skips, verbose=False): swapFee }} }} - ''' - formatted_query_string = query_string.format(first=batch_size, skip=skips) - if self.client == "CUSTOM": - response = self.callCustomEndpoint(formatted_query_string); - else: - response = self.client.execute(gql(formatted_query_string)) - - if verbose: - print("Got pools.") - return(response) - - def getV2Pools(self, batch_size, verbose=False): - - if self.client is None: - self.initBalV2Graph(verbose=verbose); - - num_pools = self.getNumPools(verbose=verbose); - num_calls = math.ceil(num_pools/batch_size) - - if verbose: - print("Querying",num_pools, "pools..."); - - # query all pools by batch to save time - pool_tokens = {}; - for i in range(num_calls): - response = self.getPools(batch_size, batch_size*i, verbose) - - for pool in response["pools"]: - curr_id = pool["id"] - curr_pool_token_data = self.getPoolTokens(curr_id, verbose=verbose); - pool_data = {}; - pool_data["tokens"] = curr_pool_token_data; - pool_data["poolType"] = pool["poolType"]; - pool_data["swapFee"] = pool["swapFee"]; - pool_tokens[curr_id] = pool_data; - return(pool_tokens) - - def getV2PoolIDs(self, batch_size, verbose=False): - - if self.client is None: - self.initBalV2Graph(verbose=verbose); - - num_pools = self.getNumPools(verbose=verbose); - num_calls = math.ceil(num_pools/batch_size) - - if verbose: - print("Querying",num_pools, "pools..."); - - # query all pools by batch to save time - poolIdsByType = {}; - for i in range(num_calls): - response = self.getPools(batch_size, batch_size*i, verbose) - for pool in response["pools"]: - if pool["poolType"] not in poolIdsByType.keys(): - poolIdsByType[pool["poolType"]] = []; - poolIdsByType[pool["poolType"]].append(pool["id"]) - header = {}; - header["stamp"] = time.time(); - poolCount = 0 - for t in poolIdsByType.keys(): - poolCount += len(poolIdsByType[t]) - header["numPools"] = poolCount; - - data = {}; - data["header"] = header; - data["pools"] = poolIdsByType - return(data) - - def getPoolBptPriceEstimate(self, poolId, verbose=False): - - self.assertInit(); - if verbose: - print("Getting data for pool", poolId, "from the subgraph...") - - query_string = ''' + """ + formatted_query_string = query_string.format(first=batch_size, skip=skips) + if self.client == "CUSTOM": + response = self.callCustomEndpoint(formatted_query_string) + else: + response = self.client.execute(gql(formatted_query_string)) + + if verbose: + print("Got pools.") + return response + + def getV2Pools(self, batch_size, verbose=False): + + if self.client is None: + self.initBalV2Graph(verbose=verbose) + + num_pools = self.getNumPools(verbose=verbose) + num_calls = math.ceil(num_pools / batch_size) + + if verbose: + print("Querying", num_pools, "pools...") + + # query all pools by batch to save time + pool_tokens = {} + for i in range(num_calls): + response = self.getPools(batch_size, batch_size * i, verbose) + + for pool in response["pools"]: + curr_id = pool["id"] + curr_pool_token_data = self.getPoolTokens(curr_id, verbose=verbose) + pool_data = {} + pool_data["tokens"] = curr_pool_token_data + pool_data["poolType"] = pool["poolType"] + pool_data["swapFee"] = pool["swapFee"] + pool_tokens[curr_id] = pool_data + return pool_tokens + + def getV2PoolIDs(self, batch_size, verbose=False): + + if self.client is None: + self.initBalV2Graph(verbose=verbose) + + num_pools = self.getNumPools(verbose=verbose) + num_calls = math.ceil(num_pools / batch_size) + + if verbose: + print("Querying", num_pools, "pools...") + + # query all pools by batch to save time + poolIdsByType = {} + for i in range(num_calls): + response = self.getPools(batch_size, batch_size * i, verbose) + for pool in response["pools"]: + if pool["poolType"] not in poolIdsByType.keys(): + poolIdsByType[pool["poolType"]] = [] + poolIdsByType[pool["poolType"]].append(pool["id"]) + header = {} + header["stamp"] = time.time() + poolCount = 0 + for t in poolIdsByType.keys(): + poolCount += len(poolIdsByType[t]) + header["numPools"] = poolCount + + data = {} + data["header"] = header + data["pools"] = poolIdsByType + return data + + def getPoolBptPriceEstimate(self, poolId, verbose=False): + + self.assertInit() + if verbose: + print("Getting data for pool", poolId, "from the subgraph...") + + query_string = """ query {{ pools(where:{{id: "{poolId}"}}) {{ totalShares totalLiquidity }} }} - ''' - formatted_query_string = query_string.format(poolId=poolId); - response = self.client.execute(gql(formatted_query_string)); + """ + formatted_query_string = query_string.format(poolId=poolId) + response = self.client.execute(gql(formatted_query_string)) - pool = response["pools"][0] - pricePerBpt = float(pool["totalLiquidity"])/float(pool["totalShares"]) + pool = response["pools"][0] + pricePerBpt = float(pool["totalLiquidity"]) / float(pool["totalShares"]) - if verbose: - print("Got price data:", pricePerBpt) - return(pricePerBpt) + if verbose: + print("Got price data:", pricePerBpt) + return pricePerBpt - def getPoolsAndTokens(self, batch_size, skips, verbose=False): - self.assertInit(); - query_string = ''' + def getPoolsAndTokens(self, batch_size, skips, verbose=False): + self.assertInit() + query_string = """ query {{ pools(first: {first}, skip: {skip}) {{ id @@ -264,44 +265,46 @@ def getPoolsAndTokens(self, batch_size, skips, verbose=False): }} }} }} - ''' - formatted_query_string = query_string.format(first=batch_size, skip=skips) + """ + formatted_query_string = query_string.format(first=batch_size, skip=skips) + + response = self.client.execute(gql(formatted_query_string)) + if self.client == "CUSTOM": + response = self.callCustomEndpoint(formatted_query_string) + else: + response = self.client.execute(gql(formatted_query_string)) + return response - response = self.client.execute(gql(formatted_query_string)); - if self.client == "CUSTOM": - response = self.callCustomEndpoint(formatted_query_string); - else: - response = self.client.execute(gql(formatted_query_string)) - return(response) def main(): - - batch_size = 30; - print(); - - if len(sys.argv) < 2: - print("Usage: python", sys.argv[0], ""); - print("No network given; defaulting to mainnet Ethereum") - network = "mainnet" - else: - network = sys.argv[1]; - - networks = ["mainnet", "kovan", "polygon", "arbitrum"]; - - if not network in networks: - print("Network", network, "is not supported!"); - print("Supported networks are:"); - for n in networks: - print("\t" + n); - print("Quitting") - quit(); - - verbose = True; - - graph = TheGraph(network) - # pools = graph.getNumPools(verbose=verbose) - pools = graph.getV2Pools(batch_size, verbose=verbose) - graph.printJson(pools) - -if __name__ == '__main__': - main(); + + batch_size = 30 + print() + + if len(sys.argv) < 2: + print("Usage: python", sys.argv[0], "") + print("No network given; defaulting to mainnet Ethereum") + network = "mainnet" + else: + network = sys.argv[1] + + networks = ["mainnet", "kovan", "polygon", "arbitrum"] + + if not network in networks: + print("Network", network, "is not supported!") + print("Supported networks are:") + for n in networks: + print("\t" + n) + print("Quitting") + quit() + + verbose = True + + graph = TheGraph(network) + # pools = graph.getNumPools(verbose=verbose) + pools = graph.getV2Pools(batch_size, verbose=verbose) + graph.printJson(pools) + + +if __name__ == "__main__": + main() diff --git a/poetry.lock b/poetry.lock index 34d6f38..7596011 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,3 +1,5 @@ +# This file is automatically @generated by Poetry and should not be changed by hand. + [[package]] name = "atomicwrites" version = "1.4.0" @@ -5,6 +7,10 @@ description = "Atomic file writes." category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, + {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, +] [[package]] name = "attrs" @@ -13,12 +19,16 @@ description = "Classes Without Boilerplate" category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, + {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, +] [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] -docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] +dev = ["coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six", "sphinx", "sphinx-notfound-page", "zope.interface"] +docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "mypy", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six", "zope.interface"] +tests-no-zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "mypy", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six"] [[package]] name = "base58" @@ -27,9 +37,13 @@ description = "Base58 and Base58Check implementation." category = "main" optional = false python-versions = ">=3.5" +files = [ + {file = "base58-2.1.1-py3-none-any.whl", hash = "sha256:11a36f4d3ce51dfc1043f3218591ac4eb1ceb172919cebe05b52a5bcc8d245c2"}, + {file = "base58-2.1.1.tar.gz", hash = "sha256:c5d0cb3f5b6e81e8e35da5754388ddcc6d0d14b6c6a132cb93d69ed580a7278c"}, +] [package.extras] -tests = ["mypy", "PyHamcrest (>=2.0.2)", "pytest (>=4.6)", "pytest-benchmark", "pytest-cov", "pytest-flake8"] +tests = ["PyHamcrest (>=2.0.2)", "mypy", "pytest (>=4.6)", "pytest-benchmark", "pytest-cov", "pytest-flake8"] [[package]] name = "bitarray" @@ -38,6 +52,45 @@ description = "efficient arrays of booleans -- C extension" category = "main" optional = false python-versions = "*" +files = [ + {file = "bitarray-1.2.2.tar.gz", hash = "sha256:27a69ffcee3b868abab3ce8b17c69e02b63e722d4d64ffd91d659f81e9984954"}, +] + +[[package]] +name = "black" +version = "22.12.0" +description = "The uncompromising code formatter." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "black-22.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eedd20838bd5d75b80c9f5487dbcb06836a43833a37846cf1d8c1cc01cef59d"}, + {file = "black-22.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:159a46a4947f73387b4d83e87ea006dbb2337eab6c879620a3ba52699b1f4351"}, + {file = "black-22.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d30b212bffeb1e252b31dd269dfae69dd17e06d92b87ad26e23890f3efea366f"}, + {file = "black-22.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:7412e75863aa5c5411886804678b7d083c7c28421210180d67dfd8cf1221e1f4"}, + {file = "black-22.12.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c116eed0efb9ff870ded8b62fe9f28dd61ef6e9ddd28d83d7d264a38417dcee2"}, + {file = "black-22.12.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1f58cbe16dfe8c12b7434e50ff889fa479072096d79f0a7f25e4ab8e94cd8350"}, + {file = "black-22.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77d86c9f3db9b1bf6761244bc0b3572a546f5fe37917a044e02f3166d5aafa7d"}, + {file = "black-22.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:82d9fe8fee3401e02e79767016b4907820a7dc28d70d137eb397b92ef3cc5bfc"}, + {file = "black-22.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:101c69b23df9b44247bd88e1d7e90154336ac4992502d4197bdac35dd7ee3320"}, + {file = "black-22.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:559c7a1ba9a006226f09e4916060982fd27334ae1998e7a38b3f33a37f7a2148"}, + {file = "black-22.12.0-py3-none-any.whl", hash = "sha256:436cc9167dd28040ad90d3b404aec22cedf24a6e4d7de221bec2730ec0c97bcf"}, + {file = "black-22.12.0.tar.gz", hash = "sha256:229351e5a18ca30f447bf724d007f890f97e13af070bb6ad4c0a441cd7596a2f"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""} +typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" @@ -46,6 +99,22 @@ description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false python-versions = "*" +files = [ + {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, + {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, +] + +[[package]] +name = "cfgv" +version = "3.3.1" +description = "Validate configuration and produce human readable error messages." +category = "main" +optional = false +python-versions = ">=3.6.1" +files = [ + {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, + {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, +] [[package]] name = "chardet" @@ -54,14 +123,37 @@ description = "Universal encoding detector for Python 2 and 3" category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, + {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, +] + +[[package]] +name = "click" +version = "8.1.3" +description = "Composable command line interface toolkit" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, + {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} [[package]] name = "colorama" version = "0.4.4" description = "Cross-platform colored terminal text." -category = "dev" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, +] [[package]] name = "cython" @@ -70,6 +162,47 @@ description = "The Cython compiler for writing C extensions for the Python langu category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "Cython-0.29.24-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:6a2cf2ccccc25413864928dfd730c29db6f63eaf98206c1e600003a445ca7f58"}, + {file = "Cython-0.29.24-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b28f92e617f540d3f21f8fd479a9c6491be920ffff672a4c61b7fc4d7f749f39"}, + {file = "Cython-0.29.24-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:37bcfa5df2a3009f49624695d917c3804fccbdfcdc5eda6378754a879711a4d5"}, + {file = "Cython-0.29.24-cp27-cp27m-win32.whl", hash = "sha256:9164aeef1af6f837e4fc20402a31d256188ba4d535e262c6cb78caf57ad744f8"}, + {file = "Cython-0.29.24-cp27-cp27m-win_amd64.whl", hash = "sha256:73ac33a4379056a02031baa4def255717fadb9181b5ac2b244792d53eae1c925"}, + {file = "Cython-0.29.24-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:09ac3087ac7a3d489ebcb3fb8402e00c13d1a3a1c6bc73fd3b0d756a3e341e79"}, + {file = "Cython-0.29.24-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:774cb8fd931ee1ba52c472bc1c19077cd6895c1b24014ae07bb27df59aed5ebe"}, + {file = "Cython-0.29.24-cp34-cp34m-win32.whl", hash = "sha256:5dd56d0be50073f0e54825a8bc3393852de0eed126339ecbca0ae149dba55cfc"}, + {file = "Cython-0.29.24-cp34-cp34m-win_amd64.whl", hash = "sha256:88dc3c250dec280b0489a83950b15809762e27232f4799b1b8d0bad503f5ab84"}, + {file = "Cython-0.29.24-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:5fa12ebafc2f688ea6d26ab6d1d2e634a9872509ba7135b902bb0d8b368fb04b"}, + {file = "Cython-0.29.24-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:60c958bcab0ff315b4036a949bed1c65334e1f6a69e17e9966d742febb59043a"}, + {file = "Cython-0.29.24-cp35-cp35m-win32.whl", hash = "sha256:166f9f29cd0058ce1a14a7b3a2458b849ed34b1ec5fd4108af3fdd2c24afcbb0"}, + {file = "Cython-0.29.24-cp35-cp35m-win_amd64.whl", hash = "sha256:76cbca0188d278e93d12ebdaf5990678e6e436485fdfad49dbe9b07717d41a3c"}, + {file = "Cython-0.29.24-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f2e9381497b12e8f622af620bde0d1d094035d79b899abb2ddd3a7891f535083"}, + {file = "Cython-0.29.24-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:d8d1a087f35e39384303f5e6b75d465d6f29d746d7138eae9d3b6e8e6f769eae"}, + {file = "Cython-0.29.24-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:112efa54a58293a4fb0acf0dd8e5b3736e95b595eee24dd88615648e445abe41"}, + {file = "Cython-0.29.24-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cf4452f0e4d50e11701bca38f3857fe6fa16593e7fd6a4d5f7be66f611b7da2"}, + {file = "Cython-0.29.24-cp36-cp36m-win32.whl", hash = "sha256:854fe2193d3ad4c8b61932ff54d6dbe10c5fa8749eb8958d72cc0ab28243f833"}, + {file = "Cython-0.29.24-cp36-cp36m-win_amd64.whl", hash = "sha256:84826ec1c11cda56261a252ddecac0c7d6b02e47e81b94f40b27b4c23c29c17c"}, + {file = "Cython-0.29.24-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6ade74eece909fd3a437d9a5084829180751d7ade118e281e9824dd75eafaff2"}, + {file = "Cython-0.29.24-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0a142c6b862e6ed6b02209d543062c038c110585b5e32d1ad7c9717af4f07e41"}, + {file = "Cython-0.29.24-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:10cb3def9774fa99e4583617a5616874aed3255dc241fd1f4a3c2978c78e1c53"}, + {file = "Cython-0.29.24-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f41ef7edd76dd23315925e003f0c58c8585f3ab24be6885c4b3f60e77c82746"}, + {file = "Cython-0.29.24-cp37-cp37m-win32.whl", hash = "sha256:821c2d416ad7d006b069657ee1034c0e0cb45bdbe9ab6ab631e8c495dfcfa4ac"}, + {file = "Cython-0.29.24-cp37-cp37m-win_amd64.whl", hash = "sha256:2d9e61ed1056a3b6a4b9156b62297ad18b357a7948e57a2f49b061217696567e"}, + {file = "Cython-0.29.24-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:55b0ee28c2c8118bfb3ad9b25cf7a6cbd724e442ea96956e32ccd908d5e3e043"}, + {file = "Cython-0.29.24-cp38-cp38-manylinux1_i686.whl", hash = "sha256:eb2843f8cc01c645725e6fc690a84e99cdb266ce8ebe427cf3a680ff09f876aa"}, + {file = "Cython-0.29.24-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:661dbdea519d9cfb288867252b75fef73ffa8e8bb674cec27acf70646afb369b"}, + {file = "Cython-0.29.24-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc05de569f811be1fcfde6756c9048ae518f0c4b6d9f8f024752c5365d934cac"}, + {file = "Cython-0.29.24-cp38-cp38-win32.whl", hash = "sha256:a102cfa795c6b3b81a29bdb9dbec545367cd7f353c03e6f30a056fdfefd92854"}, + {file = "Cython-0.29.24-cp38-cp38-win_amd64.whl", hash = "sha256:416046a98255eff97ec02077d20ebeaae52682dfca1c35aadf31260442b92514"}, + {file = "Cython-0.29.24-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ad43e684ade673565f6f9d6638015112f6c7f11aa2a632167b79014f613f0f5f"}, + {file = "Cython-0.29.24-cp39-cp39-manylinux1_i686.whl", hash = "sha256:afb521523cb46ddaa8d269b421f88ea2731fee05e65b952b96d4db760f5a2a1c"}, + {file = "Cython-0.29.24-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:0d414458cb22f8a90d64260da6dace5d5fcebde43f31be52ca51f818c46db8cb"}, + {file = "Cython-0.29.24-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8cb87777e82d1996aef6c146560a19270684271c9c669ba62ac6803b3cd2ff82"}, + {file = "Cython-0.29.24-cp39-cp39-win32.whl", hash = "sha256:91339ee4b465924a3ea4b2a9cec7f7227bc4cadf673ce859d24c2b9ef60b1214"}, + {file = "Cython-0.29.24-cp39-cp39-win_amd64.whl", hash = "sha256:5fb977945a2111f6b64501fdf7ed0ec162cc502b84457fd648d6a558ea8de0d6"}, + {file = "Cython-0.29.24-py2.py3-none-any.whl", hash = "sha256:f96411f0120b5cae483923aaacd2872af8709be4b46522daedc32f051d778385"}, + {file = "Cython-0.29.24.tar.gz", hash = "sha256:cdf04d07c3600860e8c2ebaad4e8f52ac3feb212453c1764a49ac08c827e8443"}, +] [[package]] name = "cytoolz" @@ -78,6 +211,9 @@ description = "Cython implementation of Toolz: High performance functional utili category = "main" optional = false python-versions = ">=3.5" +files = [ + {file = "cytoolz-0.11.2.tar.gz", hash = "sha256:ea23663153806edddce7e4153d1d407d62357c05120a4e8485bddf1bd5ab22b4"}, +] [package.dependencies] toolz = ">=0.8.0" @@ -85,6 +221,18 @@ toolz = ">=0.8.0" [package.extras] cython = ["cython"] +[[package]] +name = "distlib" +version = "0.3.6" +description = "Distribution utilities" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, + {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, +] + [[package]] name = "eth-abi" version = "2.1.1" @@ -92,6 +240,10 @@ description = "eth_abi: Python utilities for working with Ethereum ABI definitio category = "main" optional = false python-versions = ">=3.6, <4" +files = [ + {file = "eth_abi-2.1.1-py3-none-any.whl", hash = "sha256:78df5d2758247a8f0766a7cfcea4575bcfe568c34a33e6d05a72c328a9040444"}, + {file = "eth_abi-2.1.1.tar.gz", hash = "sha256:4bb1d87bb6605823379b07f6c02c8af45df01a27cc85bd6abb7cf1446ce7d188"}, +] [package.dependencies] eth-typing = ">=2.0.0,<3.0.0" @@ -99,10 +251,10 @@ eth-utils = ">=1.2.0,<2.0.0" parsimonious = ">=0.8.0,<0.9.0" [package.extras] -dev = ["bumpversion (>=0.5.3,<1)", "pytest-watch (>=4.1.0,<5)", "wheel", "twine", "ipython", "pytest (==4.4.1)", "pytest-pythonpath (>=0.7.1)", "pytest-xdist (==1.22.3)", "tox (>=2.9.1,<3)", "eth-hash", "hypothesis (>=3.6.1,<4)", "flake8 (==3.4.1)", "isort (>=4.2.15,<5)", "mypy (==0.701)", "pydocstyle (>=3.0.0,<4)", "Sphinx (>=1.6.5,<2)", "sphinx-rtd-theme (>=0.1.9)", "towncrier (>=19.2.0,<20)"] +dev = ["Sphinx (>=1.6.5,<2)", "bumpversion (>=0.5.3,<1)", "eth-hash[pycryptodome]", "flake8 (==3.4.1)", "hypothesis (>=3.6.1,<4)", "ipython", "isort (>=4.2.15,<5)", "mypy (==0.701)", "pydocstyle (>=3.0.0,<4)", "pytest (==4.4.1)", "pytest-pythonpath (>=0.7.1)", "pytest-watch (>=4.1.0,<5)", "pytest-xdist (==1.22.3)", "sphinx-rtd-theme (>=0.1.9)", "towncrier (>=19.2.0,<20)", "tox (>=2.9.1,<3)", "twine", "wheel"] doc = ["Sphinx (>=1.6.5,<2)", "sphinx-rtd-theme (>=0.1.9)", "towncrier (>=19.2.0,<20)"] lint = ["flake8 (==3.4.1)", "isort (>=4.2.15,<5)", "mypy (==0.701)", "pydocstyle (>=3.0.0,<4)"] -test = ["pytest (==4.4.1)", "pytest-pythonpath (>=0.7.1)", "pytest-xdist (==1.22.3)", "tox (>=2.9.1,<3)", "eth-hash", "hypothesis (>=3.6.1,<4)"] +test = ["eth-hash[pycryptodome]", "hypothesis (>=3.6.1,<4)", "pytest (==4.4.1)", "pytest-pythonpath (>=0.7.1)", "pytest-xdist (==1.22.3)", "tox (>=2.9.1,<3)"] tools = ["hypothesis (>=3.6.1,<4)"] [[package]] @@ -112,6 +264,10 @@ description = "eth-account: Sign Ethereum transactions and messages with local p category = "main" optional = false python-versions = ">=3.6, <4" +files = [ + {file = "eth-account-0.5.6.tar.gz", hash = "sha256:baef80956e88af5643f8602e72aab6bcd91d8a9f71dd03c7a7f1145f5e6fd694"}, + {file = "eth_account-0.5.6-py3-none-any.whl", hash = "sha256:d324daf5a40bd5bdaf5ddaebfec71e7440b21f9ae4989921ce1253d63f8fe436"}, +] [package.dependencies] bitarray = ">=1.2.1,<1.3.0" @@ -124,7 +280,7 @@ hexbytes = ">=0.1.0,<1" rlp = ">=1.0.0,<3" [package.extras] -dev = ["bumpversion (>=0.5.3,<1)", "pytest-watch (>=4.1.0,<5)", "wheel", "twine", "ipython", "hypothesis (>=4.18.0,<5)", "pytest (==5.4.1)", "pytest-xdist", "tox (==3.14.6)", "flake8 (==3.7.9)", "isort (>=4.2.15,<5)", "mypy (==0.770)", "pydocstyle (>=5.0.0,<6)", "Sphinx (>=1.6.5,<2)", "sphinx-rtd-theme (>=0.1.9,<1)", "towncrier (>=19.2.0,<20)"] +dev = ["Sphinx (>=1.6.5,<2)", "bumpversion (>=0.5.3,<1)", "flake8 (==3.7.9)", "hypothesis (>=4.18.0,<5)", "ipython", "isort (>=4.2.15,<5)", "mypy (==0.770)", "pydocstyle (>=5.0.0,<6)", "pytest (==5.4.1)", "pytest-watch (>=4.1.0,<5)", "pytest-xdist", "sphinx-rtd-theme (>=0.1.9,<1)", "towncrier (>=19.2.0,<20)", "tox (==3.14.6)", "twine", "wheel"] doc = ["Sphinx (>=1.6.5,<2)", "sphinx-rtd-theme (>=0.1.9,<1)", "towncrier (>=19.2.0,<20)"] lint = ["flake8 (==3.7.9)", "isort (>=4.2.15,<5)", "mypy (==0.770)", "pydocstyle (>=5.0.0,<6)"] test = ["hypothesis (>=4.18.0,<5)", "pytest (==5.4.1)", "pytest-xdist", "tox (==3.14.6)"] @@ -136,12 +292,16 @@ description = "eth-hash: The Ethereum hashing function, keccak256, sometimes (er category = "main" optional = false python-versions = ">=3.5, <4" +files = [ + {file = "eth-hash-0.3.2.tar.gz", hash = "sha256:3f40cecd5ead88184aa9550afc19d057f103728108c5102f592f8415949b5a76"}, + {file = "eth_hash-0.3.2-py3-none-any.whl", hash = "sha256:de7385148a8e0237ba1240cddbc06d53f56731140f8593bdb8429306f6b42271"}, +] [package.dependencies] pycryptodome = {version = ">=3.6.6,<4", optional = true, markers = "extra == \"pycryptodome\""} [package.extras] -dev = ["bumpversion (>=0.5.3,<1)", "pytest-watch (>=4.1.0,<5)", "wheel", "twine", "ipython", "pytest (==5.4.1)", "pytest-xdist", "tox (==3.14.6)", "flake8 (==3.7.9)", "isort (>=4.2.15,<5)", "mypy (==0.770)", "pydocstyle (>=5.0.0,<6)", "Sphinx (>=1.6.5,<2)", "sphinx-rtd-theme (>=0.1.9,<1)", "towncrier (>=19.2.0,<20)"] +dev = ["Sphinx (>=1.6.5,<2)", "bumpversion (>=0.5.3,<1)", "flake8 (==3.7.9)", "ipython", "isort (>=4.2.15,<5)", "mypy (==0.770)", "pydocstyle (>=5.0.0,<6)", "pytest (==5.4.1)", "pytest-watch (>=4.1.0,<5)", "pytest-xdist", "sphinx-rtd-theme (>=0.1.9,<1)", "towncrier (>=19.2.0,<20)", "tox (==3.14.6)", "twine", "wheel"] doc = ["Sphinx (>=1.6.5,<2)", "sphinx-rtd-theme (>=0.1.9,<1)", "towncrier (>=19.2.0,<20)"] lint = ["flake8 (==3.7.9)", "isort (>=4.2.15,<5)", "mypy (==0.770)", "pydocstyle (>=5.0.0,<6)"] pycryptodome = ["pycryptodome (>=3.6.6,<4)"] @@ -155,6 +315,10 @@ description = "A library for handling the encrypted keyfiles used to store ether category = "main" optional = false python-versions = "*" +files = [ + {file = "eth-keyfile-0.5.1.tar.gz", hash = "sha256:939540efb503380bc30d926833e6a12b22c6750de80feef3720d79e5a79de47d"}, + {file = "eth_keyfile-0.5.1-py3-none-any.whl", hash = "sha256:70d734af17efdf929a90bb95375f43522be4ed80c3b9e0a8bca575fb11cd1159"}, +] [package.dependencies] cytoolz = ">=0.9.0,<1.0.0" @@ -169,6 +333,10 @@ description = "Common API for Ethereum key operations." category = "main" optional = false python-versions = "*" +files = [ + {file = "eth-keys-0.3.3.tar.gz", hash = "sha256:a9a1e83e443bd369265b1a1b66dc30f6841bdbb3577ecd042e037b7b405b6cb0"}, + {file = "eth_keys-0.3.3-py3-none-any.whl", hash = "sha256:412dd5c9732b8e92af40c9c77597f4661c57eba3897aaa55e527af56a8c5ab47"}, +] [package.dependencies] eth-typing = ">=2.2.1,<3.0.0" @@ -176,10 +344,10 @@ eth-utils = ">=1.3.0,<2.0.0" [package.extras] coincurve = ["coincurve (>=7.0.0,<13.0.0)"] -dev = ["tox (==2.7.0)", "bumpversion (==0.5.3)", "twine", "eth-utils (>=1.3.0,<2.0.0)", "eth-typing (>=2.2.1,<3.0.0)", "flake8 (==3.0.4)", "mypy (==0.701)", "asn1tools (>=0.146.2,<0.147)", "pyasn1 (>=0.4.5,<0.5)", "pytest (==3.2.2)", "hypothesis (>=4.56.1,<5.0.0)", "eth-hash", "eth-hash"] -eth-keys = ["eth-utils (>=1.3.0,<2.0.0)", "eth-typing (>=2.2.1,<3.0.0)"] +dev = ["asn1tools (>=0.146.2,<0.147)", "bumpversion (==0.5.3)", "eth-hash[pycryptodome]", "eth-hash[pysha3]", "eth-typing (>=2.2.1,<3.0.0)", "eth-utils (>=1.3.0,<2.0.0)", "flake8 (==3.0.4)", "hypothesis (>=4.56.1,<5.0.0)", "mypy (==0.701)", "pyasn1 (>=0.4.5,<0.5)", "pytest (==3.2.2)", "tox (==2.7.0)", "twine"] +eth-keys = ["eth-typing (>=2.2.1,<3.0.0)", "eth-utils (>=1.3.0,<2.0.0)"] lint = ["flake8 (==3.0.4)", "mypy (==0.701)"] -test = ["asn1tools (>=0.146.2,<0.147)", "pyasn1 (>=0.4.5,<0.5)", "pytest (==3.2.2)", "hypothesis (>=4.56.1,<5.0.0)", "eth-hash", "eth-hash"] +test = ["asn1tools (>=0.146.2,<0.147)", "eth-hash[pycryptodome]", "eth-hash[pysha3]", "hypothesis (>=4.56.1,<5.0.0)", "pyasn1 (>=0.4.5,<0.5)", "pytest (==3.2.2)"] [[package]] name = "eth-rlp" @@ -188,6 +356,10 @@ description = "eth-rlp: RLP definitions for common Ethereum objects in Python" category = "main" optional = false python-versions = ">=3.6, <4" +files = [ + {file = "eth-rlp-0.2.1.tar.gz", hash = "sha256:f016f980b0ed42ee7650ba6e4e4d3c4e9aa06d8b9c6825a36d3afe5aa0187a8b"}, + {file = "eth_rlp-0.2.1-py3-none-any.whl", hash = "sha256:cc389ef8d7b6f76a98f90bcdbff1b8684b3a78f53d47e871191b50d4d6aee5a1"}, +] [package.dependencies] eth-utils = ">=1.0.1,<2" @@ -195,10 +367,10 @@ hexbytes = ">=0.1.0,<1" rlp = ">=0.6.0,<3" [package.extras] -dev = ["Sphinx (>=1.6.5,<2)", "bumpversion (>=0.5.3,<1)", "eth-hash", "flake8 (==3.7.9)", "ipython", "isort (>=4.2.15,<5)", "mypy (==0.770)", "pydocstyle (>=3.0.0,<4)", "pytest-watch (>=4.1.0,<5)", "pytest-xdist", "pytest (==5.4.1)", "sphinx-rtd-theme (>=0.1.9)", "towncrier (>=19.2.0,<20)", "tox (==3.14.6)", "twine", "wheel"] +dev = ["Sphinx (>=1.6.5,<2)", "bumpversion (>=0.5.3,<1)", "eth-hash[pycryptodome]", "flake8 (==3.7.9)", "ipython", "isort (>=4.2.15,<5)", "mypy (==0.770)", "pydocstyle (>=3.0.0,<4)", "pytest (==5.4.1)", "pytest-watch (>=4.1.0,<5)", "pytest-xdist", "sphinx-rtd-theme (>=0.1.9)", "towncrier (>=19.2.0,<20)", "tox (==3.14.6)", "twine", "wheel"] doc = ["Sphinx (>=1.6.5,<2)", "sphinx-rtd-theme (>=0.1.9)", "towncrier (>=19.2.0,<20)"] lint = ["flake8 (==3.7.9)", "isort (>=4.2.15,<5)", "mypy (==0.770)", "pydocstyle (>=3.0.0,<4)"] -test = ["eth-hash", "pytest-xdist", "pytest (==5.4.1)", "tox (==3.14.6)"] +test = ["eth-hash[pycryptodome]", "pytest (==5.4.1)", "pytest-xdist", "tox (==3.14.6)"] [[package]] name = "eth-typing" @@ -207,9 +379,13 @@ description = "eth-typing: Common type annotations for ethereum python packages" category = "main" optional = false python-versions = ">=3.5, <4" +files = [ + {file = "eth-typing-2.2.2.tar.gz", hash = "sha256:97ba0f83da7cf1d3668f6ed54983f21168076c552762bf5e06d4a20921877f3f"}, + {file = "eth_typing-2.2.2-py3-none-any.whl", hash = "sha256:1140c7592321dbf10d6663c46f7e43eb0e6410b011b03f14b3df3eb1f76aa9bb"}, +] [package.extras] -dev = ["bumpversion (>=0.5.3,<1)", "pytest-watch (>=4.1.0,<5)", "wheel", "twine", "ipython", "pytest (>=4.4,<4.5)", "pytest-xdist", "tox (>=2.9.1,<3)", "flake8 (==3.8.3)", "isort (>=4.2.15,<5)", "mypy (==0.782)", "pydocstyle (>=3.0.0,<4)", "Sphinx (>=1.6.5,<2)", "sphinx-rtd-theme (>=0.1.9)"] +dev = ["Sphinx (>=1.6.5,<2)", "bumpversion (>=0.5.3,<1)", "flake8 (==3.8.3)", "ipython", "isort (>=4.2.15,<5)", "mypy (==0.782)", "pydocstyle (>=3.0.0,<4)", "pytest (>=4.4,<4.5)", "pytest-watch (>=4.1.0,<5)", "pytest-xdist", "sphinx-rtd-theme (>=0.1.9)", "tox (>=2.9.1,<3)", "twine", "wheel"] doc = ["Sphinx (>=1.6.5,<2)", "sphinx-rtd-theme (>=0.1.9)"] lint = ["flake8 (==3.8.3)", "isort (>=4.2.15,<5)", "mypy (==0.782)", "pydocstyle (>=3.0.0,<4)"] test = ["pytest (>=4.4,<4.5)", "pytest-xdist", "tox (>=2.9.1,<3)"] @@ -221,6 +397,10 @@ description = "eth-utils: Common utility functions for python code that interact category = "main" optional = false python-versions = ">=3.5,!=3.5.2,<4" +files = [ + {file = "eth-utils-1.10.0.tar.gz", hash = "sha256:bf82762a46978714190b0370265a7148c954d3f0adaa31c6f085ea375e4c61af"}, + {file = "eth_utils-1.10.0-py3-none-any.whl", hash = "sha256:74240a8c6f652d085ed3c85f5f1654203d2f10ff9062f83b3bad0a12ff321c7a"}, +] [package.dependencies] cytoolz = {version = ">=0.10.1,<1.0.0", markers = "implementation_name == \"cpython\""} @@ -229,11 +409,27 @@ eth-typing = ">=2.2.1,<3.0.0" toolz = {version = ">0.8.2,<1", markers = "implementation_name == \"pypy\""} [package.extras] -dev = ["bumpversion (>=0.5.3,<1)", "pytest-watch (>=4.1.0,<5)", "wheel (>=0.30.0,<1.0.0)", "twine (>=1.13,<2)", "ipython", "hypothesis (>=4.43.0,<5.0.0)", "pytest (==5.4.1)", "pytest-xdist", "tox (==3.14.6)", "black (>=18.6b4,<19)", "flake8 (==3.7.9)", "isort (>=4.2.15,<5)", "mypy (==0.720)", "pydocstyle (>=5.0.0,<6)", "pytest (>=3.4.1,<4.0.0)", "Sphinx (>=1.6.5,<2)", "sphinx-rtd-theme (>=0.1.9,<2)", "towncrier (>=19.2.0,<20)"] +dev = ["Sphinx (>=1.6.5,<2)", "black (>=18.6b4,<19)", "bumpversion (>=0.5.3,<1)", "flake8 (==3.7.9)", "hypothesis (>=4.43.0,<5.0.0)", "ipython", "isort (>=4.2.15,<5)", "mypy (==0.720)", "pydocstyle (>=5.0.0,<6)", "pytest (==5.4.1)", "pytest (>=3.4.1,<4.0.0)", "pytest-watch (>=4.1.0,<5)", "pytest-xdist", "sphinx-rtd-theme (>=0.1.9,<2)", "towncrier (>=19.2.0,<20)", "tox (==3.14.6)", "twine (>=1.13,<2)", "wheel (>=0.30.0,<1.0.0)"] doc = ["Sphinx (>=1.6.5,<2)", "sphinx-rtd-theme (>=0.1.9,<2)", "towncrier (>=19.2.0,<20)"] lint = ["black (>=18.6b4,<19)", "flake8 (==3.7.9)", "isort (>=4.2.15,<5)", "mypy (==0.720)", "pydocstyle (>=5.0.0,<6)", "pytest (>=3.4.1,<4.0.0)"] test = ["hypothesis (>=4.43.0,<5.0.0)", "pytest (==5.4.1)", "pytest-xdist", "tox (==3.14.6)"] +[[package]] +name = "filelock" +version = "3.8.2" +description = "A platform independent file lock." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "filelock-3.8.2-py3-none-any.whl", hash = "sha256:8df285554452285f79c035efb0c861eb33a4bcfa5b7a137016e32e6a90f9792c"}, + {file = "filelock-3.8.2.tar.gz", hash = "sha256:7565f628ea56bfcd8e54e42bdc55da899c85c1abfe1b5bcfd147e9188cebb3b2"}, +] + +[package.extras] +docs = ["furo (>=2022.9.29)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] +testing = ["covdefaults (>=2.2.2)", "coverage (>=6.5)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-timeout (>=2.1)"] + [[package]] name = "gql" version = "2.0.0" @@ -241,6 +437,10 @@ description = "GraphQL client for Python" category = "main" optional = false python-versions = "*" +files = [ + {file = "gql-2.0.0-py2.py3-none-any.whl", hash = "sha256:35032ddd4bfe6b8f3169f806b022168932385d751eacc5c5f7122e0b3f4d6b88"}, + {file = "gql-2.0.0.tar.gz", hash = "sha256:fe8d3a08047f77362ddfcfddba7cae377da2dd66f5e61c59820419c9283d4fb5"}, +] [package.dependencies] graphql-core = ">=2.3.2,<3" @@ -249,8 +449,8 @@ requests = ">=2.12,<3" six = ">=1.10.0" [package.extras] -dev = ["flake8 (==3.8.1)", "isort (==4.3.21)", "black (==19.10b0)", "mypy (==0.770)", "check-manifest (>=0.42,<1)", "pytest (==5.4.2)", "pytest-asyncio (==0.11.0)", "pytest-cov (==2.8.1)", "mock (==4.0.2)", "vcrpy (==4.0.2)", "coveralls (==2.0.0)"] -test = ["pytest (==5.4.2)", "pytest-asyncio (==0.11.0)", "pytest-cov (==2.8.1)", "mock (==4.0.2)", "vcrpy (==4.0.2)", "coveralls (==2.0.0)"] +dev = ["black (==19.10b0)", "check-manifest (>=0.42,<1)", "coveralls (==2.0.0)", "flake8 (==3.8.1)", "isort (==4.3.21)", "mock (==4.0.2)", "mypy (==0.770)", "pytest (==5.4.2)", "pytest-asyncio (==0.11.0)", "pytest-cov (==2.8.1)", "vcrpy (==4.0.2)"] +test = ["coveralls (==2.0.0)", "mock (==4.0.2)", "pytest (==5.4.2)", "pytest-asyncio (==0.11.0)", "pytest-cov (==2.8.1)", "vcrpy (==4.0.2)"] [[package]] name = "graphql-core" @@ -259,6 +459,10 @@ description = "GraphQL implementation for Python" category = "main" optional = false python-versions = "*" +files = [ + {file = "graphql-core-2.3.2.tar.gz", hash = "sha256:aac46a9ac524c9855910c14c48fc5d60474def7f99fd10245e76608eba7af746"}, + {file = "graphql_core-2.3.2-py2.py3-none-any.whl", hash = "sha256:44c9bac4514e5e30c5a595fac8e3c76c1975cae14db215e8174c7fe995825bad"}, +] [package.dependencies] promise = ">=2.3,<3" @@ -267,7 +471,7 @@ six = ">=1.10.0" [package.extras] gevent = ["gevent (>=1.1)"] -test = ["six (==1.14.0)", "pyannotate (==1.2.0)", "pytest (==4.6.10)", "pytest-django (==3.9.0)", "pytest-cov (==2.8.1)", "coveralls (==1.11.1)", "cython (==0.29.17)", "gevent (==1.5.0)", "pytest-benchmark (==3.2.3)", "pytest-mock (==2.0.0)"] +test = ["coveralls (==1.11.1)", "cython (==0.29.17)", "gevent (==1.5.0)", "pyannotate (==1.2.0)", "pytest (==4.6.10)", "pytest-benchmark (==3.2.3)", "pytest-cov (==2.8.1)", "pytest-django (==3.9.0)", "pytest-mock (==2.0.0)", "six (==1.14.0)"] [[package]] name = "hexbytes" @@ -276,37 +480,43 @@ description = "hexbytes: Python `bytes` subclass that decodes hex, with a readab category = "main" optional = false python-versions = ">=3.6, <4" +files = [ + {file = "hexbytes-0.2.2-py3-none-any.whl", hash = "sha256:ef53c37ea9f316fff86fcb1df057b4c6ba454da348083e972031bbf7bc9c3acc"}, + {file = "hexbytes-0.2.2.tar.gz", hash = "sha256:a5881304d186e87578fb263a85317c808cf130e1d4b3d37d30142ab0f7898d03"}, +] [package.extras] -dev = ["Sphinx (>=1.6.5,<2)", "bumpversion (>=0.5.3,<1)", "eth-utils (>=1.0.1,<2)", "flake8 (==3.7.9)", "hypothesis (>=3.44.24,<4)", "ipython", "isort (>=4.2.15,<5)", "mypy (==0.770)", "pydocstyle (>=5.0.0,<6)", "pytest-watch (>=4.1.0,<5)", "pytest-xdist", "pytest (==5.4.1)", "sphinx-rtd-theme (>=0.1.9,<1)", "towncrier (>=19.2.0,<20)", "tox (==3.14.6)", "twine", "wheel"] +dev = ["Sphinx (>=1.6.5,<2)", "bumpversion (>=0.5.3,<1)", "eth-utils (>=1.0.1,<2)", "flake8 (==3.7.9)", "hypothesis (>=3.44.24,<4)", "ipython", "isort (>=4.2.15,<5)", "mypy (==0.770)", "pydocstyle (>=5.0.0,<6)", "pytest (==5.4.1)", "pytest-watch (>=4.1.0,<5)", "pytest-xdist", "sphinx-rtd-theme (>=0.1.9,<1)", "towncrier (>=19.2.0,<20)", "tox (==3.14.6)", "twine", "wheel"] doc = ["Sphinx (>=1.6.5,<2)", "sphinx-rtd-theme (>=0.1.9,<1)", "towncrier (>=19.2.0,<20)"] lint = ["flake8 (==3.7.9)", "isort (>=4.2.15,<5)", "mypy (==0.770)", "pydocstyle (>=5.0.0,<6)"] -test = ["eth-utils (>=1.0.1,<2)", "hypothesis (>=3.44.24,<4)", "pytest-xdist", "pytest (==5.4.1)", "tox (==3.14.6)"] +test = ["eth-utils (>=1.0.1,<2)", "hypothesis (>=3.44.24,<4)", "pytest (==5.4.1)", "pytest-xdist", "tox (==3.14.6)"] [[package]] -name = "idna" -version = "2.10" -description = "Internationalized Domain Names in Applications (IDNA)" +name = "identify" +version = "2.5.9" +description = "File identification library for Python" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.7" +files = [ + {file = "identify-2.5.9-py2.py3-none-any.whl", hash = "sha256:a390fb696e164dbddb047a0db26e57972ae52fbd037ae68797e5ae2f4492485d"}, + {file = "identify-2.5.9.tar.gz", hash = "sha256:906036344ca769539610436e40a684e170c3648b552194980bb7b617a8daeb9f"}, +] + +[package.extras] +license = ["ukkonen"] [[package]] -name = "importlib-metadata" -version = "4.8.2" -description = "Read metadata from Python packages" +name = "idna" +version = "2.10" +description = "Internationalized Domain Names in Applications (IDNA)" category = "main" optional = false -python-versions = ">=3.6" - -[package.dependencies] -typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} -zipp = ">=0.5" - -[package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -perf = ["ipython"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, + {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, +] [[package]] name = "iniconfig" @@ -315,6 +525,10 @@ description = "iniconfig: brain-dead simple config-ini parsing" category = "dev" optional = false python-versions = "*" +files = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] [[package]] name = "ipfshttpclient" @@ -323,6 +537,10 @@ description = "Python IPFS HTTP CLIENT library" category = "main" optional = false python-versions = ">=3.5.4,!=3.6.0,!=3.6.1,!=3.7.0,!=3.7.1" +files = [ + {file = "ipfshttpclient-0.7.0a1-py3-none-any.whl", hash = "sha256:a45fb0ef087d71647c77b02e0cb3a033045377f134971dfcdc1c6f21cd8ad87c"}, + {file = "ipfshttpclient-0.7.0a1.tar.gz", hash = "sha256:ada7d7c40879ebf8a736c1ff7c690ddb574a120c2226dc982d44156408de426a"}, +] [package.dependencies] multiaddr = ">=0.0.7" @@ -335,16 +553,20 @@ description = "An implementation of JSON Schema validation for Python" category = "main" optional = false python-versions = "*" +files = [ + {file = "jsonschema-3.2.0-py2.py3-none-any.whl", hash = "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163"}, + {file = "jsonschema-3.2.0.tar.gz", hash = "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a"}, +] [package.dependencies] attrs = ">=17.4.0" -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} pyrsistent = ">=0.14.0" +setuptools = "*" six = ">=1.11.0" [package.extras] format = ["idna", "jsonpointer (>1.13)", "rfc3987", "strict-rfc3339", "webcolors"] -format_nongpl = ["idna", "jsonpointer (>1.13)", "webcolors", "rfc3986-validator (>0.1.0)", "rfc3339-validator"] +format-nongpl = ["idna", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "webcolors"] [[package]] name = "jstyleson" @@ -353,6 +575,9 @@ description = "Library to parse JSON with js-style comments." category = "main" optional = false python-versions = "*" +files = [ + {file = "jstyleson-0.0.2.tar.gz", hash = "sha256:680003f3b15a2959e4e6a351f3b858e3c07dd3e073a0d54954e34d8ea5e1308e"}, +] [[package]] name = "lru-dict" @@ -361,6 +586,9 @@ description = "An Dict like LRU container." category = "main" optional = false python-versions = "*" +files = [ + {file = "lru-dict-1.1.7.tar.gz", hash = "sha256:45b81f67d75341d4433abade799a47e9c42a9e22a118531dcb5e549864032d7c"}, +] [[package]] name = "multiaddr" @@ -369,6 +597,10 @@ description = "Python implementation of jbenet's multiaddr" category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" +files = [ + {file = "multiaddr-0.0.9-py2.py3-none-any.whl", hash = "sha256:5c0f862cbcf19aada2a899f80ef896ddb2e85614e0c8f04dd287c06c69dac95b"}, + {file = "multiaddr-0.0.9.tar.gz", hash = "sha256:30b2695189edc3d5b90f1c303abb8f02d963a3a4edf2e7178b975eb417ab0ecf"}, +] [package.dependencies] base58 = "*" @@ -377,17 +609,31 @@ six = "*" varint = "*" [[package]] -name = "multicall" -version = "0.1.3" -description = "aggregate results from multiple ethereum contract calls" +name = "multicaller" +version = "0.0.0a13" +description = "web3py multicaller simplified interface" category = "main" optional = false -python-versions = ">=3.7,<4.0" +python-versions = "*" +files = [ + {file = "multicaller-0.0.0a13-py3-none-any.whl", hash = "sha256:c2978bfd759eb6436a31fd944262c819f3bca357d48a16d85c72ead2bb962a84"}, + {file = "multicaller-0.0.0a13.tar.gz", hash = "sha256:b8c26e93511a1ccc0eb689b8d106941d5a36c11b97d6127bc1056249fbbbe51a"}, +] [package.dependencies] -eth_abi = ">=2.1.0,<3.0.0" -eth_utils = ">=1.8.4,<2.0.0" -web3 = ">=5.4.0,<6.0.0" +web3 = ">=5.19.0" + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] [[package]] name = "netaddr" @@ -396,6 +642,25 @@ description = "A network address manipulation library for Python" category = "main" optional = false python-versions = "*" +files = [ + {file = "netaddr-0.8.0-py2.py3-none-any.whl", hash = "sha256:9666d0232c32d2656e5e5f8d735f58fd6c7457ce52fc21c98d45f2af78f990ac"}, + {file = "netaddr-0.8.0.tar.gz", hash = "sha256:d6cc57c7a07b1d9d2e917aa8b36ae8ce61c35ba3fcd1b83ca31c5a0ee2b5a243"}, +] + +[[package]] +name = "nodeenv" +version = "1.7.0" +description = "Node.js virtual environment builder" +category = "main" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +files = [ + {file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"}, + {file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"}, +] + +[package.dependencies] +setuptools = "*" [[package]] name = "packaging" @@ -404,6 +669,10 @@ description = "Core utilities for Python packages" category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, +] [package.dependencies] pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" @@ -415,10 +684,41 @@ description = "(Soon to be) the fastest pure-Python PEG parser I could muster" category = "main" optional = false python-versions = "*" +files = [ + {file = "parsimonious-0.8.1.tar.gz", hash = "sha256:3add338892d580e0cb3b1a39e4a1b427ff9f687858fdd61097053742391a9f6b"}, +] [package.dependencies] six = ">=1.9.0" +[[package]] +name = "pathspec" +version = "0.10.2" +description = "Utility library for gitignore style pattern matching of file paths." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pathspec-0.10.2-py3-none-any.whl", hash = "sha256:88c2606f2c1e818b978540f73ecc908e13999c6c3a383daf3705652ae79807a5"}, + {file = "pathspec-0.10.2.tar.gz", hash = "sha256:8f6bf73e5758fd365ef5d58ce09ac7c27d2833a8d7da51712eac6e27e35141b0"}, +] + +[[package]] +name = "platformdirs" +version = "2.6.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "platformdirs-2.6.0-py3-none-any.whl", hash = "sha256:1a89a12377800c81983db6be069ec068eee989748799b946cce2a6e80dcc54ca"}, + {file = "platformdirs-2.6.0.tar.gz", hash = "sha256:b46ffafa316e6b83b47489d240ce17173f123a9b9c83282141c3daf26ad9ac2e"}, +] + +[package.extras] +docs = ["furo (>=2022.9.29)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.4)"] +test = ["appdirs (==1.4.4)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] + [[package]] name = "pluggy" version = "1.0.0" @@ -426,14 +726,35 @@ description = "plugin and hook calling mechanisms for python" category = "dev" optional = false python-versions = ">=3.6" - -[package.dependencies] -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} +files = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] [package.extras] dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "pre-commit" +version = "2.20.0" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pre_commit-2.20.0-py2.py3-none-any.whl", hash = "sha256:51a5ba7c480ae8072ecdb6933df22d2f812dc897d5fe848778116129a681aac7"}, + {file = "pre_commit-2.20.0.tar.gz", hash = "sha256:a978dac7bc9ec0bcee55c18a277d553b0f419d259dadb4b9418ff2d00eb43959"}, +] + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +toml = "*" +virtualenv = ">=20.0.8" + [[package]] name = "promise" version = "2.3" @@ -441,12 +762,15 @@ description = "Promises/A+ implementation for Python" category = "main" optional = false python-versions = "*" +files = [ + {file = "promise-2.3.tar.gz", hash = "sha256:dfd18337c523ba4b6a58801c164c1904a9d4d1b1747c7d5dbf45b693a49d93d0"}, +] [package.dependencies] six = "*" [package.extras] -test = ["pytest (>=2.7.3)", "pytest-cov", "coveralls", "futures", "pytest-benchmark", "mock"] +test = ["coveralls", "futures", "mock", "pytest (>=2.7.3)", "pytest-benchmark", "pytest-cov"] [[package]] name = "protobuf" @@ -455,6 +779,32 @@ description = "Protocol Buffers" category = "main" optional = false python-versions = ">=3.5" +files = [ + {file = "protobuf-3.19.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d80f80eb175bf5f1169139c2e0c5ada98b1c098e2b3c3736667f28cbbea39fc8"}, + {file = "protobuf-3.19.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:a529e7df52204565bcd33738a7a5f288f3d2d37d86caa5d78c458fa5fabbd54d"}, + {file = "protobuf-3.19.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28ccea56d4dc38d35cd70c43c2da2f40ac0be0a355ef882242e8586c6d66666f"}, + {file = "protobuf-3.19.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8b30a7de128c46b5ecb343917d9fa737612a6e8280f440874e5cc2ba0d79b8f6"}, + {file = "protobuf-3.19.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5935c8ce02e3d89c7900140a8a42b35bc037ec07a6aeb61cc108be8d3c9438a6"}, + {file = "protobuf-3.19.1-cp36-cp36m-win32.whl", hash = "sha256:74f33edeb4f3b7ed13d567881da8e5a92a72b36495d57d696c2ea1ae0cfee80c"}, + {file = "protobuf-3.19.1-cp36-cp36m-win_amd64.whl", hash = "sha256:038daf4fa38a7e818dd61f51f22588d61755160a98db087a046f80d66b855942"}, + {file = "protobuf-3.19.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e51561d72efd5bd5c91490af1f13e32bcba8dab4643761eb7de3ce18e64a853"}, + {file = "protobuf-3.19.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:6e8ea9173403219239cdfd8d946ed101f2ab6ecc025b0fda0c6c713c35c9981d"}, + {file = "protobuf-3.19.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db3532d9f7a6ebbe2392041350437953b6d7a792de10e629c1e4f5a6b1fe1ac6"}, + {file = "protobuf-3.19.1-cp37-cp37m-win32.whl", hash = "sha256:615b426a177780ce381ecd212edc1e0f70db8557ed72560b82096bd36b01bc04"}, + {file = "protobuf-3.19.1-cp37-cp37m-win_amd64.whl", hash = "sha256:d8919368410110633717c406ab5c97e8df5ce93020cfcf3012834f28b1fab1ea"}, + {file = "protobuf-3.19.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:71b0250b0cfb738442d60cab68abc166de43411f2a4f791d31378590bfb71bd7"}, + {file = "protobuf-3.19.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:3cd0458870ea7d1c58e948ac8078f6ba8a7ecc44a57e03032ed066c5bb318089"}, + {file = "protobuf-3.19.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:655264ed0d0efe47a523e2255fc1106a22f6faab7cc46cfe99b5bae085c2a13e"}, + {file = "protobuf-3.19.1-cp38-cp38-win32.whl", hash = "sha256:b691d996c6d0984947c4cf8b7ae2fe372d99b32821d0584f0b90277aa36982d3"}, + {file = "protobuf-3.19.1-cp38-cp38-win_amd64.whl", hash = "sha256:e7e8d2c20921f8da0dea277dfefc6abac05903ceac8e72839b2da519db69206b"}, + {file = "protobuf-3.19.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fd390367fc211cc0ffcf3a9e149dfeca78fecc62adb911371db0cec5c8b7472d"}, + {file = "protobuf-3.19.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d83e1ef8cb74009bebee3e61cc84b1c9cd04935b72bca0cbc83217d140424995"}, + {file = "protobuf-3.19.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36d90676d6f426718463fe382ec6274909337ca6319d375eebd2044e6c6ac560"}, + {file = "protobuf-3.19.1-cp39-cp39-win32.whl", hash = "sha256:e7b24c11df36ee8e0c085e5b0dc560289e4b58804746fb487287dda51410f1e2"}, + {file = "protobuf-3.19.1-cp39-cp39-win_amd64.whl", hash = "sha256:77d2fadcf369b3f22859ab25bd12bb8e98fb11e05d9ff9b7cd45b711c719c002"}, + {file = "protobuf-3.19.1-py2.py3-none-any.whl", hash = "sha256:e813b1c9006b6399308e917ac5d298f345d95bb31f46f02b60cd92970a9afa17"}, + {file = "protobuf-3.19.1.tar.gz", hash = "sha256:62a8e4baa9cb9e064eb62d1002eca820857ab2138440cb4b3ea4243830f94ca7"}, +] [[package]] name = "py" @@ -463,6 +813,10 @@ description = "library with cross-python path, ini-parsing, io, code, log facili category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, +] [[package]] name = "pycryptodome" @@ -471,15 +825,51 @@ description = "Cryptographic library for Python" category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[[package]] -name = "pyparsing" -version = "3.0.6" -description = "Python parsing module" -category = "dev" -optional = false -python-versions = ">=3.6" - +files = [ + {file = "pycryptodome-3.12.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:90ad3381ccdc6a24cc2841e295706a168f32abefe64c679695712acac71fd5da"}, + {file = "pycryptodome-3.12.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e80f7469b0b3ea0f694230477d8501dc5a30a717e94fddd4821e6721f3053eae"}, + {file = "pycryptodome-3.12.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:b91404611767a7485837a6f1fd20cf9a5ae0ad362040a022cd65827ecb1b0d00"}, + {file = "pycryptodome-3.12.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:db66ccda65d5d20c17b00768e462a86f6f540f9aea8419a7f76cc7d9effd82cd"}, + {file = "pycryptodome-3.12.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:dc88355c4b261ed259268e65705b28b44d99570337694d593f06e3b1698eaaf3"}, + {file = "pycryptodome-3.12.0-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:6f8f5b7b53516da7511951910ab458e799173722c91fea54e2ba2f56d102e4aa"}, + {file = "pycryptodome-3.12.0-cp27-cp27m-win32.whl", hash = "sha256:93acad54a72d81253242eb0a15064be559ec9d989e5173286dc21cad19f01765"}, + {file = "pycryptodome-3.12.0-cp27-cp27m-win_amd64.whl", hash = "sha256:5a8c24d39d4a237dbfe181ea6593792bf9b5582c7fcfa7b8e0e12fda5eec07af"}, + {file = "pycryptodome-3.12.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:32d15da81959faea6cbed95df2bb44f7f796211c110cf90b5ad3b2aeeb97fc8e"}, + {file = "pycryptodome-3.12.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:aed7eb4b64c600fbc5e6d4238991ad1b4179a558401f203d1fcbd24883748982"}, + {file = "pycryptodome-3.12.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:341c6bbf932c406b4f3ee2372e8589b67ac0cf4e99e7dc081440f43a3cde9f0f"}, + {file = "pycryptodome-3.12.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:de0b711d673904dd6c65307ead36cb76622365a393569bf880895cba21195b7a"}, + {file = "pycryptodome-3.12.0-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:3558616f45d8584aee3eba27559bc6fd0ba9be6c076610ed3cc62bd5229ffdc3"}, + {file = "pycryptodome-3.12.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:a78e4324e566b5fbc2b51e9240950d82fa9e1c7eb77acdf27f58712f65622c1d"}, + {file = "pycryptodome-3.12.0-cp35-abi3-manylinux1_i686.whl", hash = "sha256:3f2f3dd596c6128d91314e60a6bcf4344610ef0e97f4ae4dd1770f86dd0748d8"}, + {file = "pycryptodome-3.12.0-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:e05f994f30f1cda3cbe57441f41220d16731cf99d868bb02a8f6484c454c206b"}, + {file = "pycryptodome-3.12.0-cp35-abi3-manylinux2010_i686.whl", hash = "sha256:4cded12e13785bbdf4ba1ff5fb9d261cd98162145f869e4fbc4a4b9083392f0b"}, + {file = "pycryptodome-3.12.0-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:1181c90d1a6aee68a84826825548d0db1b58d8541101f908d779d601d1690586"}, + {file = "pycryptodome-3.12.0-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:6bb0d340c93bcb674ea8899e2f6408ec64c6c21731a59481332b4b2a8143cc60"}, + {file = "pycryptodome-3.12.0-cp35-abi3-win32.whl", hash = "sha256:39da5807aa1ff820799c928f745f89432908bf6624b9e981d2d7f9e55d91b860"}, + {file = "pycryptodome-3.12.0-cp35-abi3-win_amd64.whl", hash = "sha256:212c7f7fe11cad9275fbcff50ca977f1c6643f13560d081e7b0f70596df447b8"}, + {file = "pycryptodome-3.12.0-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:b07a4238465eb8c65dd5df2ab8ba6df127e412293c0ed7656c003336f557a100"}, + {file = "pycryptodome-3.12.0-pp27-pypy_73-manylinux1_x86_64.whl", hash = "sha256:a6e1bcd9d5855f1a3c0f8d585f44c81b08f39a02754007f374fb8db9605ba29c"}, + {file = "pycryptodome-3.12.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:aceb1d217c3a025fb963849071446cf3aca1353282fe1c3cb7bd7339a4d47947"}, + {file = "pycryptodome-3.12.0-pp27-pypy_73-win32.whl", hash = "sha256:f699360ae285fcae9c8f53ca6acf33796025a82bb0ccd7c1c551b04c1726def3"}, + {file = "pycryptodome-3.12.0-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d845c587ceb82ac7cbac7d0bf8c62a1a0fe7190b028b322da5ca65f6e5a18b9e"}, + {file = "pycryptodome-3.12.0-pp36-pypy36_pp73-manylinux1_x86_64.whl", hash = "sha256:d8083de50f6dec56c3c6f270fb193590999583a1b27c9c75bc0b5cac22d438cc"}, + {file = "pycryptodome-3.12.0-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:9ea2f6674c803602a7c0437fccdc2ea036707e60456974fe26ca263bd501ec45"}, + {file = "pycryptodome-3.12.0-pp36-pypy36_pp73-win32.whl", hash = "sha256:5d4264039a2087977f50072aaff2346d1c1c101cb359f9444cf92e3d1f42b4cd"}, + {file = "pycryptodome-3.12.0.zip", hash = "sha256:12c7343aec5a3b3df5c47265281b12b611f26ec9367b6129199d67da54b768c1"}, +] + +[[package]] +name = "pyparsing" +version = "3.0.6" +description = "Python parsing module" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pyparsing-3.0.6-py3-none-any.whl", hash = "sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4"}, + {file = "pyparsing-3.0.6.tar.gz", hash = "sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81"}, +] + [package.extras] diagrams = ["jinja2", "railroad-diagrams"] @@ -490,6 +880,29 @@ description = "Persistent/Functional/Immutable data structures" category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "pyrsistent-0.18.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f4c8cabb46ff8e5d61f56a037974228e978f26bfefce4f61a4b1ac0ba7a2ab72"}, + {file = "pyrsistent-0.18.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:da6e5e818d18459fa46fac0a4a4e543507fe1110e808101277c5a2b5bab0cd2d"}, + {file = "pyrsistent-0.18.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5e4395bbf841693eaebaa5bb5c8f5cdbb1d139e07c975c682ec4e4f8126e03d2"}, + {file = "pyrsistent-0.18.0-cp36-cp36m-win32.whl", hash = "sha256:527be2bfa8dc80f6f8ddd65242ba476a6c4fb4e3aedbf281dfbac1b1ed4165b1"}, + {file = "pyrsistent-0.18.0-cp36-cp36m-win_amd64.whl", hash = "sha256:2aaf19dc8ce517a8653746d98e962ef480ff34b6bc563fc067be6401ffb457c7"}, + {file = "pyrsistent-0.18.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58a70d93fb79dc585b21f9d72487b929a6fe58da0754fa4cb9f279bb92369396"}, + {file = "pyrsistent-0.18.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:4916c10896721e472ee12c95cdc2891ce5890898d2f9907b1b4ae0f53588b710"}, + {file = "pyrsistent-0.18.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:73ff61b1411e3fb0ba144b8f08d6749749775fe89688093e1efef9839d2dcc35"}, + {file = "pyrsistent-0.18.0-cp37-cp37m-win32.whl", hash = "sha256:b29b869cf58412ca5738d23691e96d8aff535e17390128a1a52717c9a109da4f"}, + {file = "pyrsistent-0.18.0-cp37-cp37m-win_amd64.whl", hash = "sha256:097b96f129dd36a8c9e33594e7ebb151b1515eb52cceb08474c10a5479e799f2"}, + {file = "pyrsistent-0.18.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:772e94c2c6864f2cd2ffbe58bb3bdefbe2a32afa0acb1a77e472aac831f83427"}, + {file = "pyrsistent-0.18.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:c1a9ff320fa699337e05edcaae79ef8c2880b52720bc031b219e5b5008ebbdef"}, + {file = "pyrsistent-0.18.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:cd3caef37a415fd0dae6148a1b6957a8c5f275a62cca02e18474608cb263640c"}, + {file = "pyrsistent-0.18.0-cp38-cp38-win32.whl", hash = "sha256:e79d94ca58fcafef6395f6352383fa1a76922268fa02caa2272fff501c2fdc78"}, + {file = "pyrsistent-0.18.0-cp38-cp38-win_amd64.whl", hash = "sha256:a0c772d791c38bbc77be659af29bb14c38ced151433592e326361610250c605b"}, + {file = "pyrsistent-0.18.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d5ec194c9c573aafaceebf05fc400656722793dac57f254cd4741f3c27ae57b4"}, + {file = "pyrsistent-0.18.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:6b5eed00e597b5b5773b4ca30bd48a5774ef1e96f2a45d105db5b4ebb4bca680"}, + {file = "pyrsistent-0.18.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:48578680353f41dca1ca3dc48629fb77dfc745128b56fc01096b2530c13fd426"}, + {file = "pyrsistent-0.18.0-cp39-cp39-win32.whl", hash = "sha256:f3ef98d7b76da5eb19c37fda834d50262ff9167c65658d1d8f974d2e4d90676b"}, + {file = "pyrsistent-0.18.0-cp39-cp39-win_amd64.whl", hash = "sha256:404e1f1d254d314d55adb8d87f4f465c8693d6f902f67eb6ef5b4526dc58e6ea"}, + {file = "pyrsistent-0.18.0.tar.gz", hash = "sha256:773c781216f8c2900b42a7b638d5b517bb134ae1acbebe4d1e8f1f41ea60eb4b"}, +] [[package]] name = "pytest" @@ -498,12 +911,15 @@ description = "pytest: simple powerful testing with Python" category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, + {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, +] [package.dependencies] atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} attrs = ">=19.2.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} iniconfig = "*" packaging = "*" pluggy = ">=0.12,<2.0" @@ -520,6 +936,68 @@ description = "Python for Window Extensions" category = "main" optional = false python-versions = "*" +files = [ + {file = "pywin32-302-cp310-cp310-win32.whl", hash = "sha256:251b7a9367355ccd1a4cd69cd8dd24bd57b29ad83edb2957cfa30f7ed9941efa"}, + {file = "pywin32-302-cp310-cp310-win_amd64.whl", hash = "sha256:79cf7e6ddaaf1cd47a9e50cc74b5d770801a9db6594464137b1b86aa91edafcc"}, + {file = "pywin32-302-cp36-cp36m-win32.whl", hash = "sha256:fe21c2fb332d03dac29de070f191bdbf14095167f8f2165fdc57db59b1ecc006"}, + {file = "pywin32-302-cp36-cp36m-win_amd64.whl", hash = "sha256:d3761ab4e8c5c2dbc156e2c9ccf38dd51f936dc77e58deb940ffbc4b82a30528"}, + {file = "pywin32-302-cp37-cp37m-win32.whl", hash = "sha256:48dd4e348f1ee9538dd4440bf201ea8c110ea6d9f3a5010d79452e9fa80480d9"}, + {file = "pywin32-302-cp37-cp37m-win_amd64.whl", hash = "sha256:496df89f10c054c9285cc99f9d509e243f4e14ec8dfc6d78c9f0bf147a893ab1"}, + {file = "pywin32-302-cp38-cp38-win32.whl", hash = "sha256:e372e477d938a49266136bff78279ed14445e00718b6c75543334351bf535259"}, + {file = "pywin32-302-cp38-cp38-win_amd64.whl", hash = "sha256:543552e66936378bd2d673c5a0a3d9903dba0b0a87235ef0c584f058ceef5872"}, + {file = "pywin32-302-cp39-cp39-win32.whl", hash = "sha256:2393c1a40dc4497fd6161b76801b8acd727c5610167762b7c3e9fd058ef4a6ab"}, + {file = "pywin32-302-cp39-cp39-win_amd64.whl", hash = "sha256:af5aea18167a31efcacc9f98a2ca932c6b6a6d91ebe31f007509e293dea12580"}, +] + +[[package]] +name = "pyyaml" +version = "6.0" +description = "YAML parser and emitter for Python" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, + {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, + {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, + {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, + {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, + {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, + {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, + {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, + {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, + {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, + {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, + {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, + {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, + {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, + {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, + {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, + {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, +] [[package]] name = "requests" @@ -528,6 +1006,10 @@ description = "Python HTTP for Humans." category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, + {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, +] [package.dependencies] certifi = ">=2017.4.17" @@ -536,7 +1018,7 @@ idna = ">=2.5,<3" urllib3 = ">=1.21.1,<1.27" [package.extras] -security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] +security = ["cryptography (>=1.3.4)", "pyOpenSSL (>=0.14)"] socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] [[package]] @@ -546,12 +1028,16 @@ description = "A package for Recursive Length Prefix encoding and decoding" category = "main" optional = false python-versions = "*" +files = [ + {file = "rlp-2.0.1-py2.py3-none-any.whl", hash = "sha256:52a57c9f53f03c88b189283734b397314288250cc4a3c4113e9e36e2ac6bdd16"}, + {file = "rlp-2.0.1.tar.gz", hash = "sha256:665e8312750b3fc5f7002e656d05b9dcb6e93b6063df40d95c49ad90c19d1f0e"}, +] [package.dependencies] eth-utils = ">=1.0.2,<2" [package.extras] -dev = ["Sphinx (>=1.6.5,<2)", "bumpversion (>=0.5.3,<1)", "flake8 (==3.4.1)", "hypothesis (==5.19.0)", "ipython", "pytest-watch (>=4.1.0,<5)", "pytest-xdist", "pytest (==5.4.3)", "setuptools (>=36.2.0)", "sphinx-rtd-theme (>=0.1.9)", "tox (>=2.9.1,<3)", "twine", "wheel"] +dev = ["Sphinx (>=1.6.5,<2)", "bumpversion (>=0.5.3,<1)", "flake8 (==3.4.1)", "hypothesis (==5.19.0)", "ipython", "pytest (==5.4.3)", "pytest-watch (>=4.1.0,<5)", "pytest-xdist", "setuptools (>=36.2.0)", "sphinx-rtd-theme (>=0.1.9)", "tox (>=2.9.1,<3)", "twine", "wheel"] doc = ["Sphinx (>=1.6.5,<2)", "sphinx-rtd-theme (>=0.1.9)"] lint = ["flake8 (==3.4.1)"] rust-backend = ["rusty-rlp (>=0.1.15,<0.2)"] @@ -564,6 +1050,27 @@ description = "Reactive Extensions (Rx) for Python" category = "main" optional = false python-versions = "*" +files = [ + {file = "Rx-1.6.1-py2.py3-none-any.whl", hash = "sha256:7357592bc7e881a95e0c2013b73326f704953301ab551fbc8133a6fadab84105"}, + {file = "Rx-1.6.1.tar.gz", hash = "sha256:13a1d8d9e252625c173dc795471e614eadfe1cf40ffc684e08b8fff0d9748c23"}, +] + +[[package]] +name = "setuptools" +version = "65.6.3" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "setuptools-65.6.3-py3-none-any.whl", hash = "sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54"}, + {file = "setuptools-65.6.3.tar.gz", hash = "sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "six" @@ -572,14 +1079,34 @@ description = "Python 2 and 3 compatibility utilities" category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] [[package]] name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" -category = "dev" +category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] [[package]] name = "toolz" @@ -588,14 +1115,22 @@ description = "List processing tools and functional utilities" category = "main" optional = false python-versions = ">=3.5" +files = [ + {file = "toolz-0.11.2-py3-none-any.whl", hash = "sha256:a5700ce83414c64514d82d60bcda8aabfde092d1c1a8663f9200c07fdcc6da8f"}, + {file = "toolz-0.11.2.tar.gz", hash = "sha256:6b312d5e15138552f1bda8a4e66c30e236c831b612b2bf0005f8a1df10a4bc33"}, +] [[package]] name = "typing-extensions" -version = "3.10.0.2" -description = "Backported and Experimental Type Hints for Python 3.5+" +version = "4.4.0" +description = "Backported and Experimental Type Hints for Python 3.7+" category = "main" optional = false -python-versions = "*" +python-versions = ">=3.7" +files = [ + {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, + {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, +] [[package]] name = "urllib3" @@ -604,10 +1139,14 @@ description = "HTTP library with thread-safe connection pooling, file post, and category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +files = [ + {file = "urllib3-1.26.7-py2.py3-none-any.whl", hash = "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"}, + {file = "urllib3-1.26.7.tar.gz", hash = "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece"}, +] [package.extras] brotli = ["brotlipy (>=0.6.0)"] -secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] @@ -617,6 +1156,30 @@ description = "Simple python varint implementation" category = "main" optional = false python-versions = "*" +files = [ + {file = "varint-1.0.2.tar.gz", hash = "sha256:a6ecc02377ac5ee9d65a6a8ad45c9ff1dac8ccee19400a5950fb51d594214ca5"}, +] + +[[package]] +name = "virtualenv" +version = "20.17.1" +description = "Virtual Python Environment builder" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "virtualenv-20.17.1-py3-none-any.whl", hash = "sha256:ce3b1684d6e1a20a3e5ed36795a97dfc6af29bc3970ca8dab93e11ac6094b3c4"}, + {file = "virtualenv-20.17.1.tar.gz", hash = "sha256:f8b927684efc6f1cc206c9db297a570ab9ad0e51c16fa9e45487d36d1905c058"}, +] + +[package.dependencies] +distlib = ">=0.3.6,<1" +filelock = ">=3.4.1,<4" +platformdirs = ">=2.4,<3" + +[package.extras] +docs = ["proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-argparse (>=0.3.2)", "sphinx-rtd-theme (>=1)", "towncrier (>=22.8)"] +testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"] [[package]] name = "web3" @@ -625,6 +1188,10 @@ description = "Web3.py" category = "main" optional = false python-versions = ">=3.6,<4" +files = [ + {file = "web3-5.19.0-py3-none-any.whl", hash = "sha256:3e6158edac8756c6a3d874ae23ce6eb9e53d0d250ab6a5425a869e1e568bf550"}, + {file = "web3-5.19.0.tar.gz", hash = "sha256:e120decb8e1db6d7d005a4446756f16b605e0a3d78384587fd2dab6338654751"}, +] [package.dependencies] eth-abi = ">=2.0.0b6,<3.0.0" @@ -639,12 +1206,11 @@ lru-dict = ">=1.1.6,<2.0.0" protobuf = ">=3.10.0,<4" pywin32 = {version = ">=223", markers = "platform_system == \"Windows\""} requests = ">=2.16.0,<3.0.0" -typing-extensions = {version = ">=3.7.4.1,<4", markers = "python_version < \"3.8\""} websockets = ">=8.1.0,<9.0.0" [package.extras] -dev = ["eth-tester[py-evm] (==v0.5.0-beta.4)", "py-geth (>=3.0.0,<4)", "flake8 (==3.8.3)", "isort (>=4.2.15,<4.3.5)", "mypy (==0.730)", "mock", "sphinx-better-theme (>=0.1.4)", "click (>=5.1)", "configparser (==3.5.0)", "contextlib2 (>=0.5.4)", "py-geth (>=2.4.0,<3)", "py-solc (>=0.4.0)", "pytest (>=4.4.0,<5.0.0)", "sphinx (>=2.4.4,<3)", "sphinx-rtd-theme (>=0.1.9)", "toposort (>=1.4)", "towncrier (>=19.2.0,<20)", "urllib3", "web3 (>=2.1.0)", "wheel", "bumpversion", "flaky (>=3.3.0)", "hypothesis (>=3.31.2,<6)", "pytest-asyncio (>=0.10.0,<0.11)", "pytest-mock (>=1.10,<2)", "pytest-pythonpath (>=0.3)", "pytest-watch (>=4.2,<5)", "pytest-xdist (>=1.29,<2)", "setuptools (>=38.6.0)", "tox (>=1.8.0)", "tqdm (>4.32,<5)", "twine (>=1.13,<2)", "when-changed (>=0.3.0,<0.4)"] -docs = ["mock", "sphinx-better-theme (>=0.1.4)", "click (>=5.1)", "configparser (==3.5.0)", "contextlib2 (>=0.5.4)", "py-geth (>=2.4.0,<3)", "py-solc (>=0.4.0)", "pytest (>=4.4.0,<5.0.0)", "sphinx (>=2.4.4,<3)", "sphinx-rtd-theme (>=0.1.9)", "toposort (>=1.4)", "towncrier (>=19.2.0,<20)", "urllib3", "web3 (>=2.1.0)", "wheel"] +dev = ["bumpversion", "click (>=5.1)", "configparser (==3.5.0)", "contextlib2 (>=0.5.4)", "eth-tester[py-evm] (==v0.5.0-beta.4)", "flake8 (==3.8.3)", "flaky (>=3.3.0)", "hypothesis (>=3.31.2,<6)", "isort (>=4.2.15,<4.3.5)", "mock", "mypy (==0.730)", "py-geth (>=2.4.0,<3)", "py-geth (>=3.0.0,<4)", "py-solc (>=0.4.0)", "pytest (>=4.4.0,<5.0.0)", "pytest-asyncio (>=0.10.0,<0.11)", "pytest-mock (>=1.10,<2)", "pytest-pythonpath (>=0.3)", "pytest-watch (>=4.2,<5)", "pytest-xdist (>=1.29,<2)", "setuptools (>=38.6.0)", "sphinx (>=2.4.4,<3)", "sphinx-better-theme (>=0.1.4)", "sphinx-rtd-theme (>=0.1.9)", "toposort (>=1.4)", "towncrier (>=19.2.0,<20)", "tox (>=1.8.0)", "tqdm (>4.32,<5)", "twine (>=1.13,<2)", "urllib3", "web3 (>=2.1.0)", "wheel", "when-changed (>=0.3.0,<0.4)"] +docs = ["click (>=5.1)", "configparser (==3.5.0)", "contextlib2 (>=0.5.4)", "mock", "py-geth (>=2.4.0,<3)", "py-solc (>=0.4.0)", "pytest (>=4.4.0,<5.0.0)", "sphinx (>=2.4.4,<3)", "sphinx-better-theme (>=0.1.4)", "sphinx-rtd-theme (>=0.1.9)", "toposort (>=1.4)", "towncrier (>=19.2.0,<20)", "urllib3", "web3 (>=2.1.0)", "wheel"] linter = ["flake8 (==3.8.3)", "isort (>=4.2.15,<4.3.5)", "mypy (==0.730)"] tester = ["eth-tester[py-evm] (==v0.5.0-beta.4)", "py-geth (>=3.0.0,<4)"] @@ -655,338 +1221,7 @@ description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" category = "main" optional = false python-versions = ">=3.6.1" - -[[package]] -name = "zipp" -version = "3.6.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] - -[metadata] -lock-version = "1.1" -python-versions = ">=3.7.2, <4.0" -content-hash = "b3da6e91385997ef75f59da8e3b9a76139cbb473bacf9ea6f308bad78e103585" - -[metadata.files] -atomicwrites = [ - {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, - {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, -] -attrs = [ - {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, - {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, -] -base58 = [ - {file = "base58-2.1.1-py3-none-any.whl", hash = "sha256:11a36f4d3ce51dfc1043f3218591ac4eb1ceb172919cebe05b52a5bcc8d245c2"}, - {file = "base58-2.1.1.tar.gz", hash = "sha256:c5d0cb3f5b6e81e8e35da5754388ddcc6d0d14b6c6a132cb93d69ed580a7278c"}, -] -bitarray = [ - {file = "bitarray-1.2.2.tar.gz", hash = "sha256:27a69ffcee3b868abab3ce8b17c69e02b63e722d4d64ffd91d659f81e9984954"}, -] -certifi = [ - {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, - {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, -] -chardet = [ - {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, - {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, -] -colorama = [ - {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, - {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, -] -cython = [ - {file = "Cython-0.29.24-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:6a2cf2ccccc25413864928dfd730c29db6f63eaf98206c1e600003a445ca7f58"}, - {file = "Cython-0.29.24-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b28f92e617f540d3f21f8fd479a9c6491be920ffff672a4c61b7fc4d7f749f39"}, - {file = "Cython-0.29.24-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:37bcfa5df2a3009f49624695d917c3804fccbdfcdc5eda6378754a879711a4d5"}, - {file = "Cython-0.29.24-cp27-cp27m-win32.whl", hash = "sha256:9164aeef1af6f837e4fc20402a31d256188ba4d535e262c6cb78caf57ad744f8"}, - {file = "Cython-0.29.24-cp27-cp27m-win_amd64.whl", hash = "sha256:73ac33a4379056a02031baa4def255717fadb9181b5ac2b244792d53eae1c925"}, - {file = "Cython-0.29.24-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:09ac3087ac7a3d489ebcb3fb8402e00c13d1a3a1c6bc73fd3b0d756a3e341e79"}, - {file = "Cython-0.29.24-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:774cb8fd931ee1ba52c472bc1c19077cd6895c1b24014ae07bb27df59aed5ebe"}, - {file = "Cython-0.29.24-cp34-cp34m-win32.whl", hash = "sha256:5dd56d0be50073f0e54825a8bc3393852de0eed126339ecbca0ae149dba55cfc"}, - {file = "Cython-0.29.24-cp34-cp34m-win_amd64.whl", hash = "sha256:88dc3c250dec280b0489a83950b15809762e27232f4799b1b8d0bad503f5ab84"}, - {file = "Cython-0.29.24-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:5fa12ebafc2f688ea6d26ab6d1d2e634a9872509ba7135b902bb0d8b368fb04b"}, - {file = "Cython-0.29.24-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:60c958bcab0ff315b4036a949bed1c65334e1f6a69e17e9966d742febb59043a"}, - {file = "Cython-0.29.24-cp35-cp35m-win32.whl", hash = "sha256:166f9f29cd0058ce1a14a7b3a2458b849ed34b1ec5fd4108af3fdd2c24afcbb0"}, - {file = "Cython-0.29.24-cp35-cp35m-win_amd64.whl", hash = "sha256:76cbca0188d278e93d12ebdaf5990678e6e436485fdfad49dbe9b07717d41a3c"}, - {file = "Cython-0.29.24-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f2e9381497b12e8f622af620bde0d1d094035d79b899abb2ddd3a7891f535083"}, - {file = "Cython-0.29.24-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:d8d1a087f35e39384303f5e6b75d465d6f29d746d7138eae9d3b6e8e6f769eae"}, - {file = "Cython-0.29.24-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:112efa54a58293a4fb0acf0dd8e5b3736e95b595eee24dd88615648e445abe41"}, - {file = "Cython-0.29.24-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cf4452f0e4d50e11701bca38f3857fe6fa16593e7fd6a4d5f7be66f611b7da2"}, - {file = "Cython-0.29.24-cp36-cp36m-win32.whl", hash = "sha256:854fe2193d3ad4c8b61932ff54d6dbe10c5fa8749eb8958d72cc0ab28243f833"}, - {file = "Cython-0.29.24-cp36-cp36m-win_amd64.whl", hash = "sha256:84826ec1c11cda56261a252ddecac0c7d6b02e47e81b94f40b27b4c23c29c17c"}, - {file = "Cython-0.29.24-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6ade74eece909fd3a437d9a5084829180751d7ade118e281e9824dd75eafaff2"}, - {file = "Cython-0.29.24-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0a142c6b862e6ed6b02209d543062c038c110585b5e32d1ad7c9717af4f07e41"}, - {file = "Cython-0.29.24-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:10cb3def9774fa99e4583617a5616874aed3255dc241fd1f4a3c2978c78e1c53"}, - {file = "Cython-0.29.24-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f41ef7edd76dd23315925e003f0c58c8585f3ab24be6885c4b3f60e77c82746"}, - {file = "Cython-0.29.24-cp37-cp37m-win32.whl", hash = "sha256:821c2d416ad7d006b069657ee1034c0e0cb45bdbe9ab6ab631e8c495dfcfa4ac"}, - {file = "Cython-0.29.24-cp37-cp37m-win_amd64.whl", hash = "sha256:2d9e61ed1056a3b6a4b9156b62297ad18b357a7948e57a2f49b061217696567e"}, - {file = "Cython-0.29.24-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:55b0ee28c2c8118bfb3ad9b25cf7a6cbd724e442ea96956e32ccd908d5e3e043"}, - {file = "Cython-0.29.24-cp38-cp38-manylinux1_i686.whl", hash = "sha256:eb2843f8cc01c645725e6fc690a84e99cdb266ce8ebe427cf3a680ff09f876aa"}, - {file = "Cython-0.29.24-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:661dbdea519d9cfb288867252b75fef73ffa8e8bb674cec27acf70646afb369b"}, - {file = "Cython-0.29.24-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc05de569f811be1fcfde6756c9048ae518f0c4b6d9f8f024752c5365d934cac"}, - {file = "Cython-0.29.24-cp38-cp38-win32.whl", hash = "sha256:a102cfa795c6b3b81a29bdb9dbec545367cd7f353c03e6f30a056fdfefd92854"}, - {file = "Cython-0.29.24-cp38-cp38-win_amd64.whl", hash = "sha256:416046a98255eff97ec02077d20ebeaae52682dfca1c35aadf31260442b92514"}, - {file = "Cython-0.29.24-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ad43e684ade673565f6f9d6638015112f6c7f11aa2a632167b79014f613f0f5f"}, - {file = "Cython-0.29.24-cp39-cp39-manylinux1_i686.whl", hash = "sha256:afb521523cb46ddaa8d269b421f88ea2731fee05e65b952b96d4db760f5a2a1c"}, - {file = "Cython-0.29.24-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:0d414458cb22f8a90d64260da6dace5d5fcebde43f31be52ca51f818c46db8cb"}, - {file = "Cython-0.29.24-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8cb87777e82d1996aef6c146560a19270684271c9c669ba62ac6803b3cd2ff82"}, - {file = "Cython-0.29.24-cp39-cp39-win32.whl", hash = "sha256:91339ee4b465924a3ea4b2a9cec7f7227bc4cadf673ce859d24c2b9ef60b1214"}, - {file = "Cython-0.29.24-cp39-cp39-win_amd64.whl", hash = "sha256:5fb977945a2111f6b64501fdf7ed0ec162cc502b84457fd648d6a558ea8de0d6"}, - {file = "Cython-0.29.24-py2.py3-none-any.whl", hash = "sha256:f96411f0120b5cae483923aaacd2872af8709be4b46522daedc32f051d778385"}, - {file = "Cython-0.29.24.tar.gz", hash = "sha256:cdf04d07c3600860e8c2ebaad4e8f52ac3feb212453c1764a49ac08c827e8443"}, -] -cytoolz = [ - {file = "cytoolz-0.11.2.tar.gz", hash = "sha256:ea23663153806edddce7e4153d1d407d62357c05120a4e8485bddf1bd5ab22b4"}, -] -eth-abi = [ - {file = "eth_abi-2.1.1-py3-none-any.whl", hash = "sha256:78df5d2758247a8f0766a7cfcea4575bcfe568c34a33e6d05a72c328a9040444"}, - {file = "eth_abi-2.1.1.tar.gz", hash = "sha256:4bb1d87bb6605823379b07f6c02c8af45df01a27cc85bd6abb7cf1446ce7d188"}, -] -eth-account = [ - {file = "eth-account-0.5.6.tar.gz", hash = "sha256:baef80956e88af5643f8602e72aab6bcd91d8a9f71dd03c7a7f1145f5e6fd694"}, - {file = "eth_account-0.5.6-py3-none-any.whl", hash = "sha256:d324daf5a40bd5bdaf5ddaebfec71e7440b21f9ae4989921ce1253d63f8fe436"}, -] -eth-hash = [ - {file = "eth-hash-0.3.2.tar.gz", hash = "sha256:3f40cecd5ead88184aa9550afc19d057f103728108c5102f592f8415949b5a76"}, - {file = "eth_hash-0.3.2-py3-none-any.whl", hash = "sha256:de7385148a8e0237ba1240cddbc06d53f56731140f8593bdb8429306f6b42271"}, -] -eth-keyfile = [ - {file = "eth-keyfile-0.5.1.tar.gz", hash = "sha256:939540efb503380bc30d926833e6a12b22c6750de80feef3720d79e5a79de47d"}, - {file = "eth_keyfile-0.5.1-py3-none-any.whl", hash = "sha256:70d734af17efdf929a90bb95375f43522be4ed80c3b9e0a8bca575fb11cd1159"}, -] -eth-keys = [ - {file = "eth-keys-0.3.3.tar.gz", hash = "sha256:a9a1e83e443bd369265b1a1b66dc30f6841bdbb3577ecd042e037b7b405b6cb0"}, - {file = "eth_keys-0.3.3-py3-none-any.whl", hash = "sha256:412dd5c9732b8e92af40c9c77597f4661c57eba3897aaa55e527af56a8c5ab47"}, -] -eth-rlp = [ - {file = "eth-rlp-0.2.1.tar.gz", hash = "sha256:f016f980b0ed42ee7650ba6e4e4d3c4e9aa06d8b9c6825a36d3afe5aa0187a8b"}, - {file = "eth_rlp-0.2.1-py3-none-any.whl", hash = "sha256:cc389ef8d7b6f76a98f90bcdbff1b8684b3a78f53d47e871191b50d4d6aee5a1"}, -] -eth-typing = [ - {file = "eth-typing-2.2.2.tar.gz", hash = "sha256:97ba0f83da7cf1d3668f6ed54983f21168076c552762bf5e06d4a20921877f3f"}, - {file = "eth_typing-2.2.2-py3-none-any.whl", hash = "sha256:1140c7592321dbf10d6663c46f7e43eb0e6410b011b03f14b3df3eb1f76aa9bb"}, -] -eth-utils = [ - {file = "eth-utils-1.10.0.tar.gz", hash = "sha256:bf82762a46978714190b0370265a7148c954d3f0adaa31c6f085ea375e4c61af"}, - {file = "eth_utils-1.10.0-py3-none-any.whl", hash = "sha256:74240a8c6f652d085ed3c85f5f1654203d2f10ff9062f83b3bad0a12ff321c7a"}, -] -gql = [ - {file = "gql-2.0.0-py2.py3-none-any.whl", hash = "sha256:35032ddd4bfe6b8f3169f806b022168932385d751eacc5c5f7122e0b3f4d6b88"}, - {file = "gql-2.0.0.tar.gz", hash = "sha256:fe8d3a08047f77362ddfcfddba7cae377da2dd66f5e61c59820419c9283d4fb5"}, -] -graphql-core = [ - {file = "graphql-core-2.3.2.tar.gz", hash = "sha256:aac46a9ac524c9855910c14c48fc5d60474def7f99fd10245e76608eba7af746"}, - {file = "graphql_core-2.3.2-py2.py3-none-any.whl", hash = "sha256:44c9bac4514e5e30c5a595fac8e3c76c1975cae14db215e8174c7fe995825bad"}, -] -hexbytes = [ - {file = "hexbytes-0.2.2-py3-none-any.whl", hash = "sha256:ef53c37ea9f316fff86fcb1df057b4c6ba454da348083e972031bbf7bc9c3acc"}, - {file = "hexbytes-0.2.2.tar.gz", hash = "sha256:a5881304d186e87578fb263a85317c808cf130e1d4b3d37d30142ab0f7898d03"}, -] -idna = [ - {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, - {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, -] -importlib-metadata = [ - {file = "importlib_metadata-4.8.2-py3-none-any.whl", hash = "sha256:53ccfd5c134223e497627b9815d5030edf77d2ed573922f7a0b8f8bb81a1c100"}, - {file = "importlib_metadata-4.8.2.tar.gz", hash = "sha256:75bdec14c397f528724c1bfd9709d660b33a4d2e77387a3358f20b848bb5e5fb"}, -] -iniconfig = [ - {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, - {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, -] -ipfshttpclient = [ - {file = "ipfshttpclient-0.7.0a1-py3-none-any.whl", hash = "sha256:a45fb0ef087d71647c77b02e0cb3a033045377f134971dfcdc1c6f21cd8ad87c"}, - {file = "ipfshttpclient-0.7.0a1.tar.gz", hash = "sha256:ada7d7c40879ebf8a736c1ff7c690ddb574a120c2226dc982d44156408de426a"}, -] -jsonschema = [ - {file = "jsonschema-3.2.0-py2.py3-none-any.whl", hash = "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163"}, - {file = "jsonschema-3.2.0.tar.gz", hash = "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a"}, -] -jstyleson = [ - {file = "jstyleson-0.0.2.tar.gz", hash = "sha256:680003f3b15a2959e4e6a351f3b858e3c07dd3e073a0d54954e34d8ea5e1308e"}, -] -lru-dict = [ - {file = "lru-dict-1.1.7.tar.gz", hash = "sha256:45b81f67d75341d4433abade799a47e9c42a9e22a118531dcb5e549864032d7c"}, -] -multiaddr = [ - {file = "multiaddr-0.0.9-py2.py3-none-any.whl", hash = "sha256:5c0f862cbcf19aada2a899f80ef896ddb2e85614e0c8f04dd287c06c69dac95b"}, - {file = "multiaddr-0.0.9.tar.gz", hash = "sha256:30b2695189edc3d5b90f1c303abb8f02d963a3a4edf2e7178b975eb417ab0ecf"}, -] -multicall = [ - {file = "multicall-0.1.3-py3-none-any.whl", hash = "sha256:775702b52ac16939f8d9b29921957c98826e76ae6007f05095cca3fd1be82cde"}, - {file = "multicall-0.1.3.tar.gz", hash = "sha256:94ee3d117667bb9bc5be53a1304e05216b78620128d1029621dcef7f91990f62"}, -] -netaddr = [ - {file = "netaddr-0.8.0-py2.py3-none-any.whl", hash = "sha256:9666d0232c32d2656e5e5f8d735f58fd6c7457ce52fc21c98d45f2af78f990ac"}, - {file = "netaddr-0.8.0.tar.gz", hash = "sha256:d6cc57c7a07b1d9d2e917aa8b36ae8ce61c35ba3fcd1b83ca31c5a0ee2b5a243"}, -] -packaging = [ - {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, - {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, -] -parsimonious = [ - {file = "parsimonious-0.8.1.tar.gz", hash = "sha256:3add338892d580e0cb3b1a39e4a1b427ff9f687858fdd61097053742391a9f6b"}, -] -pluggy = [ - {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, - {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, -] -promise = [ - {file = "promise-2.3.tar.gz", hash = "sha256:dfd18337c523ba4b6a58801c164c1904a9d4d1b1747c7d5dbf45b693a49d93d0"}, -] -protobuf = [ - {file = "protobuf-3.19.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d80f80eb175bf5f1169139c2e0c5ada98b1c098e2b3c3736667f28cbbea39fc8"}, - {file = "protobuf-3.19.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:a529e7df52204565bcd33738a7a5f288f3d2d37d86caa5d78c458fa5fabbd54d"}, - {file = "protobuf-3.19.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28ccea56d4dc38d35cd70c43c2da2f40ac0be0a355ef882242e8586c6d66666f"}, - {file = "protobuf-3.19.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8b30a7de128c46b5ecb343917d9fa737612a6e8280f440874e5cc2ba0d79b8f6"}, - {file = "protobuf-3.19.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5935c8ce02e3d89c7900140a8a42b35bc037ec07a6aeb61cc108be8d3c9438a6"}, - {file = "protobuf-3.19.1-cp36-cp36m-win32.whl", hash = "sha256:74f33edeb4f3b7ed13d567881da8e5a92a72b36495d57d696c2ea1ae0cfee80c"}, - {file = "protobuf-3.19.1-cp36-cp36m-win_amd64.whl", hash = "sha256:038daf4fa38a7e818dd61f51f22588d61755160a98db087a046f80d66b855942"}, - {file = "protobuf-3.19.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e51561d72efd5bd5c91490af1f13e32bcba8dab4643761eb7de3ce18e64a853"}, - {file = "protobuf-3.19.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:6e8ea9173403219239cdfd8d946ed101f2ab6ecc025b0fda0c6c713c35c9981d"}, - {file = "protobuf-3.19.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db3532d9f7a6ebbe2392041350437953b6d7a792de10e629c1e4f5a6b1fe1ac6"}, - {file = "protobuf-3.19.1-cp37-cp37m-win32.whl", hash = "sha256:615b426a177780ce381ecd212edc1e0f70db8557ed72560b82096bd36b01bc04"}, - {file = "protobuf-3.19.1-cp37-cp37m-win_amd64.whl", hash = "sha256:d8919368410110633717c406ab5c97e8df5ce93020cfcf3012834f28b1fab1ea"}, - {file = "protobuf-3.19.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:71b0250b0cfb738442d60cab68abc166de43411f2a4f791d31378590bfb71bd7"}, - {file = "protobuf-3.19.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:3cd0458870ea7d1c58e948ac8078f6ba8a7ecc44a57e03032ed066c5bb318089"}, - {file = "protobuf-3.19.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:655264ed0d0efe47a523e2255fc1106a22f6faab7cc46cfe99b5bae085c2a13e"}, - {file = "protobuf-3.19.1-cp38-cp38-win32.whl", hash = "sha256:b691d996c6d0984947c4cf8b7ae2fe372d99b32821d0584f0b90277aa36982d3"}, - {file = "protobuf-3.19.1-cp38-cp38-win_amd64.whl", hash = "sha256:e7e8d2c20921f8da0dea277dfefc6abac05903ceac8e72839b2da519db69206b"}, - {file = "protobuf-3.19.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fd390367fc211cc0ffcf3a9e149dfeca78fecc62adb911371db0cec5c8b7472d"}, - {file = "protobuf-3.19.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d83e1ef8cb74009bebee3e61cc84b1c9cd04935b72bca0cbc83217d140424995"}, - {file = "protobuf-3.19.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36d90676d6f426718463fe382ec6274909337ca6319d375eebd2044e6c6ac560"}, - {file = "protobuf-3.19.1-cp39-cp39-win32.whl", hash = "sha256:e7b24c11df36ee8e0c085e5b0dc560289e4b58804746fb487287dda51410f1e2"}, - {file = "protobuf-3.19.1-cp39-cp39-win_amd64.whl", hash = "sha256:77d2fadcf369b3f22859ab25bd12bb8e98fb11e05d9ff9b7cd45b711c719c002"}, - {file = "protobuf-3.19.1-py2.py3-none-any.whl", hash = "sha256:e813b1c9006b6399308e917ac5d298f345d95bb31f46f02b60cd92970a9afa17"}, - {file = "protobuf-3.19.1.tar.gz", hash = "sha256:62a8e4baa9cb9e064eb62d1002eca820857ab2138440cb4b3ea4243830f94ca7"}, -] -py = [ - {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, - {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, -] -pycryptodome = [ - {file = "pycryptodome-3.12.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:90ad3381ccdc6a24cc2841e295706a168f32abefe64c679695712acac71fd5da"}, - {file = "pycryptodome-3.12.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e80f7469b0b3ea0f694230477d8501dc5a30a717e94fddd4821e6721f3053eae"}, - {file = "pycryptodome-3.12.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:b91404611767a7485837a6f1fd20cf9a5ae0ad362040a022cd65827ecb1b0d00"}, - {file = "pycryptodome-3.12.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:db66ccda65d5d20c17b00768e462a86f6f540f9aea8419a7f76cc7d9effd82cd"}, - {file = "pycryptodome-3.12.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:dc88355c4b261ed259268e65705b28b44d99570337694d593f06e3b1698eaaf3"}, - {file = "pycryptodome-3.12.0-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:6f8f5b7b53516da7511951910ab458e799173722c91fea54e2ba2f56d102e4aa"}, - {file = "pycryptodome-3.12.0-cp27-cp27m-win32.whl", hash = "sha256:93acad54a72d81253242eb0a15064be559ec9d989e5173286dc21cad19f01765"}, - {file = "pycryptodome-3.12.0-cp27-cp27m-win_amd64.whl", hash = "sha256:5a8c24d39d4a237dbfe181ea6593792bf9b5582c7fcfa7b8e0e12fda5eec07af"}, - {file = "pycryptodome-3.12.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:32d15da81959faea6cbed95df2bb44f7f796211c110cf90b5ad3b2aeeb97fc8e"}, - {file = "pycryptodome-3.12.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:aed7eb4b64c600fbc5e6d4238991ad1b4179a558401f203d1fcbd24883748982"}, - {file = "pycryptodome-3.12.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:341c6bbf932c406b4f3ee2372e8589b67ac0cf4e99e7dc081440f43a3cde9f0f"}, - {file = "pycryptodome-3.12.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:de0b711d673904dd6c65307ead36cb76622365a393569bf880895cba21195b7a"}, - {file = "pycryptodome-3.12.0-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:3558616f45d8584aee3eba27559bc6fd0ba9be6c076610ed3cc62bd5229ffdc3"}, - {file = "pycryptodome-3.12.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:a78e4324e566b5fbc2b51e9240950d82fa9e1c7eb77acdf27f58712f65622c1d"}, - {file = "pycryptodome-3.12.0-cp35-abi3-manylinux1_i686.whl", hash = "sha256:3f2f3dd596c6128d91314e60a6bcf4344610ef0e97f4ae4dd1770f86dd0748d8"}, - {file = "pycryptodome-3.12.0-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:e05f994f30f1cda3cbe57441f41220d16731cf99d868bb02a8f6484c454c206b"}, - {file = "pycryptodome-3.12.0-cp35-abi3-manylinux2010_i686.whl", hash = "sha256:4cded12e13785bbdf4ba1ff5fb9d261cd98162145f869e4fbc4a4b9083392f0b"}, - {file = "pycryptodome-3.12.0-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:1181c90d1a6aee68a84826825548d0db1b58d8541101f908d779d601d1690586"}, - {file = "pycryptodome-3.12.0-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:6bb0d340c93bcb674ea8899e2f6408ec64c6c21731a59481332b4b2a8143cc60"}, - {file = "pycryptodome-3.12.0-cp35-abi3-win32.whl", hash = "sha256:39da5807aa1ff820799c928f745f89432908bf6624b9e981d2d7f9e55d91b860"}, - {file = "pycryptodome-3.12.0-cp35-abi3-win_amd64.whl", hash = "sha256:212c7f7fe11cad9275fbcff50ca977f1c6643f13560d081e7b0f70596df447b8"}, - {file = "pycryptodome-3.12.0-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:b07a4238465eb8c65dd5df2ab8ba6df127e412293c0ed7656c003336f557a100"}, - {file = "pycryptodome-3.12.0-pp27-pypy_73-manylinux1_x86_64.whl", hash = "sha256:a6e1bcd9d5855f1a3c0f8d585f44c81b08f39a02754007f374fb8db9605ba29c"}, - {file = "pycryptodome-3.12.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:aceb1d217c3a025fb963849071446cf3aca1353282fe1c3cb7bd7339a4d47947"}, - {file = "pycryptodome-3.12.0-pp27-pypy_73-win32.whl", hash = "sha256:f699360ae285fcae9c8f53ca6acf33796025a82bb0ccd7c1c551b04c1726def3"}, - {file = "pycryptodome-3.12.0-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d845c587ceb82ac7cbac7d0bf8c62a1a0fe7190b028b322da5ca65f6e5a18b9e"}, - {file = "pycryptodome-3.12.0-pp36-pypy36_pp73-manylinux1_x86_64.whl", hash = "sha256:d8083de50f6dec56c3c6f270fb193590999583a1b27c9c75bc0b5cac22d438cc"}, - {file = "pycryptodome-3.12.0-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:9ea2f6674c803602a7c0437fccdc2ea036707e60456974fe26ca263bd501ec45"}, - {file = "pycryptodome-3.12.0-pp36-pypy36_pp73-win32.whl", hash = "sha256:5d4264039a2087977f50072aaff2346d1c1c101cb359f9444cf92e3d1f42b4cd"}, - {file = "pycryptodome-3.12.0.zip", hash = "sha256:12c7343aec5a3b3df5c47265281b12b611f26ec9367b6129199d67da54b768c1"}, -] -pyparsing = [ - {file = "pyparsing-3.0.6-py3-none-any.whl", hash = "sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4"}, - {file = "pyparsing-3.0.6.tar.gz", hash = "sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81"}, -] -pyrsistent = [ - {file = "pyrsistent-0.18.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f4c8cabb46ff8e5d61f56a037974228e978f26bfefce4f61a4b1ac0ba7a2ab72"}, - {file = "pyrsistent-0.18.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:da6e5e818d18459fa46fac0a4a4e543507fe1110e808101277c5a2b5bab0cd2d"}, - {file = "pyrsistent-0.18.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5e4395bbf841693eaebaa5bb5c8f5cdbb1d139e07c975c682ec4e4f8126e03d2"}, - {file = "pyrsistent-0.18.0-cp36-cp36m-win32.whl", hash = "sha256:527be2bfa8dc80f6f8ddd65242ba476a6c4fb4e3aedbf281dfbac1b1ed4165b1"}, - {file = "pyrsistent-0.18.0-cp36-cp36m-win_amd64.whl", hash = "sha256:2aaf19dc8ce517a8653746d98e962ef480ff34b6bc563fc067be6401ffb457c7"}, - {file = "pyrsistent-0.18.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58a70d93fb79dc585b21f9d72487b929a6fe58da0754fa4cb9f279bb92369396"}, - {file = "pyrsistent-0.18.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:4916c10896721e472ee12c95cdc2891ce5890898d2f9907b1b4ae0f53588b710"}, - {file = "pyrsistent-0.18.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:73ff61b1411e3fb0ba144b8f08d6749749775fe89688093e1efef9839d2dcc35"}, - {file = "pyrsistent-0.18.0-cp37-cp37m-win32.whl", hash = "sha256:b29b869cf58412ca5738d23691e96d8aff535e17390128a1a52717c9a109da4f"}, - {file = "pyrsistent-0.18.0-cp37-cp37m-win_amd64.whl", hash = "sha256:097b96f129dd36a8c9e33594e7ebb151b1515eb52cceb08474c10a5479e799f2"}, - {file = "pyrsistent-0.18.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:772e94c2c6864f2cd2ffbe58bb3bdefbe2a32afa0acb1a77e472aac831f83427"}, - {file = "pyrsistent-0.18.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:c1a9ff320fa699337e05edcaae79ef8c2880b52720bc031b219e5b5008ebbdef"}, - {file = "pyrsistent-0.18.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:cd3caef37a415fd0dae6148a1b6957a8c5f275a62cca02e18474608cb263640c"}, - {file = "pyrsistent-0.18.0-cp38-cp38-win32.whl", hash = "sha256:e79d94ca58fcafef6395f6352383fa1a76922268fa02caa2272fff501c2fdc78"}, - {file = "pyrsistent-0.18.0-cp38-cp38-win_amd64.whl", hash = "sha256:a0c772d791c38bbc77be659af29bb14c38ced151433592e326361610250c605b"}, - {file = "pyrsistent-0.18.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d5ec194c9c573aafaceebf05fc400656722793dac57f254cd4741f3c27ae57b4"}, - {file = "pyrsistent-0.18.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:6b5eed00e597b5b5773b4ca30bd48a5774ef1e96f2a45d105db5b4ebb4bca680"}, - {file = "pyrsistent-0.18.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:48578680353f41dca1ca3dc48629fb77dfc745128b56fc01096b2530c13fd426"}, - {file = "pyrsistent-0.18.0-cp39-cp39-win32.whl", hash = "sha256:f3ef98d7b76da5eb19c37fda834d50262ff9167c65658d1d8f974d2e4d90676b"}, - {file = "pyrsistent-0.18.0-cp39-cp39-win_amd64.whl", hash = "sha256:404e1f1d254d314d55adb8d87f4f465c8693d6f902f67eb6ef5b4526dc58e6ea"}, - {file = "pyrsistent-0.18.0.tar.gz", hash = "sha256:773c781216f8c2900b42a7b638d5b517bb134ae1acbebe4d1e8f1f41ea60eb4b"}, -] -pytest = [ - {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, - {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, -] -pywin32 = [ - {file = "pywin32-302-cp310-cp310-win32.whl", hash = "sha256:251b7a9367355ccd1a4cd69cd8dd24bd57b29ad83edb2957cfa30f7ed9941efa"}, - {file = "pywin32-302-cp310-cp310-win_amd64.whl", hash = "sha256:79cf7e6ddaaf1cd47a9e50cc74b5d770801a9db6594464137b1b86aa91edafcc"}, - {file = "pywin32-302-cp36-cp36m-win32.whl", hash = "sha256:fe21c2fb332d03dac29de070f191bdbf14095167f8f2165fdc57db59b1ecc006"}, - {file = "pywin32-302-cp36-cp36m-win_amd64.whl", hash = "sha256:d3761ab4e8c5c2dbc156e2c9ccf38dd51f936dc77e58deb940ffbc4b82a30528"}, - {file = "pywin32-302-cp37-cp37m-win32.whl", hash = "sha256:48dd4e348f1ee9538dd4440bf201ea8c110ea6d9f3a5010d79452e9fa80480d9"}, - {file = "pywin32-302-cp37-cp37m-win_amd64.whl", hash = "sha256:496df89f10c054c9285cc99f9d509e243f4e14ec8dfc6d78c9f0bf147a893ab1"}, - {file = "pywin32-302-cp38-cp38-win32.whl", hash = "sha256:e372e477d938a49266136bff78279ed14445e00718b6c75543334351bf535259"}, - {file = "pywin32-302-cp38-cp38-win_amd64.whl", hash = "sha256:543552e66936378bd2d673c5a0a3d9903dba0b0a87235ef0c584f058ceef5872"}, - {file = "pywin32-302-cp39-cp39-win32.whl", hash = "sha256:2393c1a40dc4497fd6161b76801b8acd727c5610167762b7c3e9fd058ef4a6ab"}, - {file = "pywin32-302-cp39-cp39-win_amd64.whl", hash = "sha256:af5aea18167a31efcacc9f98a2ca932c6b6a6d91ebe31f007509e293dea12580"}, -] -requests = [ - {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, - {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, -] -rlp = [ - {file = "rlp-2.0.1-py2.py3-none-any.whl", hash = "sha256:52a57c9f53f03c88b189283734b397314288250cc4a3c4113e9e36e2ac6bdd16"}, - {file = "rlp-2.0.1.tar.gz", hash = "sha256:665e8312750b3fc5f7002e656d05b9dcb6e93b6063df40d95c49ad90c19d1f0e"}, -] -rx = [ - {file = "Rx-1.6.1-py2.py3-none-any.whl", hash = "sha256:7357592bc7e881a95e0c2013b73326f704953301ab551fbc8133a6fadab84105"}, - {file = "Rx-1.6.1.tar.gz", hash = "sha256:13a1d8d9e252625c173dc795471e614eadfe1cf40ffc684e08b8fff0d9748c23"}, -] -six = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] -toml = [ - {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, - {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, -] -toolz = [ - {file = "toolz-0.11.2-py3-none-any.whl", hash = "sha256:a5700ce83414c64514d82d60bcda8aabfde092d1c1a8663f9200c07fdcc6da8f"}, - {file = "toolz-0.11.2.tar.gz", hash = "sha256:6b312d5e15138552f1bda8a4e66c30e236c831b612b2bf0005f8a1df10a4bc33"}, -] -typing-extensions = [ - {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"}, - {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"}, - {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"}, -] -urllib3 = [ - {file = "urllib3-1.26.7-py2.py3-none-any.whl", hash = "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"}, - {file = "urllib3-1.26.7.tar.gz", hash = "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece"}, -] -varint = [ - {file = "varint-1.0.2.tar.gz", hash = "sha256:a6ecc02377ac5ee9d65a6a8ad45c9ff1dac8ccee19400a5950fb51d594214ca5"}, -] -web3 = [ - {file = "web3-5.19.0-py3-none-any.whl", hash = "sha256:3e6158edac8756c6a3d874ae23ce6eb9e53d0d250ab6a5425a869e1e568bf550"}, - {file = "web3-5.19.0.tar.gz", hash = "sha256:e120decb8e1db6d7d005a4446756f16b605e0a3d78384587fd2dab6338654751"}, -] -websockets = [ +files = [ {file = "websockets-8.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:3762791ab8b38948f0c4d281c8b2ddfa99b7e510e46bd8dfa942a5fff621068c"}, {file = "websockets-8.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:3db87421956f1b0779a7564915875ba774295cc86e81bc671631379371af1170"}, {file = "websockets-8.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4f9f7d28ce1d8f1295717c2c25b732c2bc0645db3215cf757551c392177d7cb8"}, @@ -1010,7 +1245,8 @@ websockets = [ {file = "websockets-8.1-cp38-cp38-win_amd64.whl", hash = "sha256:f8a7bff6e8664afc4e6c28b983845c5bc14965030e3fb98789734d416af77c4b"}, {file = "websockets-8.1.tar.gz", hash = "sha256:5c65d2da8c6bce0fca2528f69f44b2f977e06954c8512a952222cea50dad430f"}, ] -zipp = [ - {file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"}, - {file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"}, -] + +[metadata] +lock-version = "2.0" +python-versions = ">=3.9, <4.0" +content-hash = "e161c5cd2d2ff2b7c3cb4904a061d0768a9ad9add04b7732f70f6d3c69f01dc7" diff --git a/pyproject.toml b/pyproject.toml index 4ace093..030a95f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,8 +17,10 @@ jstyleson="0.0.2" web3=">=5.19.0" [tool.poetry.dev-dependencies] +black = "^22.12.0" pytest="*" +pre-commit = "^2.20.0" [build-system] requires = ["poetry-core>=1.0.0"] -build-backend = "poetry.core.masonry.api" \ No newline at end of file +build-backend = "poetry.core.masonry.api" diff --git a/samples/batchSwaps/batchSwapSample.py b/samples/batchSwaps/batchSwapSample.py index 2312134..b1273b6 100644 --- a/samples/batchSwaps/batchSwapSample.py +++ b/samples/batchSwaps/batchSwapSample.py @@ -3,64 +3,67 @@ import os import json + def main(): - - if len(sys.argv) < 2: - print("Usage: python3", sys.argv[0], "/path/to/swap.json"); - quit(); - pathToSwap = sys.argv[1]; - if not os.path.isfile(pathToSwap): - print("Path", pathToSwap, "does not exist. Please enter a valid path.") - quit(); + if len(sys.argv) < 2: + print("Usage: python3", sys.argv[0], "/path/to/swap.json") + quit() + + pathToSwap = sys.argv[1] + if not os.path.isfile(pathToSwap): + print("Path", pathToSwap, "does not exist. Please enter a valid path.") + quit() + + with open(pathToSwap) as f: + data = json.load(f) + + gasFactor = 1.05 + gasSpeedApproval = "fast" + gasSpeedTrade = "fast" - with open(pathToSwap) as f: - data = json.load(f) + bal = balpy.balpy.balpy(data["network"]) + swap = data["batchSwap"] + isFlashSwap = bal.balSwapIsFlashSwap(swap) - gasFactor = 1.05; - gasSpeedApproval = "fast"; - gasSpeedTrade = "fast"; + print() + print("==============================================================") + print("================ Step 1: Check Token Balances ================") + print("==============================================================") + print() - bal = balpy.balpy.balpy(data["network"]); - swap = data["batchSwap"]; - isFlashSwap = bal.balSwapIsFlashSwap(swap); + tokens = swap["assets"] + amountsIn = swap["limits"] + if not isFlashSwap: + if not bal.erc20HasSufficientBalances(tokens, amountsIn): + print("Please fix your insufficient balance before proceeding.") + print("Quitting...") + quit() + else: + print("Executing Flash Swap, no token balances necessary.") - print(); - print("==============================================================") - print("================ Step 1: Check Token Balances ================") - print("==============================================================") - print(); - - tokens = swap["assets"]; - amountsIn = swap["limits"]; - if not isFlashSwap: - if not bal.erc20HasSufficientBalances(tokens, amountsIn): - print("Please fix your insufficient balance before proceeding.") - print("Quitting...") - quit(); - else: - print("Executing Flash Swap, no token balances necessary.") + print() + print("==============================================================") + print("============== Step 2: Approve Token Allowance ===============") + print("==============================================================") + print() + if not isFlashSwap: + bal.erc20AsyncEnforceSufficientVaultAllowances( + tokens, amountsIn, amountsIn, gasFactor, gasSpeedApproval + ) + else: + print("Executing Flash Swap, no token approvals necessary.") + # quit(); - print(); - print("==============================================================") - print("============== Step 2: Approve Token Allowance ===============") - print("==============================================================") - print(); + print() + print("==============================================================") + print("=============== Step 3: Execute Batch Swap Txn ===============") + print("==============================================================") + print() - if not isFlashSwap: - bal.erc20AsyncEnforceSufficientVaultAllowances(tokens, amountsIn, amountsIn, gasFactor, gasSpeedApproval); - else: - print("Executing Flash Swap, no token approvals necessary.") - # quit(); + txHash = bal.balDoBatchSwap(swap, gasFactor=gasFactor, gasPriceSpeed=gasSpeedTrade) - print(); - print("==============================================================") - print("=============== Step 3: Execute Batch Swap Txn ===============") - print("==============================================================") - print(); - txHash = bal.balDoBatchSwap(swap, gasFactor=gasFactor, gasPriceSpeed=gasSpeedTrade); - -if __name__ == '__main__': - main(); \ No newline at end of file +if __name__ == "__main__": + main() diff --git a/samples/batchSwaps/queryBatchSwapSample.py b/samples/batchSwaps/queryBatchSwapSample.py index ded1b99..a1aca55 100644 --- a/samples/batchSwaps/queryBatchSwapSample.py +++ b/samples/batchSwaps/queryBatchSwapSample.py @@ -3,25 +3,27 @@ import os import json + def main(): - - if len(sys.argv) < 2: - print("Usage: python3", sys.argv[0], "/path/to/swap.json"); - quit(); - - pathToSwap = sys.argv[1]; - if not os.path.isfile(pathToSwap): - print("Path", pathToSwap, "does not exist. Please enter a valid path.") - quit(); - - with open(pathToSwap) as f: - data = json.load(f) - - bal = balpy.balpy.balpy(data["network"]); - swap = data["batchSwap"]; - - data = bal.balQueryBatchSwap(swap); - print(json.dumps(data, indent=4)); - -if __name__ == '__main__': - main(); \ No newline at end of file + + if len(sys.argv) < 2: + print("Usage: python3", sys.argv[0], "/path/to/swap.json") + quit() + + pathToSwap = sys.argv[1] + if not os.path.isfile(pathToSwap): + print("Path", pathToSwap, "does not exist. Please enter a valid path.") + quit() + + with open(pathToSwap) as f: + data = json.load(f) + + bal = balpy.balpy.balpy(data["network"]) + swap = data["batchSwap"] + + data = bal.balQueryBatchSwap(swap) + print(json.dumps(data, indent=4)) + + +if __name__ == "__main__": + main() diff --git a/samples/batchSwaps/queryBatchSwapsSample.py b/samples/batchSwaps/queryBatchSwapsSample.py index fbb4e39..7347311 100644 --- a/samples/batchSwaps/queryBatchSwapsSample.py +++ b/samples/batchSwaps/queryBatchSwapsSample.py @@ -3,25 +3,27 @@ import os import json + def main(): - - if len(sys.argv) < 2: - print("Usage: python3", sys.argv[0], "/path/to/swap.json"); - quit(); - pathToSwap = sys.argv[1]; - if not os.path.isfile(pathToSwap): - print("Path", pathToSwap, "does not exist. Please enter a valid path.") - quit(); + if len(sys.argv) < 2: + print("Usage: python3", sys.argv[0], "/path/to/swap.json") + quit() + + pathToSwap = sys.argv[1] + if not os.path.isfile(pathToSwap): + print("Path", pathToSwap, "does not exist. Please enter a valid path.") + quit() + + with open(pathToSwap) as f: + data = json.load(f) - with open(pathToSwap) as f: - data = json.load(f) + bal = balpy.balpy.balpy(data["network"]) - bal = balpy.balpy.balpy(data["network"]); + swap = data["batchSwap"] + data = bal.balQueryBatchSwaps([swap] * 3) + print(json.dumps(data, indent=4)) - swap = data["batchSwap"]; - data = bal.balQueryBatchSwaps([swap] * 3); - print(json.dumps(data, indent=4)); -if __name__ == '__main__': - main(); \ No newline at end of file +if __name__ == "__main__": + main() diff --git a/samples/internalBalances/sampleInternalBalances.py b/samples/internalBalances/sampleInternalBalances.py index 8d33105..b9853ac 100644 --- a/samples/internalBalances/sampleInternalBalances.py +++ b/samples/internalBalances/sampleInternalBalances.py @@ -1,108 +1,156 @@ import balpy + def printBalances(bal, tokens, address=None): - if not address is None: - print("Balances for", address); - print("Token\t\t\t\t\t\tInternal Balance\tExternal Balance") - internalBalances = bal.balVaultGetInternalBalance(tokens, address); - for token in internalBalances.keys(): - internal = internalBalances[token]; - external = bal.erc20GetBalanceStandard(token, address); - print(token, "\t", '{:.18f}'.format(internal), "\t", '{:.18f}'.format(external)) - print(); + if not address is None: + print("Balances for", address) + print("Token\t\t\t\t\t\tInternal Balance\tExternal Balance") + internalBalances = bal.balVaultGetInternalBalance(tokens, address) + for token in internalBalances.keys(): + internal = internalBalances[token] + external = bal.erc20GetBalanceStandard(token, address) + print(token, "\t", "{:.18f}".format(internal), "\t", "{:.18f}".format(external)) + print() + def main(): - network = "kovan" - bal = balpy.balpy.balpy(network); - - deadAddress = "0x0000000000000000000000000000000000000000"; - - gasFactor = 1.05; - gasPriceSpeed = "average"; - tokens = ["0xdFCeA9088c8A88A76FF74892C1457C17dfeef9C1"]; - amounts = [10.0]; - allowances = [-1]; - - token = tokens[0]; - - # enforce allowance - bal.erc20AsyncEnforceSufficientVaultAllowances(tokens, allowances, amounts, gasFactor, gasPriceSpeed); - - # QUERY INTERNAL BALANCE(S) - internalBalances = bal.balVaultGetInternalBalance(tokens); - - # DEPOSIT_INTERNAL - print(); - print("=============================================================="); - print("================ Deposit to Internal Balances ================"); - print("=============================================================="); - print(); - - print("\n==================== Starting Balances ====================\n"); - printBalances(bal, tokens); - - amount = 10.0; - print("Depositing", amount, "of token", token, "to internal balance..."); - bal.balVaultDoManageUserBalance( bal.UserBalanceOpKind["DEPOSIT_INTERNAL"], token, amount, bal.address, bal.address); - - print("\n==================== Ending Balances ====================\n"); - printBalances(bal, tokens); - - # WITHDRAW_INTERNAL = 1; - print(); - print("============================================================="); - print("============== Withdraw from Internal Balances =============="); - print("============================================================="); - print(); - - print("\n==================== Starting Balances ====================\n"); - printBalances(bal, tokens); - - amount = 5.0; - print("Withdrawing", amount, "of token", token, "from internal balance...\n"); - bal.balVaultDoManageUserBalance( bal.UserBalanceOpKind["WITHDRAW_INTERNAL"], token, amount, bal.address, bal.address); - - print("\n==================== Ending Balances ====================\n"); - printBalances(bal, tokens); - - - # TRANSFER_INTERNAL = 2; - print(); - print("============================================================"); - print("============== Transferring Internal Balances =============="); - print("============================================================"); - print(); - - print("\n==================== Starting Balances ====================\n"); - printBalances(bal, tokens, bal.address); - printBalances(bal, tokens, deadAddress); - - amount = 2.5; - print("Transferring", amount, "of token", token, "from", bal.address, "to", deadAddress,"INTERNALLY\n"); - bal.balVaultDoManageUserBalance( bal.UserBalanceOpKind["TRANSFER_INTERNAL"], token, amount, bal.address, deadAddress); - - print("\n==================== Ending Balances ====================\n"); - printBalances(bal, tokens, bal.address); - printBalances(bal, tokens, deadAddress); - - # TRANSFER_EXTERNAL = 3; - print(); - print("============================================================"); - print("============== Transferring External Balances =============="); - print("============================================================"); - print(); - - print("\n==================== Starting Balances ====================\n"); - printBalances(bal, tokens, bal.address); - printBalances(bal, tokens, deadAddress); - - amount = 1.25; - print("Transferring", amount, "of token", token, "from", bal.address, "to", deadAddress,"EXTERNALLY\n"); - bal.balVaultDoManageUserBalance( bal.UserBalanceOpKind["TRANSFER_EXTERNAL"], token, amount, bal.address, deadAddress); - - print("\n==================== Ending Balances ====================\n"); - printBalances(bal, tokens, bal.address); - printBalances(bal, tokens, deadAddress); - -if __name__ == '__main__': - main(); \ No newline at end of file + network = "kovan" + bal = balpy.balpy.balpy(network) + + deadAddress = "0x0000000000000000000000000000000000000000" + + gasFactor = 1.05 + gasPriceSpeed = "average" + tokens = ["0xdFCeA9088c8A88A76FF74892C1457C17dfeef9C1"] + amounts = [10.0] + allowances = [-1] + + token = tokens[0] + + # enforce allowance + bal.erc20AsyncEnforceSufficientVaultAllowances( + tokens, allowances, amounts, gasFactor, gasPriceSpeed + ) + + # QUERY INTERNAL BALANCE(S) + internalBalances = bal.balVaultGetInternalBalance(tokens) + + # DEPOSIT_INTERNAL + print() + print("==============================================================") + print("================ Deposit to Internal Balances ================") + print("==============================================================") + print() + + print("\n==================== Starting Balances ====================\n") + printBalances(bal, tokens) + + amount = 10.0 + print("Depositing", amount, "of token", token, "to internal balance...") + bal.balVaultDoManageUserBalance( + bal.UserBalanceOpKind["DEPOSIT_INTERNAL"], + token, + amount, + bal.address, + bal.address, + ) + + print("\n==================== Ending Balances ====================\n") + printBalances(bal, tokens) + + # WITHDRAW_INTERNAL = 1; + print() + print("=============================================================") + print("============== Withdraw from Internal Balances ==============") + print("=============================================================") + print() + + print("\n==================== Starting Balances ====================\n") + printBalances(bal, tokens) + + amount = 5.0 + print("Withdrawing", amount, "of token", token, "from internal balance...\n") + bal.balVaultDoManageUserBalance( + bal.UserBalanceOpKind["WITHDRAW_INTERNAL"], + token, + amount, + bal.address, + bal.address, + ) + + print("\n==================== Ending Balances ====================\n") + printBalances(bal, tokens) + + # TRANSFER_INTERNAL = 2; + print() + print("============================================================") + print("============== Transferring Internal Balances ==============") + print("============================================================") + print() + + print("\n==================== Starting Balances ====================\n") + printBalances(bal, tokens, bal.address) + printBalances(bal, tokens, deadAddress) + + amount = 2.5 + print( + "Transferring", + amount, + "of token", + token, + "from", + bal.address, + "to", + deadAddress, + "INTERNALLY\n", + ) + bal.balVaultDoManageUserBalance( + bal.UserBalanceOpKind["TRANSFER_INTERNAL"], + token, + amount, + bal.address, + deadAddress, + ) + + print("\n==================== Ending Balances ====================\n") + printBalances(bal, tokens, bal.address) + printBalances(bal, tokens, deadAddress) + + # TRANSFER_EXTERNAL = 3; + print() + print("============================================================") + print("============== Transferring External Balances ==============") + print("============================================================") + print() + + print("\n==================== Starting Balances ====================\n") + printBalances(bal, tokens, bal.address) + printBalances(bal, tokens, deadAddress) + + amount = 1.25 + print( + "Transferring", + amount, + "of token", + token, + "from", + bal.address, + "to", + deadAddress, + "EXTERNALLY\n", + ) + bal.balVaultDoManageUserBalance( + bal.UserBalanceOpKind["TRANSFER_EXTERNAL"], + token, + amount, + bal.address, + deadAddress, + ) + + print("\n==================== Ending Balances ====================\n") + printBalances(bal, tokens, bal.address) + printBalances(bal, tokens, deadAddress) + + +if __name__ == "__main__": + main() diff --git a/samples/joinPool/joinPoolSample.py b/samples/joinPool/joinPoolSample.py index 6abca24..3416c24 100644 --- a/samples/joinPool/joinPoolSample.py +++ b/samples/joinPool/joinPoolSample.py @@ -4,63 +4,73 @@ import jstyleson import json + def main(): - - if len(sys.argv) < 2: - print("Usage: python3", sys.argv[0], "/path/to/join.json"); - quit(); - - pathToJoin = sys.argv[1]; - if not os.path.isfile(pathToJoin): - print("Path", pathToJoin, "does not exist. Please enter a valid path.") - quit(); - - with open(pathToJoin) as f: - join = jstyleson.load(f) - - gasFactor = 1.05; - gasSpeed = "average"; - gasPriceGweiOverride = -1; # -1 uses etherscan price at speed for gasSpeed, all other values will override - bal = balpy.balpy.balpy(join["network"]); - - print(); - print("==============================================================") - print("================ Step 1: Check Token Balances ================") - print("==============================================================") - print(); - - tokens = list(join["tokens"].keys()); - amounts = [join["tokens"][token]["amount"] for token in tokens]; - if not bal.erc20HasSufficientBalances(tokens, amounts): - print("Please fix your insufficient balance before proceeding.") - print("Quitting...") - quit(); - - print(); - print("===============================================================") - print("=============== Step 2: Approve Token Allowance ===============") - print("===============================================================") - print(); - - #the poolData structure is similar to joinData. Will be renamed in the future. - (tokensSorted, allowancesSorted) = bal.erc20GetTargetAllowancesFromPoolData(join); - amountsSorted = [join["tokens"][token]["amount"] for token in tokensSorted]; - - # Async: Do [Approve]*N then [Wait]*N instead of [Approve, Wait]*N - bal.erc20AsyncEnforceSufficientVaultAllowances(tokensSorted, allowancesSorted, amountsSorted, gasFactor, gasSpeed, gasPriceGweiOverride=gasPriceGweiOverride); - - print(); - print("===============================================================") - print("================= Step 3: Send Tokens to Pool =================") - print("===============================================================") - print(); - - query = False; - output = bal.balJoinPool(join, query=query); - - if query: - print("queryJoin results:") - print(json.dumps(output,indent=4)); - -if __name__ == '__main__': - main(); + + if len(sys.argv) < 2: + print("Usage: python3", sys.argv[0], "/path/to/join.json") + quit() + + pathToJoin = sys.argv[1] + if not os.path.isfile(pathToJoin): + print("Path", pathToJoin, "does not exist. Please enter a valid path.") + quit() + + with open(pathToJoin) as f: + join = jstyleson.load(f) + + gasFactor = 1.05 + gasSpeed = "average" + gasPriceGweiOverride = -1 + # -1 uses etherscan price at speed for gasSpeed, all other values will override + bal = balpy.balpy.balpy(join["network"]) + + print() + print("==============================================================") + print("================ Step 1: Check Token Balances ================") + print("==============================================================") + print() + + tokens = list(join["tokens"].keys()) + amounts = [join["tokens"][token]["amount"] for token in tokens] + if not bal.erc20HasSufficientBalances(tokens, amounts): + print("Please fix your insufficient balance before proceeding.") + print("Quitting...") + quit() + + print() + print("===============================================================") + print("=============== Step 2: Approve Token Allowance ===============") + print("===============================================================") + print() + + # the poolData structure is similar to joinData. Will be renamed in the future. + (tokensSorted, allowancesSorted) = bal.erc20GetTargetAllowancesFromPoolData(join) + amountsSorted = [join["tokens"][token]["amount"] for token in tokensSorted] + + # Async: Do [Approve]*N then [Wait]*N instead of [Approve, Wait]*N + bal.erc20AsyncEnforceSufficientVaultAllowances( + tokensSorted, + allowancesSorted, + amountsSorted, + gasFactor, + gasSpeed, + gasPriceGweiOverride=gasPriceGweiOverride, + ) + + print() + print("===============================================================") + print("================= Step 3: Send Tokens to Pool =================") + print("===============================================================") + print() + + query = False + output = bal.balJoinPool(join, query=query) + + if query: + print("queryJoin results:") + print(json.dumps(output, indent=4)) + + +if __name__ == "__main__": + main() diff --git a/samples/misc/balancerHelpersVaultRead.py b/samples/misc/balancerHelpersVaultRead.py index 72d6d56..b167a0a 100644 --- a/samples/misc/balancerHelpersVaultRead.py +++ b/samples/misc/balancerHelpersVaultRead.py @@ -1,11 +1,13 @@ import balpy + def main(): - network = "polygon" - - bal = balpy.balpy.balpy(network); - vaultAddress = bal.balBalancerHelpersGetVault(); - print("Vault address:", vaultAddress); - -if __name__ == '__main__': - main(); \ No newline at end of file + network = "polygon" + + bal = balpy.balpy.balpy(network) + vaultAddress = bal.balBalancerHelpersGetVault() + print("Vault address:", vaultAddress) + + +if __name__ == "__main__": + main() diff --git a/samples/misc/findPoolFactory.py b/samples/misc/findPoolFactory.py index d2cb948..5cd4817 100644 --- a/samples/misc/findPoolFactory.py +++ b/samples/misc/findPoolFactory.py @@ -1,21 +1,23 @@ import balpy + def main(): - network = "mainnet" + network = "mainnet" + + bal = balpy.balpy.balpy(network) + + print("Successful Case:") + poolId = "0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014" + factoryName = bal.balFindPoolFactory(poolId) + print(poolId, "is from", factoryName) - bal = balpy.balpy.balpy(network); + print("-----------------------------------") - print("Successful Case:"); - poolId = "0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014" - factoryName = bal.balFindPoolFactory(poolId); - print(poolId, "is from", factoryName); - - print("-----------------------------------") + print("Failure Case:") + poolId = "0x01234567899dbdb9c8ef030ab642b10820db8f56000200000000000000000014" + factoryName = bal.balFindPoolFactory(poolId) + print(poolId, "is from", factoryName) - print("Failure Case:"); - poolId = "0x01234567899dbdb9c8ef030ab642b10820db8f56000200000000000000000014" - factoryName = bal.balFindPoolFactory(poolId); - print(poolId, "is from", factoryName); -if __name__ == '__main__': - main(); \ No newline at end of file +if __name__ == "__main__": + main() diff --git a/samples/misc/gasPriceQueries.py b/samples/misc/gasPriceQueries.py index e9050a3..2158123 100644 --- a/samples/misc/gasPriceQueries.py +++ b/samples/misc/gasPriceQueries.py @@ -1,19 +1,21 @@ import balpy + def main(): - network = "mainnet" - bal = balpy.balpy.balpy(network); + network = "mainnet" + bal = balpy.balpy.balpy(network) + + # NOTE: Using these time-based gas strategies requires caching gas prices over + # extended time periods. In testing, this script made 140-150 calls to the RPC + # and took ~1 minute to initialize the cache. - # NOTE: Using these time-based gas strategies requires caching gas prices over - # extended time periods. In testing, this script made 140-150 calls to the RPC - # and took ~1 minute to initialize the cache. + speeds = ["slow", "average", "fast"] + print("\n\tSpeed\tPrice(gwei)") + print("\t-----\t-----------") + for speed in speeds: + price = bal.getGasPrice(speed) + print("\t" + speed + "\t" + str(price)) - speeds = ["slow", "average", "fast"] - print("\n\tSpeed\tPrice(gwei)") - print("\t-----\t-----------") - for speed in speeds: - price = bal.getGasPrice(speed) - print("\t" + speed + "\t" + str(price)) -if __name__ == '__main__': - main() \ No newline at end of file +if __name__ == "__main__": + main() diff --git a/samples/misc/generateDeploymentsDocsTable.py b/samples/misc/generateDeploymentsDocsTable.py index 6acc97d..2bbb302 100644 --- a/samples/misc/generateDeploymentsDocsTable.py +++ b/samples/misc/generateDeploymentsDocsTable.py @@ -1,11 +1,13 @@ import balpy + def main(): - network = "kovan" - - bal = balpy.balpy.balpy(network); - output = bal.generateDeploymentsDocsTable(); - print(output) - -if __name__ == '__main__': - main(); \ No newline at end of file + network = "kovan" + + bal = balpy.balpy.balpy(network) + output = bal.generateDeploymentsDocsTable() + print(output) + + +if __name__ == "__main__": + main() diff --git a/samples/misc/revokeAllowances.py b/samples/misc/revokeAllowances.py index 315337e..62987e2 100644 --- a/samples/misc/revokeAllowances.py +++ b/samples/misc/revokeAllowances.py @@ -1,47 +1,53 @@ import balpy + def main(): - gasFactor = 1.05; - gasSpeed = "fast"; - gasOverride = 5; - - network = "kovan" - bal = balpy.balpy.balpy(network); - allowedAddress = bal.deploymentAddresses["Vault"]; - tokens = [ "0xdFCeA9088c8A88A76FF74892C1457C17dfeef9C1", - "0x41286Bb1D3E870f3F750eB7E1C25d7E48c8A1Ac7", - "0xc2569dd7d0fd715B054fBf16E75B001E5c0C1115", - "0xAf9ac3235be96eD496db7969f60D354fe5e426B0", - "0x04DF6e4121c27713ED22341E7c7Df330F56f289B", - "0x8F4beBF498cc624a0797Fe64114A6Ff169EEe078", - "0x1C8E3Bcb3378a443CC591f154c5CE0EBb4dA9648", - "0xcC08220af469192C53295fDd34CFb8DF29aa17AB", - "0x15E76Fc74C6ab1c3141D61219883d1c59F716E21", - "0x22ee6c3B011fACC530dd01fe94C58919344d6Db5"]; - - nonce = bal.web3.eth.get_transaction_count(bal.web3.eth.default_account); - hashes = []; - - for token in tokens: - print("Checking:", token) - allowance = bal.erc20GetAllowanceStandard(token, allowedAddress); - if allowance > 0: - txHash = bal.erc20SignAndSendNewAllowance( token, - allowedAddress, - 0, - gasFactor, - gasSpeed, - nonceOverride=nonce, - isAsync=True, - gasPriceGweiOverride=gasOverride); - nonce += 1 - hashes.append(txHash); - print("\tRevoking allowance -- txHash:", txHash) - else: - print("\tNo allowance. Skipping...") - - for txHash in hashes: - bal.waitForTx(txHash); - -if __name__ == '__main__': - main(); \ No newline at end of file + gasFactor = 1.05 + gasSpeed = "fast" + gasOverride = 5 + + network = "kovan" + bal = balpy.balpy.balpy(network) + allowedAddress = bal.deploymentAddresses["Vault"] + tokens = [ + "0xdFCeA9088c8A88A76FF74892C1457C17dfeef9C1", + "0x41286Bb1D3E870f3F750eB7E1C25d7E48c8A1Ac7", + "0xc2569dd7d0fd715B054fBf16E75B001E5c0C1115", + "0xAf9ac3235be96eD496db7969f60D354fe5e426B0", + "0x04DF6e4121c27713ED22341E7c7Df330F56f289B", + "0x8F4beBF498cc624a0797Fe64114A6Ff169EEe078", + "0x1C8E3Bcb3378a443CC591f154c5CE0EBb4dA9648", + "0xcC08220af469192C53295fDd34CFb8DF29aa17AB", + "0x15E76Fc74C6ab1c3141D61219883d1c59F716E21", + "0x22ee6c3B011fACC530dd01fe94C58919344d6Db5", + ] + + nonce = bal.web3.eth.get_transaction_count(bal.web3.eth.default_account) + hashes = [] + + for token in tokens: + print("Checking:", token) + allowance = bal.erc20GetAllowanceStandard(token, allowedAddress) + if allowance > 0: + txHash = bal.erc20SignAndSendNewAllowance( + token, + allowedAddress, + 0, + gasFactor, + gasSpeed, + nonceOverride=nonce, + isAsync=True, + gasPriceGweiOverride=gasOverride, + ) + nonce += 1 + hashes.append(txHash) + print("\tRevoking allowance -- txHash:", txHash) + else: + print("\tNo allowance. Skipping...") + + for txHash in hashes: + bal.waitForTx(txHash) + + +if __name__ == "__main__": + main() diff --git a/samples/misc/vaultGetPoolTokens.py b/samples/misc/vaultGetPoolTokens.py index d8514e5..a4c2a23 100644 --- a/samples/misc/vaultGetPoolTokens.py +++ b/samples/misc/vaultGetPoolTokens.py @@ -1,23 +1,25 @@ import balpy + def main(): - network = "mainnet" + network = "mainnet" + + bal = balpy.balpy.balpy(network) + poolId = "0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014" + (tokens, balances, lastChangeBlock) = bal.balVaultGetPoolTokens(poolId) + + print("==============================================") + print("Information for pool:") + print("\t" + poolId) + print() + print("\tToken\t\t\t\t\t\tBalance (wei)") + print("\t-----\t\t\t\t\t\t-------------") + for token, balance in zip(tokens, balances): + print("\t" + token + "\t" + str(balance)) + print() + print("\tLast change block:", lastChangeBlock) + print() + - bal = balpy.balpy.balpy(network); - poolId = "0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014" - (tokens, balances, lastChangeBlock) = bal.balVaultGetPoolTokens(poolId); - - print("==============================================") - print("Information for pool:") - print("\t" + poolId) - print(); - print("\tToken\t\t\t\t\t\tBalance (wei)") - print("\t-----\t\t\t\t\t\t-------------") - for token, balance in zip(tokens, balances): - print("\t" + token + "\t" + str(balance)); - print(); - print("\tLast change block:", lastChangeBlock) - print(); - -if __name__ == '__main__': - main(); \ No newline at end of file +if __name__ == "__main__": + main() diff --git a/samples/misc/vaultWethRead.py b/samples/misc/vaultWethRead.py index 554b931..9a2c592 100644 --- a/samples/misc/vaultWethRead.py +++ b/samples/misc/vaultWethRead.py @@ -1,11 +1,13 @@ import balpy + def main(): - network = "kovan" - - bal = balpy.balpy.balpy(network); - weth = bal.balVaultWeth(); - print("Wrapped ETH Address:", weth); - -if __name__ == '__main__': - main(); \ No newline at end of file + network = "kovan" + + bal = balpy.balpy.balpy(network) + weth = bal.balVaultWeth() + print("Wrapped ETH Address:", weth) + + +if __name__ == "__main__": + main() diff --git a/samples/multicall/erc20Decimals.py b/samples/multicall/erc20Decimals.py index c2c55f0..be5d0e1 100644 --- a/samples/multicall/erc20Decimals.py +++ b/samples/multicall/erc20Decimals.py @@ -3,17 +3,154 @@ import requests import time + def main(): - network = "mainnet" - bal = balpy.balpy.balpy(network); + network = "mainnet" + bal = balpy.balpy.balpy(network) + + tokens = [ + "0xd6fd2f39ff3f3565d456bb8aa94703fe5fd88d33", + "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", + "0x1776e1f26f98b1a5df9cd347953a26dd3cb46671", + "0x5a98fcbea516cf06857215779fd812ca3bef1b32", + "0xdbdb4d16eda451d0503b854cf79d55697f90c8df", + "0x269616d549d7e8eaa82dfb17028d0b212d11232a", + "0x23b608675a2b2fb1890d3abbd85c5775c51691d5", + "0x2d94aa3e47d9d5024503ca8491fce9a2fb4da198", + "0x1e83916ea2ef2d7a6064775662e163b2d4c330a7", + "0xff20817765cb7f73d4bde2e66e067e58d11095c2", + "0xf1294e805b992320a3515682c6ab0fe6251067e5", + "0x43b4fdfd4ff969587185cdb6f0bd875c5fc83f8c", + "0x4730fb1463a6f1f44aeb45f6c5c422427f37f4d0", + "0x06325440d014e39736583c165c2963ba99faf14e", + "0x8798249c2e607446efb7ad49ec89dd1865ff4272", + "0xb81d70802a816b5dacba06d708b5acf19dcd436d", + "0x50de6856358cc35f3a9a57eaaa34bd4cb707d2cd", + "0xe7f4294033d0fde6eecb94bef7da22fde68a61ec", + "0xb6ca7399b4f9ca56fc27cbff44f4d2e4eef1fc81", + "0x467bccd9d29f223bce8043b84e8c8b282827790f", + "0x1b40183efb4dd766f11bda7a7c3ad8982e998421", + "0xeb4c2781e4eba804ce9a9803c67d0893436bb27d", + "0xdac17f958d2ee523a2206206994597c13d831ec7", + "0x94046274b5aa816ab236a9eab42b5563b56e1931", + "0xf6d2699b035fc8fd5e023d4a6da216112ad8a985", + "0xaf5d6d2e724f43769fa9e44284f0433a8f5be973", + "0x94ec4e3a7c5068ae4a035f702f2cd5a6da9c5cc1", + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "0x4f4500b3885bc72199373abfe7adefd0366bafed", + "0x45804880de22913dafe09f4980848ece6ecbaf78", + "0x6b175474e89094c44da98b954eedeac495271d0f", + "0x261b45d85ccfeabb11f022eba346ee8d1cd488c0", + "0x18aaa7115705e8be94bffebde57af9bfc265b998", + "0xdb25f211ab05b1c97d595516f45794528a807ad8", + "0xd533a949740bb3306d119cc777fa900ba034cd52", + "0xea47b64e1bfccb773a0420247c0aa0a3c1d2e5c5", + "0x89045d0af6a12782ec6f701ee6698beaf17d0ea2", + "0x677ddbd918637e5f2c79e164d402454de7da8619", + "0xbabd64a87881d8df7680907fcde176ff11fa0292", + "0xd70240dd62f4ea9a6a2416e0073d72139489d2aa", + "0x9e7fd25ad9d97f1e6716fa5bb04749a4621e892d", + "0xba4cfe5741b357fa371b506e5db0774abfecf8fc", + "0x123151402076fc819b7564510989e475c9cd93ca", + "0xfe18be6b3bd88a2d2a7f928d00292e7a9963cfc6", + "0xa1cc9bbcd3731a9fd43e1f1416f9b6bf824f37d7", + "0xfa5047c9c78b8877af97bdcb85db743fd7313d4a", + "0xba100000625a3754423978a60c9317c58a424e3d", + "0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9", + "0x72e364f2abdc788b7e918bc238b21f109cd634d7", + "0xd291e7a03283640fdc51b121ac401383a46cc623", + "0xb4efd85c19999d84251304bda99e90b92300bd93", + "0x514910771af9ca656af840dff83e8264ecf986ca", + "0x27054b13b1b798b345b591a4d22e6562d47ea75a", + "0x0bc529c00c6401aef6d220be8c6ea1667f6ad93e", + "0xd71ecff9342a5ced620049e616c5035f1db98620", + "0x90b831fa3bebf58e9744a14d638e25b4ee06f9bc", + "0xf65b5c5104c4fafd4b709d9d60a185eae063276c", + "0x641927e970222b10b2e8cdbc96b1b4f427316f16", + "0xbb0e17ef65f82ab018d8edd776e8dd940327b28b", + "0xf629cbd94d3791c9250152bd8dfbdf380e2a3b9c", + "0x16f378ebdbc54882611ad4a02929eff4b5d7d452", + "0xfca59cd816ab1ead66534d82bc21e7515ce441cf", + "0xf1290473e210b2108a85237fbcd7b6eb42cc654f", + "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599", + "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "0x2e60f6c4ca05bc55a8e577deebd61fce727c4a6e", + "0x0404fd4f0c3b474e8525558ca3d3bc33cec7545a", + "0x68037790a0229e9ce6eaa8a99ea92964106c4703", + "0xca3d75ac011bf5ad07a98d02f18225f9bd9a6bdf", + "0x86ed939b500e121c0c5f493f399084db596dad20", + "0x11c1a6b3ed6bb362954b29d3183cfa97a0c806aa", + "0x6dea81c8171d0ba574754ef6f8b412f2ed88c54d", + "0x111111111117dc0aa78b770fa6a738034120c302", + "0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f", + "0xfb5453340c03db5ade474b27e68b6a9c6b2823eb", + "0x0cec1a9154ff802e7934fc916ed7ca50bde6844e", + "0x114f1388fab456c4ba31b1850b244eedcd024136", + "0x3845badade8e6dff049820680d1f14bd3903a5d0", + "0xc4ad29ba4b3c580e6d59105fff484999997675ff", + "0x1cf0f3aabe4d12106b27ab44df5473974279c524", + "0xe2f2a5c287993345a840db3b0845fbc70f5935a5", + "0x782be9330969aa7b9db56382752a1364580f199f", + "0xfd8d7dbecd5c083dde2b828f96be5d16d1188235", + "0xa279dab6ec190ee4efce7da72896eb58ad533262", + "0x81f8f0bb1cb2a06649e51913a151f0e7ef6fa321", + "0xbc396689893d065f41bc2c6ecbee5e0085233447", + "0xba8c8b50ecd5b580f464f7611b8549ffee4d8da2", + "0xa3d58c4e56fedcae3a7c43a725aee9a71f0ece4e", + "0x3b32f63c1e0fb810f0a06814ead1d4431b237560", + "0x04fa0d235c4abf4bcf4787af4cf447de572ef828", + "0xc00e94cb662c3520282e6f5717214004a7f26888", + "0x35a18000230da775cac24873d00ff85bccded550", + "0xcc4304a31d09258b0029ea7fe63d032f52e44efe", + "0x584bc13c7d411c00c01a62e8019472de68768430", + "0x2ba592f78db6436527729929aaf6c908497cb200", + "0xc944e90c64b2c07662a292be6244bdf05cda44a7", + "0x01abc00e86c7e258823b9a055fd62ca6cf61a163", + "0x6810e776880c02933d47db1b9fc05908e5386b96", + "0x61d5dc44849c9c87b0856a2a311536205c96c7fd", + "0xde30da39c46104798bb5aa3fe8b9e0e1f348163f", + "0x0d8775f648430679a709e98d2b0cb6250d2887ef", + "0xf1f955016ecbcd7321c7266bccfb96c68ea5e49b", + "0x95a4492f028aa1fd432ea71146b433e7b4446611", + "0x58b6a8a3302369daec383334672404ee733ab239", + "0x87d73e916d7057945c9bcd8cdd94e42a6f47f776", + "0x5d67c1c829ab93867d865cf2008deb45df67044f", + "0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0", + "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0", + "0x88acdd2a6425c3faae4bc9650fd7e27e0bebb7ab", + "0x3ec8798b81485a254928b70cda1cf0a2bb0b74d7", + "0x6b4d5e9ec2acea23d4110f4803da99e25443c5df", + "0xe41d2489571d322189246dafa5ebde1f4699f498", + "0x03ab458634910aad20ef5f1c8ee96f1d6ac54919", + "0xc080f19d9e7ccb6ef2096dfa08570e0057490940", + "0xd075e95423c5c4ba1e122cae0f4cdfa19b82881b", + "0x1494ca1f11d487c2bbe4543e90080aeba4ba3c2b", + "0x10010078a54396f62c96df8532dc2b4847d47ed3", + "0x25f8087ead173b73d6e8b84329989a8eea16cf73", + "0xa3bed4e1c75d00fa6f4e5e6922db7261b5e9acd2", + "0x5f98805a4e8be255a32880fdec7f6728c6568ba0", + "0x476c5e26a75bd202a9683ffd34359c0cc15be0ff", + "0xed279fdd11ca84beef15af5d39bb4d4bee23f0ca", + "0xe9f84de264e91529af07fa2c746e934397810334", + "0x478bbc744811ee8310b461514bdc29d03739084d", + "0x0d438f3b5175bebc262bf23753c1e53d03432bde", + "0xb705268213d593b8fd88d3fdeff93aff5cbdcfae", + "0x965d79f1a1016b574a62986e13ca8ab04dfdd15c", + "0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2", + "0x408e41876cccdc0f92210600ef50372656052a38", + "0x0ec9f76202a7061eb9b3a7d6b59d36215a7e37da", + "0xf5d669627376ebd411e34b98f19c868c8aba5ada", + "0x36128d5436d2d70cab39c9af9cce146c38554ff0", + "0x0e29e5abbb5fd88e28b2d355774e73bd47de3bcd", + "0x6b3595068778dd592e39a122f4f5a5cf09c90fe2", + ] - tokens = ['0xd6fd2f39ff3f3565d456bb8aa94703fe5fd88d33', '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984', '0x1776e1f26f98b1a5df9cd347953a26dd3cb46671', '0x5a98fcbea516cf06857215779fd812ca3bef1b32', '0xdbdb4d16eda451d0503b854cf79d55697f90c8df', '0x269616d549d7e8eaa82dfb17028d0b212d11232a', '0x23b608675a2b2fb1890d3abbd85c5775c51691d5', '0x2d94aa3e47d9d5024503ca8491fce9a2fb4da198', '0x1e83916ea2ef2d7a6064775662e163b2d4c330a7', '0xff20817765cb7f73d4bde2e66e067e58d11095c2', '0xf1294e805b992320a3515682c6ab0fe6251067e5', '0x43b4fdfd4ff969587185cdb6f0bd875c5fc83f8c', '0x4730fb1463a6f1f44aeb45f6c5c422427f37f4d0', '0x06325440d014e39736583c165c2963ba99faf14e', '0x8798249c2e607446efb7ad49ec89dd1865ff4272', '0xb81d70802a816b5dacba06d708b5acf19dcd436d', '0x50de6856358cc35f3a9a57eaaa34bd4cb707d2cd', '0xe7f4294033d0fde6eecb94bef7da22fde68a61ec', '0xb6ca7399b4f9ca56fc27cbff44f4d2e4eef1fc81', '0x467bccd9d29f223bce8043b84e8c8b282827790f', '0x1b40183efb4dd766f11bda7a7c3ad8982e998421', '0xeb4c2781e4eba804ce9a9803c67d0893436bb27d', '0xdac17f958d2ee523a2206206994597c13d831ec7', '0x94046274b5aa816ab236a9eab42b5563b56e1931', '0xf6d2699b035fc8fd5e023d4a6da216112ad8a985', '0xaf5d6d2e724f43769fa9e44284f0433a8f5be973', '0x94ec4e3a7c5068ae4a035f702f2cd5a6da9c5cc1', '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', '0x4f4500b3885bc72199373abfe7adefd0366bafed', '0x45804880de22913dafe09f4980848ece6ecbaf78', '0x6b175474e89094c44da98b954eedeac495271d0f', '0x261b45d85ccfeabb11f022eba346ee8d1cd488c0', '0x18aaa7115705e8be94bffebde57af9bfc265b998', '0xdb25f211ab05b1c97d595516f45794528a807ad8', '0xd533a949740bb3306d119cc777fa900ba034cd52', '0xea47b64e1bfccb773a0420247c0aa0a3c1d2e5c5', '0x89045d0af6a12782ec6f701ee6698beaf17d0ea2', '0x677ddbd918637e5f2c79e164d402454de7da8619', '0xbabd64a87881d8df7680907fcde176ff11fa0292', '0xd70240dd62f4ea9a6a2416e0073d72139489d2aa', '0x9e7fd25ad9d97f1e6716fa5bb04749a4621e892d', '0xba4cfe5741b357fa371b506e5db0774abfecf8fc', '0x123151402076fc819b7564510989e475c9cd93ca', '0xfe18be6b3bd88a2d2a7f928d00292e7a9963cfc6', '0xa1cc9bbcd3731a9fd43e1f1416f9b6bf824f37d7', '0xfa5047c9c78b8877af97bdcb85db743fd7313d4a', '0xba100000625a3754423978a60c9317c58a424e3d', '0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9', '0x72e364f2abdc788b7e918bc238b21f109cd634d7', '0xd291e7a03283640fdc51b121ac401383a46cc623', '0xb4efd85c19999d84251304bda99e90b92300bd93', '0x514910771af9ca656af840dff83e8264ecf986ca', '0x27054b13b1b798b345b591a4d22e6562d47ea75a', '0x0bc529c00c6401aef6d220be8c6ea1667f6ad93e', '0xd71ecff9342a5ced620049e616c5035f1db98620', '0x90b831fa3bebf58e9744a14d638e25b4ee06f9bc', '0xf65b5c5104c4fafd4b709d9d60a185eae063276c', '0x641927e970222b10b2e8cdbc96b1b4f427316f16', '0xbb0e17ef65f82ab018d8edd776e8dd940327b28b', '0xf629cbd94d3791c9250152bd8dfbdf380e2a3b9c', '0x16f378ebdbc54882611ad4a02929eff4b5d7d452', '0xfca59cd816ab1ead66534d82bc21e7515ce441cf', '0xf1290473e210b2108a85237fbcd7b6eb42cc654f', '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599', '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', '0x2e60f6c4ca05bc55a8e577deebd61fce727c4a6e', '0x0404fd4f0c3b474e8525558ca3d3bc33cec7545a', '0x68037790a0229e9ce6eaa8a99ea92964106c4703', '0xca3d75ac011bf5ad07a98d02f18225f9bd9a6bdf', '0x86ed939b500e121c0c5f493f399084db596dad20', '0x11c1a6b3ed6bb362954b29d3183cfa97a0c806aa', '0x6dea81c8171d0ba574754ef6f8b412f2ed88c54d', '0x111111111117dc0aa78b770fa6a738034120c302', '0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f', '0xfb5453340c03db5ade474b27e68b6a9c6b2823eb', '0x0cec1a9154ff802e7934fc916ed7ca50bde6844e', '0x114f1388fab456c4ba31b1850b244eedcd024136', '0x3845badade8e6dff049820680d1f14bd3903a5d0', '0xc4ad29ba4b3c580e6d59105fff484999997675ff', '0x1cf0f3aabe4d12106b27ab44df5473974279c524', '0xe2f2a5c287993345a840db3b0845fbc70f5935a5', '0x782be9330969aa7b9db56382752a1364580f199f', '0xfd8d7dbecd5c083dde2b828f96be5d16d1188235', '0xa279dab6ec190ee4efce7da72896eb58ad533262', '0x81f8f0bb1cb2a06649e51913a151f0e7ef6fa321', '0xbc396689893d065f41bc2c6ecbee5e0085233447', '0xba8c8b50ecd5b580f464f7611b8549ffee4d8da2', '0xa3d58c4e56fedcae3a7c43a725aee9a71f0ece4e', '0x3b32f63c1e0fb810f0a06814ead1d4431b237560', '0x04fa0d235c4abf4bcf4787af4cf447de572ef828', '0xc00e94cb662c3520282e6f5717214004a7f26888', '0x35a18000230da775cac24873d00ff85bccded550', '0xcc4304a31d09258b0029ea7fe63d032f52e44efe', '0x584bc13c7d411c00c01a62e8019472de68768430', '0x2ba592f78db6436527729929aaf6c908497cb200', '0xc944e90c64b2c07662a292be6244bdf05cda44a7', '0x01abc00e86c7e258823b9a055fd62ca6cf61a163', '0x6810e776880c02933d47db1b9fc05908e5386b96', '0x61d5dc44849c9c87b0856a2a311536205c96c7fd', '0xde30da39c46104798bb5aa3fe8b9e0e1f348163f', '0x0d8775f648430679a709e98d2b0cb6250d2887ef', '0xf1f955016ecbcd7321c7266bccfb96c68ea5e49b', '0x95a4492f028aa1fd432ea71146b433e7b4446611', '0x58b6a8a3302369daec383334672404ee733ab239', '0x87d73e916d7057945c9bcd8cdd94e42a6f47f776', '0x5d67c1c829ab93867d865cf2008deb45df67044f', '0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0', '0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0', '0x88acdd2a6425c3faae4bc9650fd7e27e0bebb7ab', '0x3ec8798b81485a254928b70cda1cf0a2bb0b74d7', '0x6b4d5e9ec2acea23d4110f4803da99e25443c5df', '0xe41d2489571d322189246dafa5ebde1f4699f498', '0x03ab458634910aad20ef5f1c8ee96f1d6ac54919', '0xc080f19d9e7ccb6ef2096dfa08570e0057490940', '0xd075e95423c5c4ba1e122cae0f4cdfa19b82881b', '0x1494ca1f11d487c2bbe4543e90080aeba4ba3c2b', '0x10010078a54396f62c96df8532dc2b4847d47ed3', '0x25f8087ead173b73d6e8b84329989a8eea16cf73', '0xa3bed4e1c75d00fa6f4e5e6922db7261b5e9acd2', '0x5f98805a4e8be255a32880fdec7f6728c6568ba0', '0x476c5e26a75bd202a9683ffd34359c0cc15be0ff', '0xed279fdd11ca84beef15af5d39bb4d4bee23f0ca', '0xe9f84de264e91529af07fa2c746e934397810334', '0x478bbc744811ee8310b461514bdc29d03739084d', '0x0d438f3b5175bebc262bf23753c1e53d03432bde', '0xb705268213d593b8fd88d3fdeff93aff5cbdcfae', '0x965d79f1a1016b574a62986e13ca8ab04dfdd15c', '0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2', '0x408e41876cccdc0f92210600ef50372656052a38', '0x0ec9f76202a7061eb9b3a7d6b59d36215a7e37da', '0xf5d669627376ebd411e34b98f19c868c8aba5ada', '0x36128d5436d2d70cab39c9af9cce146c38554ff0', '0x0e29e5abbb5fd88e28b2d355774e73bd47de3bcd', '0x6b3595068778dd592e39a122f4f5a5cf09c90fe2']; + tStart = time.time() + results = bal.multiCallErc20BatchDecimals(tokens) + tEnd = time.time() + print(json.dumps(results, indent=4)) + print("Query took", tEnd - tStart, "seconds") - tStart = time.time(); - results = bal.multiCallErc20BatchDecimals(tokens); - tEnd = time.time() - print(json.dumps(results, indent=4)) - print("Query took", tEnd - tStart, "seconds") -if __name__ == '__main__': - main(); \ No newline at end of file +if __name__ == "__main__": + main() diff --git a/samples/multicall/getOnchainData.py b/samples/multicall/getOnchainData.py index cda93a1..f2add16 100644 --- a/samples/multicall/getOnchainData.py +++ b/samples/multicall/getOnchainData.py @@ -3,23 +3,29 @@ import requests import time + def main(): - network = "mainnet"; - bal = balpy.balpy.balpy(network); + network = "mainnet" + bal = balpy.balpy.balpy(network) + + poolIdsUrl = ( + "https://raw.githubusercontent.com/gerrrg/balancer-pool-ids/master/pools/" + + network + + ".json" + ) + r = requests.get(poolIdsUrl) + poolIds = r.json()["pools"] - poolIdsUrl = "https://raw.githubusercontent.com/gerrrg/balancer-pool-ids/master/pools/" + network + ".json"; - r = requests.get(poolIdsUrl); - poolIds = r.json()["pools"]; + if "Element" in poolIds.keys(): + del poolIds["Element"] - if "Element" in poolIds.keys(): - del poolIds["Element"]; + tStart = time.time() + results = bal.getOnchainData(poolIds) + tEnd = time.time() + print("Query took", tEnd - tStart, "seconds") - tStart = time.time(); - results = bal.getOnchainData(poolIds); - tEnd = time.time(); - print("Query took", tEnd - tStart, "seconds"); + print(json.dumps(results, indent=4)) - print(json.dumps(results,indent=4)); -if __name__ == '__main__': - main(); \ No newline at end of file +if __name__ == "__main__": + main() diff --git a/samples/oraclePool/oraclePoolGetTwap.py b/samples/oraclePool/oraclePoolGetTwap.py index 1519a5b..163aefd 100644 --- a/samples/oraclePool/oraclePoolGetTwap.py +++ b/samples/oraclePool/oraclePoolGetTwap.py @@ -1,27 +1,31 @@ import balpy + def main(): - network = "mainnet" - - bal = balpy.balpy.balpy(network); - poolId = "0x96646936b91d6b9d7d0c47c496afbf3d6ec7b6f8000200000000000000000019"; - - - # enum Variable { PAIR_PRICE, BPT_PRICE, INVARIANT } - # struct OracleAverageQuery { - # Variable variable; - # uint256 secs; - # uint256 ago; - # } - variable = 0; # pair price - secs = 60*60*24; # one day - ago = 0; # most recent - - queries = []; - queries.append((variable, secs, ago)) - results = bal.balOraclePoolGetTimeWeightedAverage(poolId, queries); - - print(results[0]) - -if __name__ == '__main__': - main(); \ No newline at end of file + network = "mainnet" + + bal = balpy.balpy.balpy(network) + poolId = "0x96646936b91d6b9d7d0c47c496afbf3d6ec7b6f8000200000000000000000019" + + # enum Variable { PAIR_PRICE, BPT_PRICE, INVARIANT } + # struct OracleAverageQuery { + # Variable variable; + # uint256 secs; + # uint256 ago; + # } + variable = 0 + # pair price + secs = 60 * 60 * 24 + # one day + ago = 0 + # most recent + + queries = [] + queries.append((variable, secs, ago)) + results = bal.balOraclePoolGetTimeWeightedAverage(poolId, queries) + + print(results[0]) + + +if __name__ == "__main__": + main() diff --git a/samples/poolCreation/poolCreationSample.py b/samples/poolCreation/poolCreationSample.py index f9e3663..432d92e 100644 --- a/samples/poolCreation/poolCreationSample.py +++ b/samples/poolCreation/poolCreationSample.py @@ -7,108 +7,127 @@ import webbrowser + def main(): - - if len(sys.argv) < 2: - print("Usage: python3", sys.argv[0], "/path/to/pool.json"); - quit(); - - pathToPool = sys.argv[1]; - if not os.path.isfile(pathToPool): - print("Path", pathToPool, "does not exist. Please enter a valid path.") - quit(); - - with open(pathToPool) as f: - pool = jstyleson.load(f) - - bal = balpy.balpy.balpy(pool["network"]); - gasFactor = 1.05; - gasSpeed = pool["gasSpeed"]; - gasPriceGweiOverride = pool["gasPriceOverride"]; - if gasPriceGweiOverride == "": - gasPriceGweiOverride = -1; - gasPriceGweiOverride = float(gasPriceGweiOverride); - - print(); - print("==============================================================") - print("================ Step 1: Check Token Balances ================") - print("==============================================================") - print(); - - tokens = list(pool["tokens"].keys()); - initialBalances = [pool["tokens"][token]["initialBalance"] for token in tokens]; - if not bal.erc20HasSufficientBalances(tokens, initialBalances): - print("Please fix your insufficient balance before proceeding."); - print("Quitting..."); - quit(); - - print(); - print("==============================================================") - print("============== Step 2: Approve Token Allowance ==============") - print("==============================================================") - print(); - - (tokensSorted, allowancesSorted) = bal.erc20GetTargetAllowancesFromPoolData(pool); - initialBalancesSorted = [pool["tokens"][token]["initialBalance"] for token in tokensSorted]; - # Async: Do [Approve]*N then [Wait]*N instead of [Approve, Wait]*N - bal.erc20AsyncEnforceSufficientVaultAllowances(tokensSorted, allowancesSorted, initialBalancesSorted, gasFactor, gasSpeed, gasPriceGweiOverride=gasPriceGweiOverride); - - print(); - print("==============================================================") - print("=============== Step 3: Create Pool in Factory ===============") - print("==============================================================") - print(); - creationHash = None; - if not "poolId" in pool.keys(): - txHash = bal.balCreatePoolInFactory(pool, gasFactor, gasSpeed, gasPriceGweiOverride=gasPriceGweiOverride); - if not txHash: - quit(); - poolId = bal.balGetPoolIdFromHash(txHash); - creationHash = txHash; - pool["poolId"] = poolId; - with open(pathToPool, 'w') as f: - json.dump(pool, f, indent=4); - else: - print("PoolId found in pool description. Skipping the pool factory!"); - poolId = pool["poolId"]; - - poolLink = bal.balGetLinkToFrontend(poolId); - if not poolLink == "": - webbrowser.open_new_tab(poolLink); - - print(); - print("==================================================================") - print("=============== Step 4: Add Initial Tokens to Pool ===============") - print("==================================================================") - print(); - try: - time.sleep(5); # sleep ensure that the RPC knows the new pool exists - txHash = bal.balJoinPoolInit(pool, poolId, gasPriceGweiOverride=gasPriceGweiOverride); - except Exception as e: - print("Joining pool failed!"); - print("Depending on the pool type, this could be due to you not being the pool owner"); - print("Caught exception:", e); - quit(); - - - - - print(); - print("==================================================================") - print("================== Step 5: Verify Pool Contract ==================") - print("==================================================================") - print(); - - if not creationHash is None: - command = bal.balGeneratePoolCreationArguments("0x" + poolId, creationHash=creationHash); - else: - command = bal.balGeneratePoolCreationArguments("0x" + poolId); - - print(command) - print() - print("If you need more complete instructions on what to do with this command, go to:"); - print("\thttps://dev.balancer.fi/resources/pools/verification"); - print() - -if __name__ == '__main__': - main(); + + if len(sys.argv) < 2: + print("Usage: python3", sys.argv[0], "/path/to/pool.json") + quit() + + pathToPool = sys.argv[1] + if not os.path.isfile(pathToPool): + print("Path", pathToPool, "does not exist. Please enter a valid path.") + quit() + + with open(pathToPool) as f: + pool = jstyleson.load(f) + + bal = balpy.balpy.balpy(pool["network"]) + gasFactor = 1.05 + gasSpeed = pool["gasSpeed"] + gasPriceGweiOverride = pool["gasPriceOverride"] + if gasPriceGweiOverride == "": + gasPriceGweiOverride = -1 + gasPriceGweiOverride = float(gasPriceGweiOverride) + + print() + print("==============================================================") + print("================ Step 1: Check Token Balances ================") + print("==============================================================") + print() + + tokens = list(pool["tokens"].keys()) + initialBalances = [pool["tokens"][token]["initialBalance"] for token in tokens] + if not bal.erc20HasSufficientBalances(tokens, initialBalances): + print("Please fix your insufficient balance before proceeding.") + print("Quitting...") + quit() + + print() + print("==============================================================") + print("============== Step 2: Approve Token Allowance ==============") + print("==============================================================") + print() + + (tokensSorted, allowancesSorted) = bal.erc20GetTargetAllowancesFromPoolData(pool) + initialBalancesSorted = [ + pool["tokens"][token]["initialBalance"] for token in tokensSorted + ] + # Async: Do [Approve]*N then [Wait]*N instead of [Approve, Wait]*N + bal.erc20AsyncEnforceSufficientVaultAllowances( + tokensSorted, + allowancesSorted, + initialBalancesSorted, + gasFactor, + gasSpeed, + gasPriceGweiOverride=gasPriceGweiOverride, + ) + + print() + print("==============================================================") + print("=============== Step 3: Create Pool in Factory ===============") + print("==============================================================") + print() + creationHash = None + if not "poolId" in pool.keys(): + txHash = bal.balCreatePoolInFactory( + pool, gasFactor, gasSpeed, gasPriceGweiOverride=gasPriceGweiOverride + ) + if not txHash: + quit() + poolId = bal.balGetPoolIdFromHash(txHash) + creationHash = txHash + pool["poolId"] = poolId + with open(pathToPool, "w") as f: + json.dump(pool, f, indent=4) + else: + print("PoolId found in pool description. Skipping the pool factory!") + poolId = pool["poolId"] + + poolLink = bal.balGetLinkToFrontend(poolId) + if not poolLink == "": + webbrowser.open_new_tab(poolLink) + + print() + print("==================================================================") + print("=============== Step 4: Add Initial Tokens to Pool ===============") + print("==================================================================") + print() + try: + time.sleep(5) + # sleep ensure that the RPC knows the new pool exists + txHash = bal.balJoinPoolInit( + pool, poolId, gasPriceGweiOverride=gasPriceGweiOverride + ) + except Exception as e: + print("Joining pool failed!") + print( + "Depending on the pool type, this could be due to you not being the pool owner" + ) + print("Caught exception:", e) + quit() + + print() + print("==================================================================") + print("================== Step 5: Verify Pool Contract ==================") + print("==================================================================") + print() + + if not creationHash is None: + command = bal.balGeneratePoolCreationArguments( + "0x" + poolId, creationHash=creationHash + ) + else: + command = bal.balGeneratePoolCreationArguments("0x" + poolId) + + print(command) + print() + print( + "If you need more complete instructions on what to do with this command, go to:" + ) + print("\thttps://dev.balancer.fi/resources/pools/verification") + print() + + +if __name__ == "__main__": + main() diff --git a/samples/poolVerification/poolVerification.py b/samples/poolVerification/poolVerification.py index 16e8b85..3d5bc91 100644 --- a/samples/poolVerification/poolVerification.py +++ b/samples/poolVerification/poolVerification.py @@ -1,28 +1,34 @@ import balpy + def main(): - network = "goerli" - poolId = "0xe867ad0a48e8f815dc0cda2cdb275e0f163a480b0002000000000000000001a0" - # On Ethereum and Ethereum testnets, you can pass creationHash=None - # On Polygon, you must pass the pool creation hash to generate verification params - creationHash = None; - # creationHash = "0x18c7e1c9235c6e93878e55a87ed249f9d0ceb9d12ee584794e92f80f7645686d"; - verbose = False; + network = "goerli" + poolId = "0xe867ad0a48e8f815dc0cda2cdb275e0f163a480b0002000000000000000001a0" + # On Ethereum and Ethereum testnets, you can pass creationHash=None + # On Polygon, you must pass the pool creation hash to generate verification params + creationHash = None + # creationHash = "0x18c7e1c9235c6e93878e55a87ed249f9d0ceb9d12ee584794e92f80f7645686d"; + verbose = False + + bal = balpy.balpy.balpy(network) + isVerified = bal.isContractVerified(poolId, verbose=verbose) + if not isVerified: + command = bal.balGeneratePoolCreationArguments( + poolId, verbose=verbose, creationHash=creationHash + ) + else: + print("Pool is already verified") + quit() + + print() + print(command) + print() + + print( + "If you need more complete instructions on what to do with this command, go to:" + ) + print("\thttps://dev.balancer.fi/resources/pools/verification\n") - bal = balpy.balpy.balpy(network); - isVerified = bal.isContractVerified(poolId, verbose=verbose); - if not isVerified: - command = bal.balGeneratePoolCreationArguments(poolId, verbose=verbose, creationHash=creationHash); - else: - print("Pool is already verified") - quit(); - - print() - print(command) - print() - print("If you need more complete instructions on what to do with this command, go to:"); - print("\thttps://dev.balancer.fi/resources/pools/verification\n"); - -if __name__ == '__main__': - main(); +if __name__ == "__main__": + main() diff --git a/samples/singleSwap/swapSample.py b/samples/singleSwap/swapSample.py index 68de1d1..46a3273 100644 --- a/samples/singleSwap/swapSample.py +++ b/samples/singleSwap/swapSample.py @@ -3,64 +3,67 @@ import os import json + def main(): - - if len(sys.argv) < 2: - print("Usage: python3", sys.argv[0], "/path/to/swap.json"); - quit(); - - pathToSwap = sys.argv[1]; - if not os.path.isfile(pathToSwap): - print("Path", pathToSwap, "does not exist. Please enter a valid path.") - quit(); - - with open(pathToSwap) as f: - data = json.load(f) - - gasFactor = 1.05; - gasSpeedApproval = "average"; - gasSpeedTrade = "average"; - - bal = balpy.balpy.balpy(data["network"]); - swap = data["swap"]; - - # determine the necessary allowance based on swap kind - maxSend = None; - kind = int(swap["kind"]) - if kind == 0: #GIVEN_IN - maxSend = swap["amount"]; - elif kind == 1: #GIVEN_OUT - maxSend = swap["limit"] - - - print(); - print("==============================================================") - print("================ Step 1: Check Token Balances ================") - print("==============================================================") - print(); - - tokens = [swap["assetIn"]]; - amountIn = [maxSend]; - if not bal.erc20HasSufficientBalances(tokens, amountIn): - print("Please fix your insufficient balance before proceeding.") - print("Quitting...") - quit(); - - print(); - print("==============================================================") - print("============== Step 2: Approve Token Allowance ===============") - print("==============================================================") - print(); - - bal.erc20AsyncEnforceSufficientVaultAllowances(tokens, amountIn, amountIn, gasFactor, gasSpeedApproval); - - print(); - print("==============================================================") - print("================== Step 3: Execute Swap Txn ==================") - print("==============================================================") - print(); - - txHash = bal.balDoSwap(swap, gasFactor=gasFactor, gasPriceSpeed=gasSpeedTrade); - -if __name__ == '__main__': - main(); \ No newline at end of file + + if len(sys.argv) < 2: + print("Usage: python3", sys.argv[0], "/path/to/swap.json") + quit() + + pathToSwap = sys.argv[1] + if not os.path.isfile(pathToSwap): + print("Path", pathToSwap, "does not exist. Please enter a valid path.") + quit() + + with open(pathToSwap) as f: + data = json.load(f) + + gasFactor = 1.05 + gasSpeedApproval = "average" + gasSpeedTrade = "average" + + bal = balpy.balpy.balpy(data["network"]) + swap = data["swap"] + + # determine the necessary allowance based on swap kind + maxSend = None + kind = int(swap["kind"]) + if kind == 0: # GIVEN_IN + maxSend = swap["amount"] + elif kind == 1: # GIVEN_OUT + maxSend = swap["limit"] + + print() + print("==============================================================") + print("================ Step 1: Check Token Balances ================") + print("==============================================================") + print() + + tokens = [swap["assetIn"]] + amountIn = [maxSend] + if not bal.erc20HasSufficientBalances(tokens, amountIn): + print("Please fix your insufficient balance before proceeding.") + print("Quitting...") + quit() + + print() + print("==============================================================") + print("============== Step 2: Approve Token Allowance ===============") + print("==============================================================") + print() + + bal.erc20AsyncEnforceSufficientVaultAllowances( + tokens, amountIn, amountIn, gasFactor, gasSpeedApproval + ) + + print() + print("==============================================================") + print("================== Step 3: Execute Swap Txn ==================") + print("==============================================================") + print() + + txHash = bal.balDoSwap(swap, gasFactor=gasFactor, gasPriceSpeed=gasSpeedTrade) + + +if __name__ == "__main__": + main() diff --git a/samples/stablePool/stablePoolGetAmp.py b/samples/stablePool/stablePoolGetAmp.py index a891780..1a8d8f6 100644 --- a/samples/stablePool/stablePoolGetAmp.py +++ b/samples/stablePool/stablePoolGetAmp.py @@ -1,14 +1,16 @@ import balpy + def main(): - network = "kovan" - - bal = balpy.balpy.balpy(network); - poolId = "0x6b15a01b5d46a5321b627bd7deef1af57bc629070000000000000000000000d4"; - (value, isUpdating, precision) = bal.balStablePoolGetAmplificationParameter(poolId); - print("value:", value) - print("isUpdating:", isUpdating) - print("precision:", precision) - -if __name__ == '__main__': - main(); \ No newline at end of file + network = "kovan" + + bal = balpy.balpy.balpy(network) + poolId = "0x6b15a01b5d46a5321b627bd7deef1af57bc629070000000000000000000000d4" + (value, isUpdating, precision) = bal.balStablePoolGetAmplificationParameter(poolId) + print("value:", value) + print("isUpdating:", isUpdating) + print("precision:", precision) + + +if __name__ == "__main__": + main() diff --git a/samples/stablePool/stablePoolUpdateAmp.py b/samples/stablePool/stablePoolUpdateAmp.py index bfcb017..6a7517d 100644 --- a/samples/stablePool/stablePoolUpdateAmp.py +++ b/samples/stablePool/stablePoolUpdateAmp.py @@ -1,16 +1,23 @@ import balpy import time + + def main(): - network = "kovan" + network = "kovan" + + bal = balpy.balpy.balpy(network) + + poolId = "0x6b15a01b5d46a5321b627bd7deef1af57bc629070000000000000000000000d4" + rawEndValue = 149 + # MIN_AMP = 1, MAX_AMP = 5000 + durationInSeconds = 600000 + # MIN_UPDATE_TIME = 1 day = 86400 seconds + + endTime = int(time.time()) + durationInSeconds + bal.balStablePoolStartAmplificationParameterUpdate( + poolId, rawEndValue, endTime, gasEstimateOverride=400000 + ) + - bal = balpy.balpy.balpy(network); - - poolId = "0x6b15a01b5d46a5321b627bd7deef1af57bc629070000000000000000000000d4"; - rawEndValue = 149; #MIN_AMP = 1, MAX_AMP = 5000 - durationInSeconds = 600000; #MIN_UPDATE_TIME = 1 day = 86400 seconds - - endTime = int(time.time()) + durationInSeconds; - bal.balStablePoolStartAmplificationParameterUpdate(poolId, rawEndValue, endTime, gasEstimateOverride=400000); - -if __name__ == '__main__': - main() \ No newline at end of file +if __name__ == "__main__": + main() diff --git a/samples/theGraph/getPools.py b/samples/theGraph/getPools.py index d3f1b86..235de75 100644 --- a/samples/theGraph/getPools.py +++ b/samples/theGraph/getPools.py @@ -2,32 +2,34 @@ import sys import balpy.graph.graph as balGraph + def main(): - - batch_size = 5; - print(); - - if len(sys.argv) < 2: - print("Usage: python", sys.argv[0], ""); - print("No network given; defaulting to mainnet Ethereum") - network = "mainnet" - else: - network = sys.argv[1]; - - networks = ["mainnet", "kovan", "polygon"]; - - if not network in networks: - print("Network", network, "is not supported!"); - print("Supported networks are:"); - for n in networks: - print("\t" + n); - print("Quitting") - quit(); - - verbose = True; - bg = balGraph.TheGraph(network) - pools = bg.getV2Pools(batch_size, verbose=verbose) - bg.printJson(pools) - -if __name__ == '__main__': - main(); + + batch_size = 5 + print() + + if len(sys.argv) < 2: + print("Usage: python", sys.argv[0], "") + print("No network given; defaulting to mainnet Ethereum") + network = "mainnet" + else: + network = sys.argv[1] + + networks = ["mainnet", "kovan", "polygon"] + + if not network in networks: + print("Network", network, "is not supported!") + print("Supported networks are:") + for n in networks: + print("\t" + n) + print("Quitting") + quit() + + verbose = True + bg = balGraph.TheGraph(network) + pools = bg.getV2Pools(batch_size, verbose=verbose) + bg.printJson(pools) + + +if __name__ == "__main__": + main()