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