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, )
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, )
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)
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, )
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, )
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
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
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
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))
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]
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
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)
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
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}')
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}")
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)
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
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()))
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
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)
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)
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
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()))
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
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
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, )
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
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(), )
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(), )