Beispiel #1
0
    def buy_data_tokens(self, pool_address: str, amount: float,
                        max_OCEAN_amount: float, from_wallet: Wallet) -> str:
        """
        Buy data tokens from this pool, paying `max_OCEAN_amount_base` of OCEAN tokens.
        If total spent <= max_OCEAN_amount_base.
        - Caller is spending OCEAN tokens, and receiving `amount_base` DataTokens
        - OCEAN tokens are going into pool, DataTokens are going out of pool

        The transaction fails if total spent exceeds `max_OCEAN_amount_base`.

        :param pool_address: str address of pool contract
        :param amount: int number of data tokens to add to this pool in *base*
        :param max_OCEAN_amount:
        :param from_wallet:
        :return: str transaction id/hash
        """
        ocean_tok = DataToken(self.ocean_address)
        ocean_tok.approve_tokens(pool_address,
                                 max_OCEAN_amount,
                                 from_wallet,
                                 wait=True)

        dtoken_address = self.get_token_address(pool_address)
        pool = BPool(pool_address)
        return pool.swapExactAmountOut(
            tokenIn_address=self.ocean_address,  # entering pool
            maxAmountIn_base=to_base_18(max_OCEAN_amount),  # ""
            tokenOut_address=dtoken_address,  # leaving pool
            tokenAmountOut_base=to_base_18(amount),  # ""
            maxPrice_base=2**255,  # here we limit by max_num_OCEAN, not price
            from_wallet=from_wallet,
        )
Beispiel #2
0
    def buy_data_tokens(
        self, pool_address: str, amount: int, max_OCEAN_amount: int, from_wallet: Wallet
    ) -> str:
        """
        Buy data tokens from this pool, paying `max_OCEAN_amount` of OCEAN tokens.
        If total spent <= max_OCEAN_amount.
        - Caller is spending OCEAN tokens, and receiving `amount` DataTokens
        - OCEAN tokens are going into pool, DataTokens are going out of pool

        The transaction fails if total spent exceeds `max_OCEAN_amount`.

        :param pool_address: str address of pool contract
        :param amount: int number of data tokens to add to this pool in *base*
        :param max_OCEAN_amount:
        :param from_wallet:
        :return: str transaction id/hash
        """
        ocean_tok = DataToken(self.web3, self.ocean_address)
        if ocean_tok.balanceOf(from_wallet.address) < max_OCEAN_amount:
            raise InsufficientBalance("Insufficient funds for buying DataTokens!")
        if ocean_tok.allowance(from_wallet.address, pool_address) < max_OCEAN_amount:
            ocean_tok.approve(pool_address, max_OCEAN_amount, from_wallet)

        dtoken_address = self.get_token_address(pool_address)
        pool = BPool(self.web3, pool_address)
        return pool.swapExactAmountOut(
            tokenIn_address=self.ocean_address,  # entering pool
            maxAmountIn=max_OCEAN_amount,  # ""
            tokenOut_address=dtoken_address,  # leaving pool
            tokenAmountOut=amount,  # ""
            maxPrice=2 ** 255,  # here we limit by max_num_OCEAN, not price
            from_wallet=from_wallet,
        )
Beispiel #3
0
    def add_liquidity_finalized(self, pool_address: str, bpt_amount_base: int,
                                max_data_token_amount_base: int,
                                max_OCEAN_amount_base: int,
                                from_wallet: Wallet) -> str:
        """
        Add liquidity to a pool that's been finalized.
        Buy bpt_amount_base tokens from the pool, spending DataTokens and OCEAN tokens
        as needed and up to the specified maximum amounts.

        :param pool_address: str address of pool contract
        :param bpt_amount_base: int number of pool shares to receive for adding the liquidity
        :param max_data_token_amount_base: int maximum amount of Data tokens to go into the pool
        :param max_OCEAN_amount_base: int maximum amount of OCEAN tokens to go into the pool
        :param from_wallet: Wallet instance
        :return: str transaction id/hash
        """
        assert self._is_valid_pool(pool_address)
        dt_address = self.get_token_address(pool_address)
        dt = BToken(dt_address)
        dt.approve(pool_address,
                   max_data_token_amount_base,
                   from_wallet=from_wallet)

        OCEAN = BToken(self.ocean_address)
        OCEAN.approve(pool_address,
                      max_OCEAN_amount_base,
                      from_wallet=from_wallet)

        pool = BPool(pool_address)
        return pool.joinPool(
            bpt_amount_base,
            [max_data_token_amount_base, max_OCEAN_amount_base],
            from_wallet=from_wallet)
