def invoke(self, engine: contracts.ApplicationEngine, version: int) -> None: """ Calls a contract function Reads the required arguments from the engine's stack and converts them to the appropiate contract function types Args: engine: the engine executing the smart contract version: which version of the smart contract to load Raises: ValueError: if the request contract version is not ValueError: if the function to be called does not exist on the contract ValueError: if trying to call a function without having the correct CallFlags """ if version != 0: raise ValueError(f"Native contract version {version} is not active") # type: ignore context = engine.current_context flags = contracts.CallFlags(context.call_flags) method = self._methods.get(context.ip, None) if method is None: raise ValueError(f"Method at IP \"{context.ip}\" does not exist on contract {self.service_name()}") if method.required_flags not in flags: raise ValueError(f"Method requires call flag: {method.required_flags} received: {flags}") engine.add_gas(method.cpu_price * contracts.PolicyContract().get_exec_fee_factor(engine.snapshot) + method.storage_price * contracts.PolicyContract().get_storage_price(engine.snapshot)) params: List[Any] = [] if method.add_engine: params.append(engine) if method.add_snapshot: params.append(engine.snapshot) for t in method.parameter_types: params.append(engine._stackitem_to_native(context.evaluation_stack.pop(), t)) if len(params) > 0: return_value = method.handler(*params) else: return_value = method.handler() if method.return_type is not None: context.evaluation_stack.push(engine._native_to_stackitem(return_value, type(return_value)))
def transfer(self, engine: contracts.ApplicationEngine, account_from: types.UInt160, account_to: types.UInt160, amount: vm.BigInteger, data: vm.StackItem ) -> bool: """ Transfer tokens from one account to another. Raises: ValueError: if the requested amount is negative. Returns: True on success. False otherwise. """ if amount.sign < 0: raise ValueError("Can't transfer a negative amount") # transfer from an account not owned by the smart contract that is requesting the transfer # and there is no signature that approves we are allowed todo so if account_from != engine.calling_scripthash and not engine.checkwitness(account_from): return False storage_key_from = self.key_account + account_from storage_item_from = engine.snapshot.storages.try_get(storage_key_from, read_only=False) if storage_item_from is None: return False state_from = storage_item_from.get(self._state) if amount == vm.BigInteger.zero(): self.on_balance_changing(engine, account_from, state_from, amount) else: if state_from.balance < amount: return False if account_from == account_to: self.on_balance_changing(engine, account_from, state_from, vm.BigInteger.zero()) else: self.on_balance_changing(engine, account_from, state_from, -amount) if state_from.balance == amount: engine.snapshot.storages.delete(storage_key_from) else: state_from.balance -= amount storage_key_to = self.key_account + account_to storage_item_to = engine.snapshot.storages.try_get(storage_key_to, read_only=False) if storage_item_to is None: storage_item_to = storage.StorageItem(self._state().to_array()) engine.snapshot.storages.put(storage_key_to, storage_item_to) state_to = storage_item_to.get(self._state) self.on_balance_changing(engine, account_to, state_to, amount) state_to.balance += amount self._post_transfer(engine, account_from, account_to, amount, data, True) return True
def contract_update_with_data(self, engine: contracts.ApplicationEngine, nef_file: bytes, manifest: bytes, data: vm.StackItem) -> None: nef_len = len(nef_file) manifest_len = len(manifest) engine.add_gas(engine.STORAGE_PRICE * (nef_len + manifest_len)) contract = engine.snapshot.contracts.try_get(engine.calling_scripthash, read_only=False) if contract is None: raise ValueError("Can't find contract to update") if nef_len == 0: raise ValueError(f"Invalid NEF length: {nef_len}") # update contract contract.nef = contracts.NEF.deserialize_from_bytes(nef_file) if manifest_len == 0 or manifest_len > contracts.ContractManifest.MAX_LENGTH: raise ValueError(f"Invalid manifest length: {manifest_len}") manifest_new = contracts.ContractManifest.from_json( json.loads(manifest.decode())) if manifest_new.name != contract.manifest.name: raise ValueError("Error: cannot change contract name") if not contract.manifest.is_valid(contract.hash): raise ValueError("Error: manifest does not match with script") contract.manifest = manifest_new self.validate(contract.nef.script, contract.manifest.abi) contract.update_counter += 1 if len(nef_file) != 0: method_descriptor = contract.manifest.abi.get_method("_deploy", 2) if method_descriptor is not None: engine.call_from_native(self.hash, contract.hash, method_descriptor.name, [data, vm.BooleanStackItem(True)]) msgrouter.interop_notify( self.hash, "Update", vm.ArrayStackItem(engine.reference_counter, vm.ByteStringStackItem( contract.hash.to_array())))
def vote(self, engine: contracts.ApplicationEngine, account: types.UInt160, vote_to: cryptography.ECPoint) -> bool: """ Vote on a consensus candidate Args: engine: Application engine instance. account: source account to take voting balance from vote_to: candidate public key Returns: True is vote registered succesfully. False otherwise. """ if not engine.checkwitness(account): return False storage_key_account = self.key_account + account storage_item = engine.snapshot.storages.try_get(storage_key_account, read_only=False) if storage_item is None: return False account_state = storage_item.get(self._state) storage_key_candidate = self.key_candidate + vote_to storage_item_candidate = engine.snapshot.storages.try_get( storage_key_candidate, read_only=False) if storage_item_candidate is None: return False candidate_state = storage_item_candidate.get(_CandidateState) if not candidate_state.registered: return False if account_state.vote_to.is_zero(): si_voters_count = engine.snapshot.storages.get( self.key_voters_count, read_only=False) old_value = vm.BigInteger(si_voters_count.value) new_value = old_value + account_state.balance si_voters_count.value = new_value.to_array() self._distribute_gas(engine, account, account_state) if not account_state.vote_to.is_zero(): sk_validator = self.key_candidate + account_state.vote_to si_validator = engine.snapshot.storages.get(sk_validator, read_only=False) validator_state = si_validator.get(_CandidateState) validator_state.votes -= account_state.balance if not validator_state.registered and validator_state.votes == 0: engine.snapshot.storages.delete(sk_validator) account_state.vote_to = vote_to candidate_state.votes += account_state.balance self._candidates_dirty = True return True
def do_register(self, engine: contracts.ApplicationEngine, name: str, owner: types.UInt160) -> bool: if not self.is_available(engine.snapshot, name): raise ValueError( f"Registration failure - '{name}' is not available") if not engine.checkwitness(owner): raise ValueError("CheckWitness failed") engine.add_gas(self.get_price(engine.snapshot)) state = NameState( owner, name, (engine.snapshot.persisting_block.timestamp // 1000) + self.ONE_YEAR) self.mint(engine, state) engine.snapshot.storages.put( self.key_expiration + state.expiration.to_bytes(4, 'big') + name.encode(), storage.StorageItem(b'\x00')) return True
def set_admin(self, engine: contracts.ApplicationEngine, name: str, admin: types.UInt160) -> None: if not self.REGEX_NAME.match(name): raise ValueError("Regex failure - name is not valid") names = name.split(".") if len(names) != 2: raise ValueError("Invalid format") if admin != types.UInt160.zero() and not engine.checkwitness(admin): raise ValueError("New admin is not valid - check witness failed") storage_item = engine.snapshot.storages.get(self.key_token + name.encode()) state = storage_item.get(NameState) if not engine.checkwitness(state.owner): raise ValueError state.admin = admin
def new_engine( previous_engine: ApplicationEngine = None) -> ApplicationEngine: tx = payloads.Transaction._serializable_init() if not previous_engine: blockchain.Blockchain.__it__ = None snapshot = blockchain.Blockchain( store_genesis_block=True ).currentSnapshot # blockchain is singleton return ApplicationEngine(contracts.TriggerType.APPLICATION, tx, snapshot, 0, test_mode=True) else: return ApplicationEngine(contracts.TriggerType.APPLICATION, tx, previous_engine.snapshot, 0, test_mode=True)
def do_checkwitness(engine: contracts.ApplicationEngine, data: bytes) -> bool: if len(data) == 20: hash_ = types.UInt160(data) elif len(data) == 33: redeemscript = contracts.Contract.create_signature_redeemscript( cryptography.ECPoint.deserialize_from_bytes(data)) hash_ = to_script_hash(redeemscript) else: raise ValueError("Supplied CheckWitness data is not a valid hash") return engine.checkwitness(hash_)
def contract_call(engine: contracts.ApplicationEngine, contract_hash: types.UInt160, method: str, call_flags: contracts.CallFlags, args: vm.ArrayStackItem) -> None: if method.startswith("_"): raise ValueError("Invalid method name") target_contract = contracts.ManagementContract().get_contract(engine.snapshot, contract_hash) if target_contract is None: raise ValueError("[System.Contract.Call] Can't find target contract") method_descriptor = target_contract.manifest.abi.get_method(method, len(args)) if method_descriptor is None: raise ValueError(f"[System.Contract.Call] Method '{method}' does not exist on target contract") has_return_value = method_descriptor.return_type != contracts.ContractParameterType.VOID if not has_return_value: engine.current_context.evaluation_stack.push(vm.NullStackItem()) engine._contract_call_internal2(target_contract, method_descriptor, call_flags, has_return_value, list(args))
def contract_create(engine: contracts.ApplicationEngine, script: bytes, manifest: bytes) -> storage.ContractState: script_len = len(script) manifest_len = len(manifest) if (script_len == 0 or script_len > engine.MAX_CONTRACT_LENGTH or manifest_len == 0 or manifest_len > contracts.ContractManifest.MAX_LENGTH): raise ValueError("Invalid script or manifest length") engine.add_gas(engine.STORAGE_PRICE * (script_len + manifest_len)) hash_ = to_script_hash(script) contract = engine.snapshot.contracts.try_get(hash_) if contract is not None: raise ValueError("Contract already exists") contract = storage.ContractState(script, contracts.ContractManifest.from_json(json.loads(manifest.decode()))) if not contract.manifest.is_valid(hash_): raise ValueError("Error: manifest does not match with script") engine.snapshot.contracts.put(contract) return contract
def _post_transfer(self, engine: contracts.ApplicationEngine, account_from: types.UInt160, account_to: types.UInt160, token_id: bytes) -> None: state = vm.ArrayStackItem(engine.reference_counter) if account_from == types.UInt160.zero(): state.append(vm.NullStackItem()) else: state.append(vm.ByteStringStackItem(account_from.to_array())) if account_to == types.UInt160.zero(): state.append(vm.NullStackItem()) else: state.append(vm.ByteStringStackItem(account_to.to_array())) state.append(vm.IntegerStackItem(1)) state.append(vm.ByteStringStackItem(token_id)) msgrouter.interop_notify(self.hash, "Transfer", state) if account_to != types.UInt160.zero() and \ contracts.ManagementContract().get_contract(engine.snapshot, account_to) is not None: engine.call_from_native(self.hash, account_to, "onNEP17Payment", list(state))
def invoke_method_of_arbitrary_contract( self, contract_hash: Union[UInt160, Hash160Str, str], method: str, params: List = None, signers: List[Union[str, UInt160, payloads.Signer]] = None, scope: payloads.WitnessScope = payloads.WitnessScope.GLOBAL, engine: ApplicationEngine = None): if params is None: params = [] params = list(map(lambda param: self.param_auto_checker(param), params)) if not engine: engine = self.new_engine(self.previous_engine) contract_hash = self.contract_hash_auto_checker(contract_hash) # engine.load_script(vm.Script(contract.script)) sb = vm.ScriptBuilder() if params: sb.emit_dynamic_call_with_args(contract_hash, method, params) else: sb.emit_dynamic_call(contract_hash, method) engine.load_script(vm.Script(sb.to_array())) if signers and signers != self.NO_SIGNER: signers = list( map(lambda signer: self.signer_auto_checker(signer, scope), signers)) engine.script_container.signers = signers elif self.signers: # use signers stored in self when no external signer specified engine.script_container.signers = self.signers engine.execute() engine.snapshot.commit() self.previous_engine = engine return engine
def _check_committee(self, engine: contracts.ApplicationEngine) -> bool: addr = contracts.NeoToken().get_committee_address(engine.snapshot) return engine.checkwitness(addr)
def get_invocationcounter(engine: contracts.ApplicationEngine) -> int: return engine.get_invocation_counter()
def do_burn_gas(engine: contracts.ApplicationEngine, gas: int) -> None: if gas <= 0: raise ValueError("Burn gas cannot be called with negative value") engine.add_gas(gas)
def _request(self, engine: contracts.ApplicationEngine, url: str, filter: str, callback: str, user_data: vm.StackItem, gas_for_response: int) -> None: if len(url.encode('utf-8')) > self._MAX_URL_LENGTH or \ len(filter.encode('utf-8')) > self._MAX_FILTER_LEN or \ len(callback.encode('utf-8')) > self._MAX_CALLBACK_LEN or \ callback.startswith("_") or \ gas_for_response < 10000000: raise ValueError engine.add_gas(self.get_price(engine.snapshot)) engine.add_gas(gas_for_response) self._gas.mint(engine, self.hash, vm.BigInteger(gas_for_response), False) si_item_id = engine.snapshot.storages.get(self.key_request_id, read_only=False) item_id = vm.BigInteger(si_item_id.value) si_item_id.value = (item_id + 1).to_array() if contracts.ManagementContract().get_contract(engine.snapshot, engine.calling_scripthash) is None: raise ValueError oracle_request = OracleRequest(self._get_original_txid(engine), gas_for_response, url, filter, engine.calling_scripthash, callback, contracts.BinarySerializer.serialize(user_data, self._MAX_USER_DATA_LEN)) engine.snapshot.storages.put(self.key_request + int(item_id).to_bytes(8, 'little', signed=False), storage.StorageItem(oracle_request.to_array()) ) sk_id_list = self.key_id_list + self._get_url_hash(url) si_id_list = engine.snapshot.storages.try_get(sk_id_list, read_only=False) if si_id_list is None: si_id_list = storage.StorageItem(b'\x00') with serialization.BinaryReader(si_id_list.value) as reader: count = reader.read_var_int() id_list = [] for _ in range(count): id_list.append(reader.read_uint64()) id_list.append(item_id) if len(id_list) >= 256: raise ValueError("Oracle has too many pending responses for this url") with serialization.BinaryWriter() as writer: writer.write_var_int(len(id_list)) for id in id_list: writer.write_uint64(id) si_id_list.value = writer.to_array() engine.snapshot.storages.update(sk_id_list, si_id_list) state = vm.ArrayStackItem( engine.reference_counter, [vm.IntegerStackItem(item_id), vm.ByteStringStackItem(engine.calling_scripthash.to_array()), vm.ByteStringStackItem(url.encode()), vm.ByteStringStackItem(filter.encode()), ] ) msgrouter.interop_notify(self.hash, "OracleRequest", state)