def __init__( self, jsonrpc_client, manager_address, poll_timeout=DEFAULT_POLL_TIMEOUT, ): # pylint: disable=too-many-arguments contract = jsonrpc_client.new_contract( CONTRACT_MANAGER.get_contract_abi(CONTRACT_CHANNEL_MANAGER), to_normalized_address(manager_address), ) self.proxy = ContractProxy(jsonrpc_client, contract) if not is_binary_address(manager_address): raise ValueError('manager_address must be a valid address') check_address_has_code(jsonrpc_client, manager_address, 'Channel Manager') CONTRACT_MANAGER.check_contract_version( self.version(), CONTRACT_CHANNEL_MANAGER, ) self.address = manager_address self.client = jsonrpc_client self.poll_timeout = poll_timeout self.open_channel_transactions = dict()
def call_minting_method(client: JSONRPCClient, proxy: ContractProxy, contract_method: MintingMethod, args: List[Any]) -> TransactionHash: """ Try to mint tokens by calling `contract_method` on `proxy`. Raises: MintFailed if anything goes wrong. """ method = contract_method.value gas_limit = proxy.estimate_gas("latest", method, *args) if gas_limit is None: raise MintFailed(f"Gas estimation failed. Make sure the token has a " f"method named {method} with the expected signature.") try: tx_hash = proxy.transact(method, gas_limit, *args) except (RaidenError, ValueError) as e: # Re-raise TransactionAlreadyPending etc. as MintFailed. # Since the token minting api is not standardized, we also catch ValueErrors # that might fall through ContractProxy.transact()'s exception handling. raise MintFailed(str(e)) receipt = client.poll(tx_hash) if check_transaction_threw(receipt=receipt): raise MintFailed( f"Call to contract method {method}: Transaction failed.") return tx_hash
def __init__( self, jsonrpc_client, registry_address, poll_timeout=DEFAULT_POLL_TIMEOUT, ): # pylint: disable=too-many-arguments contract = jsonrpc_client.new_contract( CONTRACT_MANAGER.get_contract_abi(CONTRACT_REGISTRY), address_encoder(registry_address), ) self.proxy = ContractProxy(jsonrpc_client, contract) if not is_binary_address(registry_address): raise ValueError('registry_address must be a valid address') check_address_has_code(jsonrpc_client, registry_address, 'Registry') CONTRACT_MANAGER.check_contract_version( self.proxy.contract.functions.contract_version().call(), CONTRACT_REGISTRY ) self.address = registry_address self.client = jsonrpc_client self.poll_timeout = poll_timeout self.node_address = privatekey_to_address(self.client.privkey) self.address_to_channelmanager = dict() self.token_to_channelmanager = dict()
def __init__( self, jsonrpc_client, channel_address, poll_timeout=DEFAULT_POLL_TIMEOUT, ): contract = jsonrpc_client.new_contract( CONTRACT_MANAGER.get_contract_abi(CONTRACT_NETTING_CHANNEL), to_normalized_address(channel_address), ) self.proxy = ContractProxy(jsonrpc_client, contract) self.address = channel_address self.client = jsonrpc_client self.poll_timeout = poll_timeout # Prevents concurrent deposit, close, or settle operations on the same channel self.channel_operations_lock = RLock() self.client = jsonrpc_client self.node_address = privatekey_to_address(self.client.privkey) CONTRACT_MANAGER.check_contract_version( self.proxy.contract.functions.contract_version().call(), CONTRACT_NETTING_CHANNEL, ) # check we are a participant of the given channel self.detail() self._check_exists()
def __init__( self, jsonrpc_client, discovery_address, poll_timeout=DEFAULT_POLL_TIMEOUT, ): contract = jsonrpc_client.new_contract( CONTRACT_MANAGER.get_contract_abi(CONTRACT_ENDPOINT_REGISTRY), to_normalized_address(discovery_address), ) self.proxy = ContractProxy(jsonrpc_client, contract) if not is_binary_address(discovery_address): raise ValueError('discovery_address must be a valid address') check_address_has_code(jsonrpc_client, discovery_address, 'Discovery') CONTRACT_MANAGER.check_contract_version( self.version(), CONTRACT_ENDPOINT_REGISTRY, ) self.address = discovery_address self.client = jsonrpc_client self.poll_timeout = poll_timeout self.not_found_address = NULL_ADDRESS
def __init__( self, jsonrpc_client, discovery_address, contract_manager: ContractManager, ): contract = jsonrpc_client.new_contract( contract_manager.get_contract_abi(CONTRACT_ENDPOINT_REGISTRY), to_normalized_address(discovery_address), ) proxy = ContractProxy(jsonrpc_client, contract) if not is_binary_address(discovery_address): raise ValueError('discovery_address must be a valid address') check_address_has_code(jsonrpc_client, discovery_address, 'Discovery') compare_contract_versions( proxy=proxy, expected_version=contract_manager.contracts_version, contract_name=CONTRACT_ENDPOINT_REGISTRY, address=discovery_address, ) self.address = discovery_address self.node_address = jsonrpc_client.address self.client = jsonrpc_client self.not_found_address = NULL_ADDRESS self.proxy = proxy
def all_contract_events(rpc: JSONRPCClient, contract_address: str, abi, start_block: Union[str, int] = 0, end_block: Union[str, int] = 'latest') -> List[Dict]: """Find and decode all events for a deployed contract given its `contract_address` and `abi`. Args: rpc: client instance. contract_address: hex encoded contract address. abi: the contract's ABI. start_block: read event-logs starting from this block number (default: 0). end_block: read event-logs up to this block number (default: 'latest'). Returns: A list of all events from the given contract. """ events_raw = all_contract_events_raw(rpc, contract_address, start_block=start_block, end_block=end_block) events = list() for event_encoded in events_raw: event = ContractProxy.decode_event(abi, event_encoded) events.append(event) return events
def __init__( self, jsonrpc_client, discovery_address, ): contract = jsonrpc_client.new_contract( CONTRACT_MANAGER.get_contract_abi(CONTRACT_ENDPOINT_REGISTRY), to_normalized_address(discovery_address), ) proxy = ContractProxy(jsonrpc_client, contract) if not is_binary_address(discovery_address): raise ValueError('discovery_address must be a valid address') check_address_has_code(jsonrpc_client, discovery_address, 'Discovery') try: is_valid_version = compare_versions( proxy.contract.functions.contract_version().call(), EXPECTED_CONTRACTS_VERSION, ) if not is_valid_version: raise ContractVersionMismatch('Incompatible ABI for Discovery') except BadFunctionCallOutput: raise AddressWrongContract('') self.address = discovery_address self.client = jsonrpc_client self.not_found_address = NULL_ADDRESS self.proxy = proxy
def __init__( self, jsonrpc_client: JSONRPCClient, token_address: TokenAddress, contract_manager: ContractManager, ) -> None: contract = jsonrpc_client.new_contract( contract_manager.get_contract_abi(CONTRACT_CUSTOM_TOKEN), Address(token_address)) proxy = ContractProxy(jsonrpc_client, contract) if not is_binary_address(token_address): raise ValueError("token_address must be a valid address") check_address_has_code(jsonrpc_client, Address(token_address), "Token", expected_code=None) self.address = token_address self.client = jsonrpc_client self.node_address = jsonrpc_client.address self.proxy = proxy self.token_lock: RLock = RLock()
def get_address_from_rns(self, address=None) -> str: contract = self.client.new_contract(RNS_RESOLVER_ABI, RNS_RESOLVER_ADDRESS) proxy = ContractProxy(self.client, contract) eip137hash = namehash(address) resolved_address = proxy.contract.functions.addr(eip137hash).call() return resolved_address
def __init__( self, jsonrpc_client, token_address, ): contract = jsonrpc_client.new_contract( CONTRACT_MANAGER.get_contract_abi(CONTRACT_HUMAN_STANDARD_TOKEN), to_normalized_address(token_address), ) self.proxy = ContractProxy(jsonrpc_client, contract) if not is_binary_address(token_address): raise ValueError('token_address must be a valid address') check_address_has_code(jsonrpc_client, token_address, 'Token') self.address = token_address self.client = jsonrpc_client
def new_contract_proxy(self, abi: List[Dict[str, Any]], contract_address: Address) -> ContractProxy: """ Return a proxy for interacting with a smart contract. Args: abi: The contract interface as defined by the json. contract_address: The contract's address. """ return ContractProxy(self, contract=self.new_contract(abi, contract_address))
def __init__( self, jsonrpc_client, token_address, poll_timeout=DEFAULT_POLL_TIMEOUT, ): contract = jsonrpc_client.new_contract( CONTRACT_MANAGER.get_contract_abi(CONTRACT_HUMAN_STANDARD_TOKEN), address_encoder(token_address), ) self.proxy = ContractProxy(jsonrpc_client, contract) if not is_binary_address(token_address): raise ValueError('token_address must be a valid address') check_address_has_code(jsonrpc_client, token_address, 'Token') self.address = token_address self.client = jsonrpc_client self.poll_timeout = poll_timeout
def new_contract_proxy(self, contract_interface, contract_address: Address): """ Return a proxy for interacting with a smart contract. Args: contract_interface: The contract interface as defined by the json. address: The contract's address. """ return ContractProxy(self, contract=self.new_contract( contract_interface, contract_address))
def new_contract_proxy(self, contract_interface, address): """ Return a proxy for interacting with a smart contract. Args: contract_interface: The contract interface as defined by the json. address: The contract's address. """ return ContractProxy( self.sender, contract_interface, address, self.eth_call, self.send_transaction, self.eth_estimateGas, )
def __init__(self, jsonrpc_client, token_address, contract_manager: ContractManager): contract = jsonrpc_client.new_contract( contract_manager.get_contract_abi(CONTRACT_HUMAN_STANDARD_TOKEN), to_normalized_address(token_address), ) proxy = ContractProxy(jsonrpc_client, contract) if not is_binary_address(token_address): raise ValueError("token_address must be a valid address") check_address_has_code(jsonrpc_client, token_address, "Token") self.address = token_address self.client = jsonrpc_client self.node_address = jsonrpc_client.address self.proxy = proxy
def mint_token_if_balance_low( token_contract: ContractProxy, target_address: str, min_balance: int, fund_amount: int, gas_limit: int, mint_msg: str, no_action_msg: str = None, ) -> Optional[TransactionHash]: """ Check token balance and mint if below minimum """ balance = token_contract.contract.functions.balanceOf(target_address).call() if balance < min_balance: mint_amount = fund_amount - balance log.debug(mint_msg, address=target_address, amount=mint_amount) return token_contract.transact("mintFor", gas_limit, mint_amount, target_address) else: if no_action_msg: log.debug(no_action_msg, balance=balance) return None
def __init__( self, jsonrpc_client, token_address, ): contract_manager = ContractManager(CONTRACTS_PRECOMPILED_PATH) contract = jsonrpc_client.new_contract( contract_manager.get_contract_abi(CONTRACT_HUMAN_STANDARD_TOKEN), to_normalized_address(token_address), ) proxy = ContractProxy(jsonrpc_client, contract) if not is_binary_address(token_address): raise ValueError('token_address must be a valid address') check_address_has_code(jsonrpc_client, token_address, 'Token') self.address = token_address self.client = jsonrpc_client self.node_address = privatekey_to_address(jsonrpc_client.privkey) self.proxy = proxy
class NettingChannel: def __init__( self, jsonrpc_client, channel_address, poll_timeout=DEFAULT_POLL_TIMEOUT, ): contract = jsonrpc_client.new_contract( CONTRACT_MANAGER.get_contract_abi(CONTRACT_NETTING_CHANNEL), to_normalized_address(channel_address), ) self.proxy = ContractProxy(jsonrpc_client, contract) self.address = channel_address self.client = jsonrpc_client self.poll_timeout = poll_timeout # Prevents concurrent deposit, close, or settle operations on the same channel self.channel_operations_lock = RLock() self.client = jsonrpc_client self.node_address = privatekey_to_address(self.client.privkey) CONTRACT_MANAGER.check_contract_version( self.proxy.contract.functions.contract_version().call(), CONTRACT_NETTING_CHANNEL, ) # check we are a participant of the given channel self.detail() self._check_exists() def _check_exists(self): check_address_has_code(self.client, self.address, 'Netting Channel') def _call_and_check_result(self, function_name: str): fn = getattr(self.proxy.contract.functions, function_name) try: call_result = fn().call() except BadFunctionCallOutput as e: raise AddressWithoutCode(str(e)) if call_result == b'': self._check_exists() raise RuntimeError( "Call to '{}' returned nothing".format(function_name), ) return call_result def token_address(self): """ Returns the type of token that can be transferred by the channel. Raises: AddressWithoutCode: If the channel was settled prior to the call. """ address = self._call_and_check_result('tokenAddress') return to_canonical_address(address) def detail(self): """ Returns a dictionary with the details of the netting channel. Raises: AddressWithoutCode: If the channel was settled prior to the call. """ data = self._call_and_check_result('addressAndBalance') settle_timeout = self.settle_timeout() our_address = privatekey_to_address(self.client.privkey) if to_canonical_address(data[0]) == our_address: return { 'our_address': to_canonical_address(data[0]), 'our_balance': data[1], 'partner_address': to_canonical_address(data[2]), 'partner_balance': data[3], 'settle_timeout': settle_timeout, } if to_canonical_address(data[2]) == our_address: return { 'our_address': to_canonical_address(data[2]), 'our_balance': data[3], 'partner_address': to_canonical_address(data[0]), 'partner_balance': data[1], 'settle_timeout': settle_timeout, } raise ValueError( 'We [{}] are not a participant of the given channel ({}, {})'. format( pex(our_address), data[0], data[2], )) def settle_timeout(self): """ Returns the netting channel settle_timeout. Raises: AddressWithoutCode: If the channel was settled prior to the call. """ return self._call_and_check_result('settleTimeout') def opened(self): """ Returns the block in which the channel was created. Raises: AddressWithoutCode: If the channel was settled prior to the call. """ return self._call_and_check_result('opened') def closed(self): """ Returns the block in which the channel was closed or 0. Raises: AddressWithoutCode: If the channel was settled prior to the call. """ return self._call_and_check_result('closed') def closing_address(self): """ Returns the address of the closer, if the channel is closed, None otherwise. Raises: AddressWithoutCode: If the channel was settled prior to the call. """ closer = self.proxy.contract.functions.closingAddress().call() if closer: return to_canonical_address(closer) return None def can_transfer(self): """ Returns True if the channel is opened and the node has deposit in it. Note: Having a deposit does not imply having a balance for off-chain transfers. Raises: AddressWithoutCode: If the channel was settled prior to the call. """ closed = self.closed() if closed != 0: return False return self.detail()['our_balance'] > 0 def set_total_deposit(self, total_deposit): """ Set the total deposit of token in the channel. Raises: AddressWithoutCode: If the channel was settled prior to the call. ChannelBusyError: If the channel is busy with another operation RuntimeError: If the netting channel token address is empty. """ if not isinstance(total_deposit, int): raise ValueError('total_deposit needs to be an integral number.') log.info( 'set_total_deposit called', node=pex(self.node_address), contract=pex(self.address), amount=total_deposit, ) if not self.channel_operations_lock.acquire(blocking=False): raise ChannelBusyError( f'Channel with address {self.address} is ' f'busy with another ongoing operation.', ) with releasing(self.channel_operations_lock): transaction_hash = self.proxy.transact( 'setTotalDeposit', total_deposit, ) self.client.poll( unhexlify(transaction_hash), timeout=self.poll_timeout, ) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: log.critical( 'set_total_deposit failed', node=pex(self.node_address), contract=pex(self.address), ) self._check_exists() raise TransactionThrew('Deposit', receipt_or_none) log.info( 'set_total_deposit successful', node=pex(self.node_address), contract=pex(self.address), amount=total_deposit, ) def close(self, nonce, transferred_amount, locked_amount, locksroot, extra_hash, signature): """ Close the channel using the provided balance proof. Raises: AddressWithoutCode: If the channel was settled prior to the call. ChannelBusyError: If the channel is busy with another operation. """ log.info( 'close called', node=pex(self.node_address), contract=pex(self.address), nonce=nonce, transferred_amount=transferred_amount, locked_amount=locked_amount, locksroot=encode_hex(locksroot), extra_hash=encode_hex(extra_hash), signature=encode_hex(signature), ) if not self.channel_operations_lock.acquire(blocking=False): raise ChannelBusyError( f'Channel with address {self.address} is ' f'busy with another ongoing operation.', ) with releasing(self.channel_operations_lock): transaction_hash = self.proxy.transact( 'close', nonce, transferred_amount, locked_amount, locksroot, extra_hash, signature, ) self.client.poll(unhexlify(transaction_hash), timeout=self.poll_timeout) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: log.critical( 'close failed', node=pex(self.node_address), contract=pex(self.address), nonce=nonce, transferred_amount=transferred_amount, locked_amount=locked_amount, locksroot=encode_hex(locksroot), extra_hash=encode_hex(extra_hash), signature=encode_hex(signature), ) self._check_exists() raise TransactionThrew('Close', receipt_or_none) log.info( 'close successful', node=pex(self.node_address), contract=pex(self.address), nonce=nonce, transferred_amount=transferred_amount, locked_amount=locked_amount, locksroot=encode_hex(locksroot), extra_hash=encode_hex(extra_hash), signature=encode_hex(signature), ) def update_transfer( self, nonce, transferred_amount, locked_amount, locksroot, extra_hash, signature, ): if signature: log.info( 'updateTransfer called', node=pex(self.node_address), contract=pex(self.address), nonce=nonce, transferred_amount=transferred_amount, locked_amount=locked_amount, locksroot=encode_hex(locksroot), extra_hash=encode_hex(extra_hash), signature=encode_hex(signature), ) transaction_hash = self.proxy.transact( 'updateTransfer', nonce, transferred_amount, locked_amount, locksroot, extra_hash, signature, ) self.client.poll( unhexlify(transaction_hash), timeout=self.poll_timeout, ) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: log.critical( 'updateTransfer failed', node=pex(self.node_address), contract=pex(self.address), nonce=nonce, transferred_amount=transferred_amount, locked_amount=locked_amount, locksroot=encode_hex(locksroot), extra_hash=encode_hex(extra_hash), signature=encode_hex(signature), ) self._check_exists() raise TransactionThrew('Update Transfer', receipt_or_none) log.info( 'updateTransfer successful', node=pex(self.node_address), contract=pex(self.address), nonce=nonce, transferred_amount=transferred_amount, locked_amount=locked_amount, locksroot=encode_hex(locksroot), extra_hash=encode_hex(extra_hash), signature=encode_hex(signature), ) def unlock(self, unlock_proof): log.info( 'unlock called', node=pex(self.node_address), contract=pex(self.address), ) if isinstance(unlock_proof.lock_encoded, messages.Lock): raise ValueError( 'unlock must be called with a lock encoded `.as_bytes`') merkleproof_encoded = b''.join(unlock_proof.merkle_proof) transaction_hash = self.proxy.transact( 'unlock', unlock_proof.lock_encoded, merkleproof_encoded, unlock_proof.secret, ) self.client.poll(unhexlify(transaction_hash), timeout=self.poll_timeout) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: log.critical( 'unlock failed', node=pex(self.node_address), contract=pex(self.address), lock=unlock_proof, ) self._check_exists() raise TransactionThrew('unlock', receipt_or_none) log.info( 'unlock successful', node=pex(self.node_address), contract=pex(self.address), lock=unlock_proof, ) def settle(self): """ Settle the channel. Raises: ChannelBusyError: If the channel is busy with another operation """ log.info( 'settle called', node=pex(self.node_address), ) if not self.channel_operations_lock.acquire(blocking=False): raise ChannelBusyError( f'Channel with address {self.address} is ' f'busy with another ongoing operation', ) with releasing(self.channel_operations_lock): transaction_hash = self.proxy.transact('settle', ) self.client.poll(unhexlify(transaction_hash), timeout=self.poll_timeout) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: log.info( 'settle failed', node=pex(self.node_address), contract=pex(self.address), ) self._check_exists() raise TransactionThrew('Settle', receipt_or_none) log.info( 'settle successful', node=pex(self.node_address), contract=pex(self.address), ) def all_events_filter( self, from_block: typing.BlockSpecification = None, to_block: typing.BlockSpecification = None, ) -> Filter: """ Install a new filter for all the events emitted by the current netting channel contract Return: Filter: The filter instance. """ return self.client.new_filter( contract_address=self.proxy.contract_address, topics=None, from_block=from_block, to_block=to_block, )
class ChannelManager: def __init__( self, jsonrpc_client, manager_address, poll_timeout=DEFAULT_POLL_TIMEOUT, ): # pylint: disable=too-many-arguments contract = jsonrpc_client.new_contract( CONTRACT_MANAGER.get_contract_abi(CONTRACT_CHANNEL_MANAGER), to_normalized_address(manager_address), ) self.proxy = ContractProxy(jsonrpc_client, contract) if not is_binary_address(manager_address): raise ValueError('manager_address must be a valid address') check_address_has_code(jsonrpc_client, manager_address, 'Channel Manager') CONTRACT_MANAGER.check_contract_version( self.version(), CONTRACT_CHANNEL_MANAGER, ) self.address = manager_address self.client = jsonrpc_client self.poll_timeout = poll_timeout self.open_channel_transactions = dict() def token_address(self) -> Address: """ Return the token of this manager. """ token_address = self.proxy.contract.functions.tokenAddress().call() return to_canonical_address(token_address) def new_netting_channel(self, other_peer: Address, settle_timeout: int) -> Address: """ Creates and deploys a new netting channel contract. Args: other_peer: The peer to open the channel with. settle_timeout: The settle timout to use for this channel. Returns: The address of the new netting channel. """ if not is_binary_address(other_peer): raise ValueError('The other_peer must be a valid address') invalid_timeout = (settle_timeout < NETTINGCHANNEL_SETTLE_TIMEOUT_MIN or settle_timeout > NETTINGCHANNEL_SETTLE_TIMEOUT_MAX) if invalid_timeout: raise InvalidSettleTimeout( 'settle_timeout must be in range [{}, {}]'.format( NETTINGCHANNEL_SETTLE_TIMEOUT_MIN, NETTINGCHANNEL_SETTLE_TIMEOUT_MAX, )) local_address = privatekey_to_address(self.client.privkey) if local_address == other_peer: raise SamePeerAddress( 'The other peer must not have the same address as the client.') # Prevent concurrent attempts to open a channel with the same token and # partner address. if other_peer not in self.open_channel_transactions: new_open_channel_transaction = AsyncResult() self.open_channel_transactions[ other_peer] = new_open_channel_transaction try: transaction_hash = self._new_netting_channel( other_peer, settle_timeout) except Exception as e: new_open_channel_transaction.set_exception(e) raise else: new_open_channel_transaction.set(transaction_hash) finally: self.open_channel_transactions.pop(other_peer, None) else: # All other concurrent threads should block on the result of opening this channel self.open_channel_transactions[other_peer].get() netting_channel_results_encoded = self.proxy.contract.functions.getChannelWith( to_checksum_address(other_peer), ).call( {'from': to_checksum_address(self.client.sender)}) # address is at index 0 netting_channel_address_encoded = netting_channel_results_encoded if not netting_channel_address_encoded: log.error( 'netting_channel_address failed', peer1=pex(local_address), peer2=pex(other_peer), ) raise RuntimeError('netting_channel_address failed') netting_channel_address_bin = to_canonical_address( netting_channel_address_encoded) log.info( 'new_netting_channel called', peer1=pex(local_address), peer2=pex(other_peer), netting_channel=pex(netting_channel_address_bin), ) return netting_channel_address_bin def _new_netting_channel(self, other_peer, settle_timeout): if self.channel_exists(other_peer): raise DuplicatedChannelError( 'Channel with given partner address already exists') transaction_hash = self.proxy.transact( 'newChannel', other_peer, settle_timeout, ) if not transaction_hash: raise RuntimeError('open channel transaction failed') self.client.poll(unhexlify(transaction_hash), timeout=self.poll_timeout) if check_transaction_threw(self.client, transaction_hash): raise DuplicatedChannelError('Duplicated channel') return transaction_hash def channels_addresses(self) -> List[Tuple[Address, Address]]: # for simplicity the smart contract return a shallow list where every # second item forms a tuple channel_flat_encoded = self.proxy.contract.functions.getChannelsParticipants( ).call() channel_flat = [ to_canonical_address(channel) for channel in channel_flat_encoded ] # [a,b,c,d] -> [(a,b),(c,d)] channel_iter = iter(channel_flat) return list(zip(channel_iter, channel_iter)) def channels_by_participant(self, participant_address: Address) -> List[Address]: """ Return a list of channel address that `participant_address` is a participant. """ address_list = self.proxy.contract.functions.nettingContractsByAddress( to_checksum_address(participant_address), ).call( {'from': to_checksum_address(self.client.sender)}) return [to_canonical_address(address) for address in address_list] def channel_exists(self, participant_address: Address) -> bool: existing_channel = self.proxy.contract.functions.getChannelWith( to_checksum_address(participant_address), ).call( {'from': to_checksum_address(self.client.sender)}) exists = False if existing_channel != NULL_ADDRESS: exists = self.proxy.contract.functions.contractExists( to_checksum_address(existing_channel), ).call( {'from': to_checksum_address(self.client.sender)}) return exists def channelnew_filter( self, from_block: BlockSpecification = 0, to_block: BlockSpecification = 'latest', ) -> Filter: """ Install a new filter for ChannelNew events. Args: from_block: Create filter starting from this block number (default: 0). to_block: Create filter stopping at this block number (default: 'latest'). Return: The filter instance. """ topics = [CONTRACT_MANAGER.get_event_id(EVENT_CHANNEL_NEW)] channel_manager_address_bin = self.proxy.contract_address return self.client.new_filter( channel_manager_address_bin, topics=topics, from_block=from_block, to_block=to_block, ) def version(self): return self.proxy.contract.functions.contract_version().call()
class Registry: def __init__( self, jsonrpc_client, registry_address, poll_timeout=DEFAULT_POLL_TIMEOUT, ): # pylint: disable=too-many-arguments contract = jsonrpc_client.new_contract( CONTRACT_MANAGER.get_contract_abi(CONTRACT_REGISTRY), address_encoder(registry_address), ) self.proxy = ContractProxy(jsonrpc_client, contract) if not is_binary_address(registry_address): raise ValueError('registry_address must be a valid address') check_address_has_code(jsonrpc_client, registry_address, 'Registry') CONTRACT_MANAGER.check_contract_version( self.proxy.contract.functions.contract_version().call(), CONTRACT_REGISTRY ) self.address = registry_address self.client = jsonrpc_client self.poll_timeout = poll_timeout self.node_address = privatekey_to_address(self.client.privkey) self.address_to_channelmanager = dict() self.token_to_channelmanager = dict() def manager_address_by_token(self, token_address): """ Return the channel manager address for the given token or None if there is no correspoding address. """ address = self.proxy.contract.functions.channelManagerByToken( to_checksum_address(token_address), ).call() if address == b'': check_address_has_code(self.client, self.address) return None return address_decoder(address) def add_token(self, token_address): if not is_binary_address(token_address): raise ValueError('token_address must be a valid address') log.info( 'add_token called', node=pex(self.node_address), token_address=pex(token_address), registry_address=pex(self.address), ) transaction_hash = self.proxy.transact( 'addToken', self.address, token_address, ) self.client.poll(unhexlify(transaction_hash), timeout=self.poll_timeout) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: log.info( 'add_token failed', node=pex(self.node_address), token_address=pex(token_address), registry_address=pex(self.address), ) raise TransactionThrew('AddToken', receipt_or_none) manager_address = self.manager_address_by_token(token_address) if manager_address is None: log.info( 'add_token failed and check_transaction_threw didnt detect it', node=pex(self.node_address), token_address=pex(token_address), registry_address=pex(self.address), ) raise RuntimeError('channelManagerByToken failed') log.info( 'add_token sucessful', node=pex(self.node_address), token_address=pex(token_address), registry_address=pex(self.address), manager_address=pex(manager_address), ) return manager_address def token_addresses(self): addresses = self.proxy.contract.functions.tokenAddresses().call() return [ address_decoder(address) for address in addresses ] def manager_addresses(self): addresses = self.proxy.contract.functions.channelManagerAddresses().call() return [ address_decoder(address) for address in addresses ] def tokenadded_filter(self, from_block=None, to_block=None) -> Filter: topics = [CONTRACT_MANAGER.get_event_id(EVENT_TOKEN_ADDED)] registry_address_bin = self.proxy.contract_address return self.client.new_filter( registry_address_bin, topics=topics, from_block=from_block, to_block=to_block, ) def manager(self, manager_address): """ Return a proxy to interact with a ChannelManagerContract. """ if not is_binary_address(manager_address): raise ValueError('manager_address must be a valid address') if manager_address not in self.address_to_channelmanager: manager = ChannelManager( self.client, manager_address, self.poll_timeout, ) token_address = manager.token_address() self.token_to_channelmanager[token_address] = manager self.address_to_channelmanager[manager_address] = manager return self.address_to_channelmanager[manager_address] def manager_by_token(self, token_address): """ Find the channel manager for `token_address` and return a proxy to interact with it. If the token is not already registered it raises `EthNodeCommunicationError`, since we try to instantiate a Channel manager with an empty address. """ if not is_binary_address(token_address): raise ValueError('token_address must be a valid address') if token_address not in self.token_to_channelmanager: check_address_has_code(self.client, token_address) # check that the token exists manager_address = self.manager_address_by_token(token_address) if manager_address is None: raise NoTokenManager( 'Manager for token 0x{} does not exist'.format(hexlify(token_address)) ) manager = ChannelManager( self.client, manager_address, self.poll_timeout, ) self.token_to_channelmanager[token_address] = manager self.address_to_channelmanager[manager_address] = manager return self.token_to_channelmanager[token_address]
class Token: def __init__( self, jsonrpc_client, token_address, ): contract = jsonrpc_client.new_contract( CONTRACT_MANAGER.get_contract_abi(CONTRACT_HUMAN_STANDARD_TOKEN), to_normalized_address(token_address), ) self.proxy = ContractProxy(jsonrpc_client, contract) if not is_binary_address(token_address): raise ValueError('token_address must be a valid address') check_address_has_code(jsonrpc_client, token_address, 'Token') self.address = token_address self.client = jsonrpc_client def approve(self, contract_address, allowance): """ Aprove `contract_address` to transfer up to `deposit` amount of token. """ # TODO: check that `contract_address` is a netting channel and that # `self.address` is one of the participants (maybe add this logic into # `NettingChannel` and keep this straight forward) transaction_hash = self.proxy.transact( 'approve', contract_address, allowance, ) self.client.poll(unhexlify(transaction_hash)) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: user_balance = self.balance_of(self.client.sender) # If the balance is zero, either the smart contract doesnt have a # balanceOf function or the actual balance is zero if user_balance == 0: msg = ( "Approve failed. \n" "Your account balance is 0 (zero), either the smart " "contract is not a valid ERC20 token or you don't have funds " "to use for openning a channel. ") raise TransactionThrew(msg, receipt_or_none) # The approve call failed, check the user has enough balance # (assuming the token smart contract may check for the maximum # allowance, which is not necessarily the case) elif user_balance < allowance: msg = ( 'Approve failed. \n' 'Your account balance is {}, nevertheless the call to ' 'approve failed. Please make sure the corresponding smart ' 'contract is a valid ERC20 token.').format(user_balance) raise TransactionThrew(msg, receipt_or_none) # If the user has enough balance, warn the user the smart contract # may not have the approve function. else: msg = ( 'Approve failed. \n' 'Your account balance is {}, the request allowance is {}. ' 'The smart contract may be rejecting your request for the ' 'lack of balance.').format(user_balance, allowance) raise TransactionThrew(msg, receipt_or_none) def balance_of(self, address): """ Return the balance of `address`. """ return self.proxy.contract.functions.balanceOf( to_checksum_address(address), ).call() def transfer(self, to_address, amount): transaction_hash = self.proxy.transact( 'transfer', to_checksum_address(to_address), amount, ) self.client.poll(unhexlify(transaction_hash)) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: raise TransactionThrew('Transfer', receipt_or_none)
class Discovery: """On chain smart contract raiden node discovery: allows registering endpoints (host, port) for your ethereum-/raiden-address and looking up endpoints for other ethereum-/raiden-addressess. """ def __init__( self, jsonrpc_client, discovery_address, poll_timeout=DEFAULT_POLL_TIMEOUT, ): contract = jsonrpc_client.new_contract( CONTRACT_MANAGER.get_contract_abi(CONTRACT_ENDPOINT_REGISTRY), to_normalized_address(discovery_address), ) self.proxy = ContractProxy(jsonrpc_client, contract) if not is_binary_address(discovery_address): raise ValueError('discovery_address must be a valid address') check_address_has_code(jsonrpc_client, discovery_address, 'Discovery') CONTRACT_MANAGER.check_contract_version( self.version(), CONTRACT_ENDPOINT_REGISTRY, ) self.address = discovery_address self.client = jsonrpc_client self.poll_timeout = poll_timeout self.not_found_address = NULL_ADDRESS def register_endpoint(self, node_address, endpoint): if node_address != self.client.sender: raise ValueError("node_address doesnt match this node's address") transaction_hash = self.proxy.transact( 'registerEndpoint', endpoint, ) self.client.poll( unhexlify(transaction_hash), timeout=self.poll_timeout, ) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: raise TransactionThrew('Register Endpoint', receipt_or_none) def endpoint_by_address(self, node_address_bin): node_address_hex = to_checksum_address(node_address_bin) endpoint = self.proxy.contract.functions.findEndpointByAddress( node_address_hex, ).call() if endpoint == '': raise UnknownAddress('Unknown address {}'.format(pex(node_address_bin))) return endpoint def address_by_endpoint(self, endpoint): address = self.proxy.contract.functions.findAddressByEndpoint(endpoint).call() if address == self.not_found_address: # the 0 address means nothing found return None return to_canonical_address(address) def version(self): return self.proxy.contract.functions.contract_version().call()