Beispiel #4
0
    def sell_data_tokens(self, pool_address: str, amount_base: int,
                         min_OCEAN_amount_base: int,
                         from_wallet: Wallet) -> str:
        """
        Sell data tokens into this pool, receive `min_OCEAN_amount_base` of OCEAN tokens.
        If total income >= min_OCEAN_amount_base
        - Caller is spending DataTokens, and receiving OCEAN tokens
        - DataTokens are going into pool, OCEAN tokens are going out of pool

        The transaction fails if total income does not reach `min_OCEAN_amount_base`

        :param pool_address: str address of pool contract
        :param amount_base: int number of data tokens to add to this pool in *base*
        :param min_OCEAN_amount_base:
        :param from_wallet:
        :return: str transaction id/hash
        """
        dtoken_address = self.get_token_address(pool_address)
        dt = BToken(dtoken_address)
        dt.approve(pool_address, amount_base, from_wallet=from_wallet)

        pool = BPool(pool_address)
        return pool.swapExactAmountIn(
            tokenIn_address=dtoken_address,  # entering pool
            tokenAmountIn_base=amount_base,  # ""
            tokenOut_address=self.ocean_address,  # leaving pool
            minAmountOut_base=min_OCEAN_amount_base,  # ""
            maxPrice_base=2**255,  # here we limit by max_num_OCEAN, not price
            from_wallet=from_wallet,
        )
Beispiel #5
0
    def sell_data_tokens(
        self, pool_address: str, amount: int, min_OCEAN_amount: int, from_wallet: Wallet
    ) -> str:
        """
        Sell data tokens into this pool, receive `min_OCEAN_amount` of OCEAN tokens.
        If total income >= min_OCEAN_amount
        - Caller is spending DataTokens, and receiving OCEAN tokens
        - DataTokens are going into pool, OCEAN tokens are going out of pool

        The transaction fails if total income does not reach `min_OCEAN_amount`

        :param pool_address: str address of pool contract
        :param amount: int number of data tokens to add to this pool
        :param min_OCEAN_amount:
        :param from_wallet:
        :return: str transaction id/hash
        """
        dtoken_address = self.get_token_address(pool_address)
        dt = BToken(self.web3, dtoken_address)
        if dt.balanceOf(from_wallet.address) < amount:
            raise InsufficientBalance("Insufficient funds for selling DataTokens!")
        if dt.allowance(from_wallet.address, pool_address) < amount:
            dt.approve(pool_address, amount, from_wallet=from_wallet)

        pool = BPool(self.web3, pool_address)
        return pool.swapExactAmountIn(
            tokenIn_address=dtoken_address,  # entering pool
            tokenAmountIn=amount,  # ""
            tokenOut_address=self.ocean_address,  # leaving pool
            minAmountOut=min_OCEAN_amount,  # ""
            maxPrice=2 ** 255,  # here we limit by max_num_OCEAN, not price
            from_wallet=from_wallet,
        )
Beispiel #6
0
    def _add_liquidity(
        self,
        pool_address: str,
        token_address: str,
        amount_base: int,
        from_wallet: Wallet,
    ) -> str:
        assert amount_base >= 0
        if amount_base == 0:
            return ""

        pool = BPool(pool_address)
        token = BToken(token_address)
        assert token.balanceOf(from_wallet.address) >= amount_base, (
            f"Insufficient funds, {amount_base} tokens are required of token address {token_address}, "
            f"but only a balance of {token.balanceOf(from_wallet.address)} is available."
        )

        tx_id = token.approve(pool_address, amount_base, from_wallet)
        r = token.get_tx_receipt(tx_id)
        if not r or r.status != 1:
            return 0

        pool_amount = pool.joinswapExternAmountIn(token_address, amount_base,
                                                  0, from_wallet)
        return pool_amount
