def _post_transfer(self, engine: contracts.ApplicationEngine, account_from: types.UInt160, account_to: types.UInt160, amount: vm.BigInteger, data: vm.StackItem, call_on_payment: bool) -> None: state = vm.ArrayStackItem(vm.ReferenceCounter()) 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(amount)) msgrouter.interop_notify(self.hash, "Transfer", state) # wallet or smart contract if not call_on_payment \ or account_to == types.UInt160.zero() \ or contracts.ManagementContract().get_contract(engine.snapshot, account_to) is None: return if account_from == types.UInt160.zero(): from_: vm.StackItem = vm.NullStackItem() else: from_ = vm.ByteStringStackItem(account_from.to_array()) engine.call_from_native( self.hash, account_to, "onNEP17Payment", [from_, vm.IntegerStackItem(amount), data])
def get_read_only_context( engine: contracts.ApplicationEngine) -> storage.StorageContext: contract = contracts.ManagementContract().get_contract( engine.snapshot, engine.current_scripthash) if contract is None: raise ValueError("Contract not deployed") return storage.StorageContext(contract.id, True)
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 checkwitness(self, hash_: types.UInt160) -> bool: """ Check if the hash is a valid witness for the engines script_container """ with suppress(ValueError): if hash_ == self.calling_scripthash: return True if isinstance(self.script_container, payloads.Transaction): tx = self.script_container response = tx.try_get_attribute(payloads.OracleResponse) if response is None: signers = tx.signers else: signers = [] request = contracts.OracleContract().get_request(self.snapshot, response.id) if request: tmp_tx = contracts.LedgerContract().get_tx_for_contract(self.snapshot, request.original_tx_id) if tmp_tx: signers = tmp_tx.signers for s in signers: if s.account == hash_: signer = s break else: return False if signer.scope == payloads.WitnessScope.GLOBAL: return True if payloads.WitnessScope.CALLED_BY_ENTRY in signer.scope: if self.calling_scripthash == self.entry_scripthash: return True if payloads.WitnessScope.CUSTOM_CONTRACTS in signer.scope: if self.current_scripthash in signer.allowed_contracts: return True if payloads.WitnessScope.CUSTOM_GROUPS in signer.scope: self._validate_callflags(contracts.CallFlags.READ_STATES) contract = contracts.ManagementContract().get_contract(self.snapshot, self.calling_scripthash) if contract is None: return False group_keys = set(map(lambda g: g.public_key, contract.manifest.groups)) if any(group_keys.intersection(signer.allowed_groups)): return True return False self._validate_callflags(contracts.CallFlags.READ_STATES) # for other IVerifiable types like Block hashes_for_verifying = self.script_container.get_script_hashes_for_verifying(self.snapshot) return hash_ in hashes_for_verifying
def init(self): self._methods: Dict[int, _NativeMethodMeta] = {} # offset, meta self._management = contracts.ManagementContract() self._neo = contracts.NeoToken() self._gas = contracts.GasToken() self._policy = contracts.PolicyContract() self._oracle = contracts.OracleContract() self._ledger = contracts.LedgerContract() self._role = contracts.DesignationContract() self._crypto = contracts.CryptoContract() self._stdlib = contracts.StdLibContract() # Find all methods that have been augmented by the @register decorator # and turn them into methods that can be called by VM scripts methods_meta = [] for pair in inspect.getmembers(self, lambda m: hasattr(m, "native_call")): methods_meta.append(_NativeMethodMeta(pair[1])) methods_meta.sort( key=lambda x: (x.descriptor.name, len(x.descriptor.parameters))) sb = vm.ScriptBuilder() for meta in methods_meta: meta.descriptor.offset = len(sb) sb.emit_push(0) self._methods.update({len(sb): meta}) sb.emit_syscall(1736177434) # "System.Contract.CallNative" sb.emit(vm.OpCode.RET) self._script: bytes = sb.to_array() self.nef = contracts.NEF("neo-core-v3.0", self._script) sender = types.UInt160.zero() # OpCode.PUSH1 sb = vm.ScriptBuilder() sb.emit(vm.OpCode.ABORT) sb.emit_push(sender.to_array()) sb.emit_push(0) sb.emit_push(self.service_name()) self._hash: types.UInt160 = to_script_hash(sb.to_array()) self._manifest: contracts.ContractManifest = contracts.ContractManifest( ) self._manifest.name = self.service_name() self._manifest.abi.methods = list( map(lambda m: m.descriptor, methods_meta)) if self._id != NativeContract._id: self._contracts.update({self.service_name(): self}) self._contract_hashes.update({self._hash: self}) self.active_block_index = settings.native_contract_activation.get( self.service_name, 0)
def _contract_call_internal(self, contract_hash: types.UInt160, method: str, flags: contracts.CallFlags, has_return_value: bool, args: List[vm.StackItem]) -> vm.ExecutionContext: target_contract = contracts.ManagementContract().get_contract(self.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}' with {len(args)} arguments does not exist on " f"target contract") return self._contract_call_internal2(target_contract, method_descriptor, flags, has_return_value, args)
def _contract_call_internal2( self, target_contract: contracts.ContractState, method_descriptor: contracts.ContractMethodDescriptor, flags: contracts.CallFlags, has_return_value: bool, args: List[vm.StackItem]): if method_descriptor.safe: flags &= ~(contracts.CallFlags.WRITE_STATES | contracts.CallFlags.ALLOW_NOTIFY) else: current_contract = contracts.ManagementContract().get_contract( self.snapshot, self.current_scripthash) if current_contract and not current_contract.can_call( target_contract, method_descriptor.name): raise ValueError( f"[System.Contract.Call] Not allowed to call target method '{method_descriptor.name}' according " f"to manifest") counter = self._invocation_counter.get(target_contract.hash, 0) self._invocation_counter.update({target_contract.hash: counter + 1}) state = self.current_context calling_script_hash_bytes = state.scripthash_bytes calling_flags = state.call_flags arg_len = len(args) expected_len = len(method_descriptor.parameters) if arg_len != expected_len: raise ValueError( f"[System.Contract.Call] Invalid number of contract arguments. Expected {expected_len} actual {arg_len}" ) # noqa if has_return_value ^ (method_descriptor.return_type != contracts.ContractParameterType.VOID): raise ValueError("Return value type does not match") context_new = self.load_contract(target_contract, method_descriptor, flags & calling_flags) if context_new is None: raise ValueError context_new.calling_scripthash_bytes = calling_script_hash_bytes for item in reversed(args): context_new.evaluation_stack.push(item) return context_new
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 test_native_contract(contract_hash: types.UInt160, operation: str, args=None): engine = test_engine(has_snapshot=True) block = test_block(0) # or we won't pass the native deploy call engine.snapshot.persisting_block = block sb = vm.ScriptBuilder() sb.emit_dynamic_call(contract_hash, operation) script = vm.Script(sb.to_array()) engine.load_script(script) # storing the current script in a contract otherwise "System.Contract.Call" will fail its checks nef = contracts.NEF(script=sb.to_array()) manifest = contracts.ContractManifest("test_contract") next_id = contracts.ManagementContract().get_next_available_id( engine.snapshot) contract = contracts.ContractState(next_id + 1, nef, manifest, 0, to_script_hash(nef.script)) engine.snapshot.contracts.put(contract) return engine
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)