def contract_call(ctx, deploy_descriptor, function, params, return_type): try: with open(deploy_descriptor) as fp: contract = json.load(fp) source = contract.get('source') bytecode = contract.get('bytecode') address = contract.get('address') kp, _ = _keypair() contract = Contract(source, bytecode=bytecode, address=address, client=_epoch_cli()) result = contract.tx_call(kp, function, params, gas=40000000) _pp([ ('Contract address', contract.address), ('Gas price', result.gas_price), ('Gas used', result.gas_used), ('Return value (encoded)', result.return_value), ]) if result.return_type == 'ok': value, remote_type = contract.decode_data( result.return_value, return_type) _pp([ ('Return value', value), ('Return remote type', remote_type), ]) pass except Exception as e: print(e)
def contract_compile(contract_file): try: with open(contract_file) as fp: code = fp.read() contract = Contract(Contract.SOPHIA, client=_epoch_cli()) result = contract.compile(code) _pp([("bytecode", result)]) except Exception as e: print(e)
def test_sophia_contract_tx_call(): contract = Contract(aer_identity_contract, Contract.SOPHIA) address, tx = contract.tx_create_wait(KEYPAIR, gas=1000) print("contract: ", address) print("tx contract: ", tx) result = contract.tx_call(address, KEYPAIR, 'main', '42') assert result is not None assert result.return_type == 'ok' assert result.return_value.lower() == f'0x{hex(42)[2:].zfill(64).lower()}'
def contract_compile(contract_file): try: with open(contract_file) as fp: c = fp.read() print(c) contract = Contract(fp.read(), Contract.SOPHIA, client=_epoch_cli()) result = contract.compile('') _pp([("contract", result)]) except Exception as e: print(e)
def contract_deploy(contract_file, gas): try: with open(contract_file) as fp: contract = Contract(fp.read(), Contract.SOPHIA, client=_epoch_cli()) kp, _ = _keypair() address, tx = contract.tx_create(kp, gas=gas) _pp([ ("Contract address", address), ("Transaction hash", tx.tx_hash), ]) except Exception as e: print(e)
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 contract_deploy(contract_file, gas): """ Deploy a contract to the chain and create a deploy descriptor with the contract informations that can be use to invoke the contract later on. The generated descriptor will be created in the same folde of the contract source file. Multiple deploy of the same contract file will generate different deploy descriptor """ try: with open(contract_file) as fp: code = fp.read() contract = Contract(code, client=_epoch_cli()) kp, _ = _keypair() tx = contract.tx_create(kp, gas=gas) # save the contract data contract_data = { 'source': contract.source_code, 'bytecode': contract.bytecode, 'address': contract.address, 'transaction': tx.tx_hash, 'owner': kp.get_address(), 'created_at': datetime.now().isoformat('T') } # write the contract data to a file deploy_descriptor = f"{contract_file}.deploy.{contract.address[3:]}.json" with open(deploy_descriptor, 'w') as fw: json.dump(contract_data, fw, indent=2) _pp([ ("Contract address", contract.address), ("Transaction hash", tx.tx_hash), ("Deploy descriptor", deploy_descriptor), ]) except Exception as e: print(e)
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_broken_contract_compile(): contract = Contract(broken_contract, Contract.EVM) with raises(AException): result = contract.compile('')
def test_evm_encode_calldata(): contract = Contract(aer_identity_contract, Contract.EVM) result = contract.encode_calldata('main', '1') assert result is not None assert result == 'main1'
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_evm_contract_compile(): contract = Contract(aer_identity_contract, Contract.EVM) result = contract.compile() assert result is not None assert result.startswith('0x')
def test_sophia_contract_compile(): contract = Contract(aer_identity_contract, Contract.SOPHIA) result = contract.compile('') assert result is not None assert result.startswith('0x')
def test_sophia_broken_encode_calldata(): contract = Contract(broken_contract, Contract.SOPHIA) with raises(ContractError): result = contract.encode_calldata('IdentityBroken.main', '1') print(result)
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_compile(): contract = Contract(broken_contract, Contract.SOPHIA) with raises(ContractError): result = contract.compile('') print(result)
def test_sophia_encode_calldata(): contract = Contract(aer_identity_contract, Contract.SOPHIA) result = contract.encode_calldata('main', '1') assert result is not None assert result == 'main1'
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
def test_evm_broken_encode_calldata(): contract = Contract(broken_contract, Contract.EVM) #with raises(AException): result = contract.encode_calldata('IdentityBroken.main', '1')
def test_ring_broken_contract_compile(): contract = Contract(broken_contract, Contract.RING) with raises(AException): result = contract.compile('')
def test_evm_broken_contract_compile(): contract = Contract(broken_contract, Contract.EVM) with raises(ContractError): result = contract.compile('') print(result)
def test_ring_broken_contract_call(): contract = Contract(broken_contract, Contract.RING) with raises(AException): result = contract.call('IdentityBroken.main', '1')
def test_ring_contract_compile(): contract = Contract(aer_identity_contract, Contract.RING) result = contract.compile('') assert result is not None assert result.startswith('0x')
def test_sophia_contract_tx_create(): contract = Contract(aer_identity_contract, Contract.SOPHIA) address, tx = contract.tx_create(KEYPAIR, gas=1000) assert address is not None assert len(address) > 0