Beispiel #7
0
def run_compute(did, consumer_wallet, algorithm_file, pool_address, order_id=None):
    ocean = Ocean(config=Config(options_dict=get_config_dict()))

    # Get asset DDO/metadata and service
    asset = ocean.assets.resolve(did)
    service = asset.get_service(ServiceTypes.CLOUD_COMPUTE)

    # check the price in ocean tokens
    num_ocean = ocean.pool.calcInGivenOut(pool_address, ocean.OCEAN_address, asset.data_token_address, 1.0)

    # buy datatoken to be able to run the compute service
    dt = DataToken(asset.asset_id)
    dt_balance = dt.token_balance(consumer_wallet.address)
    if dt_balance < 1.0:
        pool = BPool(pool_address)
        txid = ocean.pool.buy_data_tokens(pool_address, 1.0, num_ocean+0.1, consumer_wallet)
        receipt = pool.get_tx_receipt(txid)
        if not receipt or receipt.status != 1:
            print(f'buying data token failed: txId={txid}, txReceipt={receipt}')
            return None, None

    tx_id = order_id
    if not tx_id:
        tx_id = ocean.assets.pay_for_service(1.0, asset.data_token_address, did, service.index,
                                             fee_receiver=asset.publisher, from_wallet=consumer_wallet)

    # load python algorithm to run in the compute job
    with open(algorithm_file) as f:
        algorithm_text = f.read()

    # whether to publish the algorithm results as an Ocean assets
    output_dict = {
        'publishOutput': False,
        'publishAlgorithmLog': False,
    }
    # start the compute job (submit the compute service request)
    algorithm_meta = AlgorithmMetadata(
        {
            'language': 'python',
            'rawcode': algorithm_text,
            'container': {
                'tag': 'latest',
                'image': 'amancevice/pandas',
                'entrypoint': 'python $ALGO'
            }
        }
    )
    job_id = ocean.compute.start(did, consumer_wallet, tx_id, algorithm_meta=algorithm_meta, output=output_dict)

    # check the status of the compute job
    status = ocean.compute.status(did, job_id, consumer_wallet)
    print(f'status of compute job {job_id}: {status}')

    # get the result of the compute run
    result = ocean.compute.result(did, job_id, consumer_wallet)
    print(f'got result of compute job {job_id}: {result}')
    return job_id, status
Beispiel #8
0
    def _is_valid_pool(self, pool_address) -> bool:
        pool = BPool(pool_address)
        if pool.getNumTokens() != 2:
            return False

        # dt should be 0th token, OCEAN should be 1st token
        if pool.getCurrentTokens()[1] != self.ocean_address:
            return False
        return True
Beispiel #9
0
    def get_token_price(self, pool_address: str) -> float:
        """

        :param pool_address: str the address of the pool contract
        :return: int price of data token in terms of OCEAN tokens
        """
        dtoken_address = self.get_token_address(pool_address)
        pool = BPool(pool_address)
        return from_base_18(
            pool.getSpotPrice(tokenIn_address=self.ocean_address,
                              tokenOut_address=dtoken_address))
Beispiel #10
0
    def get_token_address(
        self, pool_address: str, pool: BPool = None, validate=True
    ) -> str:
        """Returns the address of this pool's datatoken."""
        if not pool:
            if validate:
                assert self._is_valid_pool(pool_address)

            pool = BPool(pool_address)

        tokens = pool.getCurrentTokens()
        return tokens[0] if tokens[0] != self.ocean_address else tokens[1]
Beispiel #11
0
    def get_user_balances(self, user_address, from_block):
        current_block = self.web3.eth.block_number
        pool = BPool(self.web3, None)

        pools = self.get_all_pools(from_block, chunk_size=5000, include_balance=False)
        join_logs = pool.get_join_logs(
            from_block, current_block, user_address, this_pool_only=False
        )
        join_logs = [lg for lg in join_logs if lg.address in pools]

        balances = {
            lg.address: DataToken(self.web3, lg.address).balanceOf(user_address)
            for lg in join_logs
        }
        return balances
Beispiel #12
0
    def _remove_liquidity(self, pool_address: str, token_address: str,
                          amount_base: int, max_pool_shares_base: int,
                          from_wallet: Wallet) -> str:
        assert amount_base >= 0
        if amount_base == 0:
            return ''

        assert max_pool_shares_base > 0, f''

        pool = BPool(pool_address)
        if pool.balanceOf(from_wallet.address) == 0:
            return ''

        return pool.exitswapExternAmountOut(token_address, amount_base,
                                            max_pool_shares_base, from_wallet)
