def test_evm_broken_contract_call(): contract = Contract(broken_contract, Contract.EVM) with raises(AException): result = contract.call('IdentityBroken.main', '1') print(result)
def test_evm_contract_call(): contract = Contract(aer_identity_contract, Contract.EVM) result = contract.call('main', '1') assert result is not None assert result.get('out')
def test_ring_broken_contract_call(): contract = Contract(broken_contract, Contract.RING) with raises(AException): result = contract.call('IdentityBroken.main', '1')
class ContractNative(object): def __init__(self, **kwargs): """ Initialize a ContractNative object :param client: an instance of NodeClient :param source: the source code of the contract :param compiler: an instance of the CompilerClient :param address: address of the currently deployed contract (optional) :param gas: Gas to be used for all contract interactions (optional) :param fee: fee to be used for all contract interactions (optional) :param gas_price: Gas price to be used for all contract interactions (optional) :param account: Account to be used for contract deploy and contract calls (optional) :param use_dry_run: use dry run for all method calls except for payable and stateful methods (default: True) """ if 'client' in kwargs: self.contract = Contract(kwargs.get('client')) else: raise ValueError("client is not provided") self.compiler = kwargs.get('compiler', None) if self.compiler is None: raise ValueError("Compiler is not provided") else: if isinstance(self.compiler, str): self.compiler = compiler.CompilerClient(self.compiler) self.source = kwargs.get('source', None) if self.source is not None: self.bytecode = self.compiler.compile(self.source).bytecode self.aci = self.compiler.aci(self.source) else: raise ValueError("contract source not provided") self.contract_name = self.aci.encoded_aci.contract.name self.gas = kwargs.get('gas', defaults.CONTRACT_GAS) self.gas_price = kwargs.get('gas_price', defaults.CONTRACT_GAS_PRICE) self.fee = kwargs.get('fee', defaults.FEE) self.contract_amount = kwargs.get('amount', defaults.CONTRACT_AMOUNT) self.use_dry_run = kwargs.get('use_dry_run', True) address = kwargs.get('address', None) if address: self.at(address) self.account = kwargs.get('account', None) if self.account and type(self.account) is not signing.Account: raise TypeError( "Invalid account type. Use `class Account` for creating an account" ) self.sophia_transformer = SophiaTransformation() self.__generate_methods() def __generate_methods(self): if self.aci: for f in self.aci.encoded_aci.contract.functions: self.__add_contract_method( namedtupled.map( { "name": f.name, "doc": f"Contract Method {f.name}", "arguments": f.arguments, "returns": f.returns, "stateful": f.stateful, "payable": f.payable }, _nt_name="ContractMethod")) def __encode_method_args(self, method, *args): if len(args) != len(method.arguments): raise ValueError( f"Invalid number of arguments. Expected {len(method.arguments)}, Provided {len(args)}" ) transformed_args = [] for i, val in enumerate(args): transformed_args.append( self.sophia_transformer.convert_to_sophia( val, namedtupled.reduce(method.arguments[i].type), self.aci.encoded_aci)) return self.compiler.encode_calldata(self.source, method.name, *transformed_args).calldata def __decode_method_args(self, method, args): return self.sophia_transformer.convert_to_py( namedtupled.reduce(args), namedtupled.reduce(method.returns), self.aci.encoded_aci) def __add_contract_method(self, method): def contract_method(*args, **kwargs): calldata = self.__encode_method_args(method, *args) use_dry_run = kwargs.get('use_dry_run', self.use_dry_run) call_info = None if method.stateful or method.payable or not use_dry_run: tx_hash = self.call(method.name, calldata, **kwargs).hash call_info = namedtupled.reduce( self.contract.get_call_object(tx_hash)) call_info['tx_hash'] = tx_hash call_info = namedtupled.map(call_info) else: call_info = self.call_static(method.name, calldata, **kwargs) if call_info.result == 'error': raise ValueError(call_info.reason) call_info = call_info.call_obj decoded_call_result = self.compiler.decode_call_result( self.source, method.name, call_info.return_value, call_info.return_type) return call_info, self.__decode_method_args( method, decoded_call_result) contract_method.__name__ = method.name contract_method.__doc__ = method.doc setattr(self, contract_method.__name__, contract_method) def __process_options(self, **kwargs): gas = self.gas if kwargs.get('gas') is None else kwargs.get('gas') gas_price = self.gas_price if kwargs.get( 'gas_price') is None else kwargs.get('gas_price') amount = self.contract_amount if kwargs.get( 'amount') is None else kwargs.get('amount') fee = self.fee if kwargs.get('fee') is None else kwargs.get('fee') account = self.account if kwargs.get( 'account') is None else kwargs.get('account') if account is None: raise ValueError( "Please provide an account to sign contract call transactions. You can set a default account using 'set_account' method" ) if account and type(account) is not signing.Account: raise TypeError( "Invalid account type. Use `class Account` for creating an account" ) return namedtupled.map( { "gas": gas, "gas_price": gas_price, "amount": amount, "fee": fee, "account": account }, _nt_name="ContractOptions") def at(self, address): """ Set contract address """ if not address or not utils.is_valid_hash( address, prefix=identifiers.CONTRACT_ID): raise ValueError(f"Invalid contract address {address}") if not self.contract.is_deployed(address): raise ValueError("Contract not deployed") self.address = address self.deployed = True def set_account(self, account): if account is None: raise ValueError("Account can not be of None type") if type(account) is not signing.Account: raise TypeError( "Invalid account type. Use `class Account` for creating an account" ) self.account = account def deploy(self, *arguments, entrypoint="init", deposit=defaults.CONTRACT_DEPOSIT, vm_version=None, abi_version=None, tx_ttl=defaults.TX_TTL, **kwargs): """ Create a contract and deploy it to the chain :return: the transaction """ method_list = list( filter(lambda f: f.name == entrypoint, self.aci.encoded_aci.contract.functions)) calldata = None if len(method_list) == 1 and method_list[0].name == entrypoint: calldata = self.__encode_method_args(method_list[0], *arguments) else: calldata = self.compiler.encode_calldata(self.source, entrypoint, *arguments).calldata opts = self.__process_options(**kwargs) tx = self.contract.create(opts.account, self.bytecode, calldata, opts.amount, deposit, opts.gas, opts.gas_price, opts.fee, vm_version, abi_version, tx_ttl) self.at(tx.metadata.contract_id) return tx def call(self, function, calldata, abi_version=None, tx_ttl=defaults.TX_TTL, **kwargs): """ call a contract method :return: the transaction """ opts = self.__process_options(**kwargs) return self.contract.call(self.address, opts.account, function, calldata, opts.amount, opts.gas, opts.gas_price, opts.fee, abi_version, tx_ttl) def call_static(self, function, calldata, abi_version=None, tx_ttl=defaults.TX_TTL, top=None, **kwargs): """ call-static a contract method :return: the call object """ opts = self.__process_options(**kwargs) return self.contract.call_static(self.address, function, calldata, opts.account.get_address(), opts.amount, opts.gas, opts.gas_price, opts.fee, abi_version, tx_ttl, top)
def test_sophia_broken_contract_call(): contract = Contract(broken_contract, Contract.SOPHIA) with raises(ContractError): result = contract.call('IdentityBroken.main', '1') print(result)
def test_sophia_contract_call(): contract = Contract(aer_identity_contract, Contract.SOPHIA) result = contract.call('main', '1') assert result is not None assert result.out