Beispiel #13
0
    def get_user_balances(self, user_address, from_block):
        web3 = Web3Provider.get_web3()
        current_block = web3.eth.blockNumber
        pool = BPool(None)

        pools = self.get_all_pools(from_block, chunk_size=5000, include_balance=False)
        join_logs = pool.get_join_logs(
            web3, from_block, current_block, user_address, this_pool_only=False
        )
        join_logs = [l for l in join_logs if l.address in pools]

        balances = {
            l.address: DataToken(l.address).token_balance(user_address)
            for l in join_logs
        }
        return balances
Beispiel #14
0
def main(did, pool_address, order_tx_id=None):
ocean = Ocean(config=Config(options_dict=get_config_dict()))
publisher = Wallet(ocean.web3, private_key='0xc594c6e5def4bab63ac29eed19a134c130388f74f019bc74b8f4389df2837a58')  # 0xe2DD09d719Da89e5a3D0F2549c7E24566e947260
#consumer = Wallet(ocean.web3, private_key='0x9bf5d7e4978ed5206f760e6daded34d657572bd49fa5b3fe885679329fb16b16')  # 0x068Ed00cF0441e4829D9784fCBe7b9e26D4BD8d0
publisher_wallet = Wallet(ocean.web3, private_key=os.getenv('Publisher_Key')) #addr: 0xc966Ba2a41888B6B4c5273323075B98E27B9F364
consumer = Wallet(ocean.web3, private_key=os.getenv('Consumer_Key')) #addr: 0xEF5dc33A53DD2ED3F670B53F07cEc5ADD4D80504

    if not (did and pool_address):
        metadata_file = './examples/data/metadata.json'
        with open(metadata_file) as f:
            metadata = json.load(f)

        asset, pool = publish_asset(metadata, publisher)
        #Dataset asset created successfully: did=did:op:784Cc17176533cc962cf659B9f49349ba6F9df3b, datatoken=0x784Cc17176533cc962cf659B9f49349ba6F9df3b
        #pool_address = 0x3490DDd035B2e1DA30Af09AB6090Bf71fdb94898
    else:
        asset = ocean.assets.resolve(did)
        pool = BPool(pool_address)

    if not asset:
        print(f'publish asset failed, cannot continue with running compute.')
        return

    print(f'Requesting compute using asset {asset.did} and pool {pool.address}')
    algo_file = './examples/data/algorithm.py'
    order_tx_id=

    job_id, status = run_compute(asset.did, consumer, algo_file, pool.address, order_tx_id)
    print(f'Compute started on asset {asset.did}: job_id={job_id}, status={status}')
Beispiel #15
0
def main(did, pool_address, order_tx_id=None):
    ocean = Ocean(config=Config(options_dict=get_config_dict()))
    publisher = Wallet(
        ocean.web3,
        private_key="0xc594c6e5def4bab63ac29eed19a134c130388f74f019bc74b8f4389df2837a58",
    )  # 0xe2DD09d719Da89e5a3D0F2549c7E24566e947260
    consumer = Wallet(
        ocean.web3,
        private_key="0x9bf5d7e4978ed5206f760e6daded34d657572bd49fa5b3fe885679329fb16b16",
    )  # 0x068Ed00cF0441e4829D9784fCBe7b9e26D4BD8d0

    if not (did and pool_address):
        metadata_file = "./examples/data/metadata.json"
        with open(metadata_file) as f:
            metadata = json.load(f)

        asset, pool = publish_asset(metadata, publisher)
    else:
        asset = ocean.assets.resolve(did)
        pool = BPool(pool_address)

    if not asset:
        print("publish asset failed, cannot continue with running compute.")
        return

    print(f"Requesting compute using asset {asset.did} and pool {pool.address}")
    algo_file = "./examples/data/algorithm.py"
    job_id, status = run_compute(
        asset.did, consumer, algo_file, pool.address, order_tx_id
    )
    print(f"Compute started on asset {asset.did}: job_id={job_id}, status={status}")
Beispiel #16
0
def test1(network, alice_wallet):
    bfactory_address = get_bfactory_address(network)
    bfactory = BFactory(bfactory_address)

    pool_address = bfactory.newBPool(from_wallet=alice_wallet)
    pool = BPool(pool_address)
    assert isinstance(pool, BPool)
Beispiel #17
0
    def get_all_pools(self,
                      from_block=0,
                      chunk_size=1000,
                      include_balance=False):
        web3 = Web3Provider.get_web3()
        current_block = web3.eth.blockNumber

        bfactory = BFactory(self.bfactory_address)
        logs = bfactory.get_event_logs(
            "BPoolRegistered",
            from_block,
            current_block,
            {},
            web3=web3,
            chunk_size=chunk_size,
        )
        if include_balance:
            pools = sorted(
                [(
                    lg.args.bpoolAddress,
                    from_base_18(
                        BPool(lg.args.bpoolAddress).getBalance(
                            self.ocean_address)),
                ) for lg in logs],
                key=lambda x: x[1],
                reverse=True,
            )
        else:
            pools = {lg.args.bpoolAddress for lg in logs}

        return pools
Beispiel #18
0
 def calcSingleOutGivenPoolIn(self, pool_address: str,
                              token_out_address: str, pool_shares: float):
     pool = BPool(pool_address)
     return from_base_18(
         pool.calcSingleInGivenPoolOut(
             pool.getBalance(token_out_address),
             pool.getDenormalizedWeight(token_out_address),
             pool.totalSupply(), pool.getTotalDenormalizedWeight(),
             to_base_18(pool_shares), pool.getSwapFee()))
Beispiel #19
0
    def _add_liquidity(self, pool_address: str, token_address: str,
                       amount_base: int, from_wallet: Wallet) -> str:
        assert amount_base >= 0
        if amount_base == 0:
            return ''

        pool = BPool(pool_address)
        token = BToken(token_address)
        assert token.balanceOf(from_wallet.address) >= amount_base, \
            f'Insufficient funds, {amount_base} tokens are required of token address {token_address}, ' \
            f'but only a balance of {token.balanceOf(from_wallet.address)} is available.'

        token.approve(pool_address, amount_base, from_wallet)

        pool_amount = pool.joinswapExternAmountIn(token_address, amount_base,
                                                  0, from_wallet)
        return pool_amount
Beispiel #20
0
def test_bpool_creation(network, alice_wallet):
    """Test the creation of a Balancer Pool from BFactory (happy flow)."""
    bfactory_address = get_bfactory_address(network)
    bfactory = BFactory(bfactory_address)

    pool_address = bfactory.newBPool(from_wallet=alice_wallet)
    pool = BPool(pool_address)
    assert isinstance(pool, BPool)
Beispiel #21
0
def test_bpool_creation(web3, config, alice_wallet):
    """Test the creation of a Balancer Pool from BFactory (happy flow)."""
    bfactory_address = get_bfactory_address(config.address_file, web3=web3)
    bfactory = BFactory(web3, bfactory_address)

    pool_address = bfactory.newBPool(from_wallet=alice_wallet)
    pool = BPool(web3, pool_address)
    assert isinstance(pool, BPool)
Beispiel #22
0
def _deployBPool(network: str, from_wallet: Wallet) -> BPool:
    """Helper function to deploy a pool."""
    factory_address = get_bfactory_address(network)
    factory = BFactory(factory_address)
    pool_address = factory.newBPool(from_wallet=from_wallet)
    pool = BPool(pool_address)

    return pool
Beispiel #23
0
 def calcPoolOutGivenSingleIn(self, pool_address: str,
                              token_in_address: str,
                              token_in_amount: float):
     pool = BPool(pool_address)
     return from_base_18(
         pool.calcPoolOutGivenSingleIn(
             pool.getBalance(token_in_address),
             pool.getDenormalizedWeight(token_in_address),
             pool.totalSupply(), pool.getTotalDenormalizedWeight(),
             to_base_18(token_in_amount), pool.getSwapFee()))
Beispiel #24
0
 def calcOutGivenIn(self, pool_address: str, token_in_address: str,
                    token_out_address: str, token_in_amount: float):
     pool = BPool(pool_address)
     out_amount = pool.calcOutGivenIn(
         pool.getBalance(token_in_address),
         pool.getDenormalizedWeight(token_in_address),
         pool.getBalance(token_out_address),
         pool.getDenormalizedWeight(token_out_address),
         to_base_18(token_in_amount), pool.getSwapFee())
     return from_base_18(out_amount)
def test_setSwapFee_fails(network, alice_wallet, alice_address, bob_wallet,
                          bob_address):
    factory = BFactory(get_bfactory_address(network))
    pool_address = factory.newBPool(alice_wallet)
    pool = BPool(pool_address)
    with pytest.raises(Exception):
        pool.setSwapFee(to_base_18(0.011),
                        from_wallet=bob_wallet)  # not ok, bob isn't controller
    pool.setController(bob_address, from_wallet=alice_wallet)
    pool.setSwapFee(to_base_18(0.011), from_wallet=bob_wallet)  # ok now
Beispiel #26
0
def _deployBPool(web3: Web3, address_file: str, network: str,
                 from_wallet: Wallet) -> BPool:
    """Helper function to deploy a pool."""
    factory_address = get_bfactory_address(address_file, network)
    factory = BFactory(web3, factory_address)
    pool_address = factory.newBPool(from_wallet=from_wallet)
    pool = BPool(web3, pool_address)

    return pool
Beispiel #27
0
    def add_liquidity_finalized(
        self,
        pool_address: str,
        bpt_amount: int,
        max_data_token_amount: int,
        max_OCEAN_amount: int,
        from_wallet: Wallet,
    ) -> str:
        """
        Add liquidity to a pool that's been finalized.
        Buy bpt_amount tokens from the pool, spending DataTokens and OCEAN tokens
        as needed and up to the specified maximum amounts.

        :param pool_address: str address of pool contract
        :param bpt_amount: int number of pool shares to receive for adding the liquidity
        :param max_data_token_amount: int maximum amount of Data tokens to go into the pool
        :param max_OCEAN_amount: int maximum amount of OCEAN tokens to go into the pool
        :param from_wallet: Wallet instance
        :return: str transaction id/hash
        """
        assert self._is_valid_pool(pool_address), "The pool address is not valid."
        dt_address = self.get_token_address(pool_address)
        dt = BToken(self.web3, dt_address)
        if dt.balanceOf(from_wallet.address) < max_data_token_amount:
            raise InsufficientBalance(
                f"Insufficient funds for adding liquidity for {dt.address} datatoken!"
            )
        if dt.allowance(from_wallet.address, pool_address) < max_data_token_amount:
            dt.approve(pool_address, max_data_token_amount, from_wallet=from_wallet)

        OCEAN = BToken(self.web3, self.ocean_address)
        if OCEAN.balanceOf(from_wallet.address) < max_OCEAN_amount:
            raise InsufficientBalance(
                f"Insufficient funds for adding liquidity for {OCEAN.address} OCEAN token!"
            )
        if OCEAN.allowance(from_wallet.address, pool_address) < max_OCEAN_amount:
            OCEAN.approve(pool_address, max_OCEAN_amount, from_wallet=from_wallet)

        pool = BPool(self.web3, pool_address)
        return pool.joinPool(
            bpt_amount,
            [max_data_token_amount, max_OCEAN_amount],
            from_wallet=from_wallet,
        )
Beispiel #28
0
def test_setSwapFee_fails(network, config, web3, alice_wallet, alice_address,
                          bob_wallet, bob_address):
    """Tests that someone who isn't a controller can not set the swap fee."""
    factory = BFactory(web3, get_bfactory_address(config.address_file,
                                                  network))
    pool_address = factory.newBPool(alice_wallet)
    pool = BPool(web3, pool_address)
    with pytest.raises(Exception):
        pool.setSwapFee(to_wei("0.011"),
                        from_wallet=bob_wallet)  # not ok, bob isn't controller
    pool.setController(bob_address, from_wallet=alice_wallet)
    pool.setSwapFee(to_wei("0.011"), from_wallet=bob_wallet)  # ok now
Beispiel #29
0
 def calcSingleOutGivenPoolIn(
     self, pool_address: str, token_out_address: str, pool_shares: int
 ) -> int:
     pool = BPool(self.web3, pool_address)
     return pool.calcSingleInGivenPoolOut(
         pool.getBalance(token_out_address),
         pool.getDenormalizedWeight(token_out_address),
         pool.totalSupply(),
         pool.getTotalDenormalizedWeight(),
         pool_shares,
         pool.getSwapFee(),
     )
Beispiel #30
0
 def calcPoolInGivenSingleOut(
     self, pool_address: str, token_out_address: str, token_out_amount: int
 ):
     pool = BPool(self.web3, pool_address)
     return pool.calcPoolInGivenSingleOut(
         pool.getBalance(token_out_address),
         pool.getDenormalizedWeight(token_out_address),
         pool.totalSupply(),
         pool.getTotalDenormalizedWeight(),
         token_out_amount,
         pool.getSwapFee(),
     )