def test_contract_create_already_exits(self): engine = test_engine(has_snapshot=True) # store store the contract ourselves contract = storage.ContractState(hello_world_nef.script, hello_world_manifest) engine.snapshot.contracts.put(contract) # now try to create a contract engine.push(vm.ByteStringStackItem(str(hello_world_manifest).encode())) engine.push(vm.ByteStringStackItem(hello_world_nef.script)) with self.assertRaises(ValueError) as context: engine.invoke_syscall_by_name("System.Contract.Create") self.assertEqual("Contract already exists", str(context.exception))
def test_contract_is_standard_ok(self): keypair = cryptography.KeyPair(b'\x01' * 32) sig_contract = contracts.Contract.create_signature_contract( keypair.public_key) engine = test_engine(has_snapshot=True) contract = storage.ContractState( sig_contract.script, contracts.ContractManifest(sig_contract.script_hash)) engine.snapshot.contracts.put(contract) engine.push(vm.ByteStringStackItem(contract.script_hash().to_array())) engine.invoke_syscall_by_name("System.Contract.IsStandard") engine.execute() self.assertEqual(True, engine.result_stack.pop().to_boolean())
def test_contract_destroy_not_found(self): # contract destroy must be called by the contract itself, the function uses the engine.current_script hash for # locating the contract to delete. We set the engine to have a default script, which does not match a contract # that exists in storage engine = test_engine(has_snapshot=True, default_script=True) # we also place at least 1 contract in storage and validate it doesn't get removed contract = storage.ContractState(hello_world_nef.script, hello_world_manifest) engine.snapshot.contracts.put(contract) # now call the destroy function engine.invoke_syscall_by_name("System.Contract.Destroy") # and assert nothing changed to our contracts storage self.assertIsNotNone( engine.snapshot.contracts.try_get(contract.script_hash()))
def contract_update(engine: contracts.ApplicationEngine, script: bytes, manifest: bytes) -> None: script_len = len(script) manifest_len = len(manifest) # TODO: In preview 4 revert back to # engine.add_gas(engine.STORAGE_PRICE * (script_len + manifest_len)) # They made a mistake in their storage price calculation logic where manifest size is never taken into account engine.add_gas(engine.STORAGE_PRICE * script_len) contract = engine.snapshot.contracts.try_get(engine.current_scripthash, read_only=True) if contract is None: raise ValueError("Can't find contract to update") if script_len == 0 or script_len > engine.MAX_CONTRACT_LENGTH: raise ValueError(f"Invalid script length: {script_len}") hash_ = to_script_hash(script) if hash_ == engine.current_scripthash or engine.snapshot.contracts.try_get(hash_) is not None: raise ValueError("Nothing to update") old_contract_has_storage = contract.has_storage contract = storage.ContractState(script, contract.manifest) contract.manifest.abi.contract_hash = hash_ engine.snapshot.contracts.put(contract) # migrate storage to new contract hash with blockchain.Blockchain().backend.get_snapshotview() as snapshot: if old_contract_has_storage: for key, value in snapshot.storages.find(engine.current_scripthash, b''): # delete the old storage snapshot.storages.delete(key) # update key to new contract hash key.contract = contract.script_hash() # now persist all data under new contract key snapshot.storages.put(key, value) snapshot.commit() engine.snapshot.contracts.delete(engine.current_scripthash) if manifest_len == 0 or manifest_len > contracts.ContractManifest.MAX_LENGTH: raise ValueError(f"Invalid manifest length: {manifest_len}") contract.manifest = contracts.ContractManifest.from_json(json.loads(manifest.decode())) if not contract.manifest.is_valid(contract.script_hash()): raise ValueError("Error: manifest does not match with script") if (not contract.has_storage and len(list(engine.snapshot.storages.find(contract.script_hash(), key_prefix=b''))) != 0): raise ValueError("Error: New contract does not support storage while old contract has existing storage")
def test_policy_set_and_get_blocked_accounts(self): engine = test_engine(has_snapshot=True) block = test_block(0) # set or we won't pass the native deploy call engine.snapshot.persisting_block = block sb = vm.ScriptBuilder() sb.emit_syscall(syscall_name_to_int("Neo.Native.Deploy")) # set or we won't pass the check_comittee() in the policy contract function implementations engine.script_container = TestIVerifiable() validator = settings.standby_committee[0] script_hash = to_script_hash( contracts.Contract.create_multisig_redeemscript(1, [validator])) engine.script_container.script_hashes = [script_hash] # first we setup the stack for calling `blockAccount` # push data to create a vm.Array holding 20 bytes for the UInt160 Account parameter of the _block_account function. sb.emit_push(b'\x11' * 20) sb.emit(vm.OpCode.PUSH1) sb.emit(vm.OpCode.PACK) sb.emit_push("blockAccount") sb.emit_push(contracts.PolicyContract().script_hash.to_array()) sb.emit_syscall(syscall_name_to_int("System.Contract.Call")) # next we call `getBlockedAccounts` sb.emit_contract_call(contracts.PolicyContract().script_hash, "getBlockedAccounts") 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 engine.snapshot.contracts.put( storage.ContractState(sb.to_array(), contracts.ContractManifest())) engine.execute() self.assertEqual(vm.VMState.HALT, engine.state) self.assertEqual(2, len(engine.result_stack)) get_blocked_accounts_result = engine.result_stack.pop() set_blocked_accounts_result = engine.result_stack.pop() self.assertTrue(set_blocked_accounts_result.to_boolean()) self.assertIsInstance(get_blocked_accounts_result, vm.InteropStackItem) stored_accounts = get_blocked_accounts_result.get_object() self.assertEqual(1, len(stored_accounts)) expected_account = types.UInt160(data=b'\x11' * 20) self.assertEqual(expected_account, stored_accounts[0])
def test_contract_is_standard_fail2(self): # can find contract, but is not a signature contract engine = test_engine(has_snapshot=True) # create a non-standard contract script = b'\x01\x02\x03' script_hash = to_script_hash(script) manifest = contracts.ContractManifest(script_hash) contract = storage.ContractState(script, manifest) engine.snapshot.contracts.put(contract) # push function argument and call engine.push(vm.ByteStringStackItem(script_hash.to_array())) engine.invoke_syscall_by_name("System.Contract.IsStandard") engine.execute() self.assertEqual(False, engine.result_stack.pop().to_boolean())
def test_native_call(self): engine = test_engine(has_snapshot=True, default_script=True) block = test_block(0) engine.snapshot.persisting_block = block # need to create and store a contract matching the current_context.script # otherwise system.contract.call checks will fail engine.snapshot.contracts.put( storage.ContractState(b'\x40', contracts.ContractManifest())) engine.invoke_syscall_by_name("Neo.Native.Deploy") engine.push(vm.ArrayStackItem( engine.reference_counter)) # empty array for no arguments engine.push(vm.ByteStringStackItem(b'getMaxTransactionsPerBlock')) policy_contract_hash = vm.ByteStringStackItem( contracts.PolicyContract().script_hash.to_array()) engine.push(policy_contract_hash) engine.invoke_syscall_by_name("System.Contract.Call")
def test_contract_update_exceptions4(self): # asking to update with a new script but with an invalid (0 length) manifest engine = test_engine(has_snapshot=True, default_script=False) contract_old = storage.ContractState(hello_world_nef.script, hello_world_manifest) engine.snapshot.contracts.put(contract_old) # we load the stored as script to properly setup "engine.current_scripthash" engine.load_script(vm.Script(contract_old.script)) # next we push the necessary items on the stack before calling the update function bad_manifest = b'' engine.push(vm.ByteStringStackItem(bad_manifest)) engine.push(vm.ByteStringStackItem(bye_world_nef.script)) with self.assertRaises(ValueError) as context: engine.invoke_syscall_by_name("System.Contract.Update") self.assertEqual("Invalid manifest length: 0", str(context.exception))
def test_contract_destroy_ok(self): engine = test_engine(has_snapshot=True, default_script=False) # for this test we modify our contract to also have storage, to validate it gets cleared properly contract = storage.ContractState(hello_world_nef.script, deepcopy(hello_world_manifest)) contract.manifest.features |= contracts.ContractFeatures.HAS_STORAGE engine.snapshot.contracts.put(contract) storage_key = storage.StorageKey(contract.script_hash(), b'firstkey') storage_item = storage.StorageItem(b'firstitem') engine.snapshot.storages.put(storage_key, storage_item) # setup the engine by loading the contract script such that we can call destroy on _that_ contract engine.load_script(vm.Script(contract.script)) engine.invoke_syscall_by_name("System.Contract.Destroy") self.assertIsNone( engine.snapshot.contracts.try_get(contract.script_hash())) self.assertIsNone(engine.snapshot.storages.try_get(storage_key))
def test_transfer_full_balance(self): gas = contracts.GasToken() manifest = contracts.ContractManifest() manifest.features = contracts.ContractFeatures.PAYABLE state_to = storage.ContractState(b'\x00' * 20, manifest) account_to = state_to.script_hash() account_from = types.UInt160(b'\x01' * 20) storage_key_from = storage.StorageKey( gas.script_hash, gas._PREFIX_ACCOUNT + account_from.to_array()) account_from_state = gas._state() account_from_state.balance = vm.BigInteger(123) storage_item_from = storage.StorageItem(account_from_state.to_array()) amount = account_from_state.balance engine = self.transfer_helper(gas, account_from, account_to, amount) # ensure the destination contract exists engine.snapshot.contracts.put(state_to) # ensure the source account has balance engine.snapshot.storages.put(storage_key_from, storage_item_from) transfer_event = () def notify_listener(contract_script_hash, event, state): nonlocal transfer_event transfer_event = (contract_script_hash, event, state) msgrouter.interop_notify += notify_listener engine.execute() self.assertEqual(1, len(engine.result_stack)) result = engine.result_stack.pop() self.assertTrue(result) self.assertEqual(gas.script_hash, transfer_event[0]) self.assertEqual("Transfer", transfer_event[1]) state_items = list(transfer_event[2]) self.assertEqual(account_from, types.UInt160(state_items[0].to_array())) self.assertEqual(account_to, types.UInt160(state_items[1].to_array())) self.assertEqual(amount, state_items[2].to_biginteger()) # test that the source account is no longer present in storage as the balance is zero self.assertIsNone(engine.snapshot.storages.try_get(storage_key_from))
def test_contract_update_exceptions3(self): # asking to update without making changes to the script engine = test_engine(has_snapshot=True, default_script=False) contract = storage.ContractState(hello_world_nef.script, hello_world_manifest) engine.snapshot.contracts.put(contract) # we load the stored as script to properly setup "engine.current_scripthash" engine.load_script(vm.Script(contract.script)) # next we push the necessary items on the stack before calling the update function fake_manifest = b'' engine.push(vm.ByteStringStackItem(fake_manifest)) # same script as already exists in storage engine.push(vm.ByteStringStackItem(contract.script)) with self.assertRaises(ValueError) as context: engine.invoke_syscall_by_name("System.Contract.Update") self.assertEqual("Nothing to update", str(context.exception))
def test_get_context_no_storage(self): engine = test_engine(has_snapshot=True) manifest_without_storage = contracts.ContractManifest() contract = storage.ContractState(script=self.RET, _manifest=manifest_without_storage) engine.snapshot.contracts.put(contract) # first test for asking for a context for a smart contract without storage with self.assertRaises(ValueError) as context: engine.invoke_syscall_by_name("System.Storage.GetContext") self.assertEqual( "Cannot get context for smart contract without storage", str(context.exception)) # the same should hold for getting a read only context with self.assertRaises(ValueError) as context: engine.invoke_syscall_by_name("System.Storage.GetReadOnlyContext") self.assertEqual( "Cannot get context for smart contract without storage", str(context.exception))
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_syscall(syscall_name_to_int("Neo.Native.Deploy")) # now call the actual native contract sb.emit_contract_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 engine.snapshot.contracts.put( storage.ContractState(sb.to_array(), contracts.ContractManifest())) return engine
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 test_storage_get_ok2(self): # this is basically the same as `test_storage_get_ok` # but performed by executing a script # it exists to validate that the `Optional[bytes]` return value is converted properly engine = test_engine(has_snapshot=True) script = vm.ScriptBuilder() script.emit(vm.OpCode.PUSH1) script.emit_syscall(syscall_name_to_int("System.Storage.GetContext")) script.emit_syscall(syscall_name_to_int("System.Storage.Get")) engine.load_script(vm.Script(script.to_array())) contract = storage.ContractState(script=script.to_array(), _manifest=self.manifest) engine.snapshot.contracts.put(contract) storage_key = storage.StorageKey(contract.script_hash(), b'\x01') storage_item = storage.StorageItem(b'\x11') engine.snapshot.storages.put(storage_key, storage_item) engine.execute() self.assertEqual(vm.VMState.HALT, engine.state) self.assertEqual(1, len(engine.result_stack)) item = engine.result_stack.pop() self.assertEqual(storage_item.value, item.to_array())
def test_transfer_more_than_balance(self): gas = contracts.GasToken() account_from = types.UInt160.zero() storage_key_from = storage.StorageKey( gas.script_hash, gas._PREFIX_ACCOUNT + account_from.to_array()) account_state = gas._state() account_state.balance = vm.BigInteger(123) storage_item_from = storage.StorageItem(account_state.to_array()) manifest = contracts.ContractManifest() manifest.features = contracts.ContractFeatures.PAYABLE state_to = storage.ContractState(b'\x00', manifest) account_to = state_to.script_hash() amount = account_state.balance + 1 engine = self.transfer_helper(gas, account_from, account_to, amount) # ensure the destination contract exists engine.snapshot.contracts.put(state_to) # ensure the source account has balance engine.snapshot.storages.put(storage_key_from, storage_item_from) engine.execute() self.assertEqual(1, len(engine.result_stack)) result = engine.result_stack.pop() self.assertFalse(result)
def setUp(self) -> None: self.RET = b'\x40' self.manifest = contracts.ContractManifest() self.manifest.features = contracts.ContractFeatures.HAS_STORAGE self.contract = storage.ContractState(script=self.RET, _manifest=self.manifest)
def test_contract_call_exceptions(self): engine = test_engine(has_snapshot=True, default_script=False) engine.load_script(vm.Script(hello_world_nef.script)) with self.assertRaises(ValueError) as context: contract_call_internal(engine, types.UInt160.zero(), "_invalid_method", vm.ArrayStackItem(engine.reference_counter), contracts.native.CallFlags) self.assertEqual( "[System.Contract.Call] Method not allowed to start with _", str(context.exception)) # can't find contract with self.assertRaises(ValueError) as context: contract_call_internal(engine, types.UInt160.zero(), "valid_method", vm.ArrayStackItem(engine.reference_counter), contracts.native.CallFlags) self.assertEqual("[System.Contract.Call] Can't find target contract", str(context.exception)) target_contract = storage.ContractState(contract3_nef.script, contract3_manifest) engine.snapshot.contracts.put(target_contract) # modify the manifest of the current executing contract to only allow to call 1 specific method on other contracts new_current_manifest = deepcopy(hello_world_manifest) new_current_manifest.permissions = [ contracts.ContractPermission( contracts.ContractPermissionDescriptor( ), # allow to call any contract contracts.WildcardContainer( ['method_aaaa']) # allowing to call the listed method only ) ] new_current_contract = storage.ContractState(hello_world_nef.script, new_current_manifest) engine.snapshot.contracts.put(new_current_contract) with self.assertRaises(ValueError) as context: contract_call_internal(engine, target_contract.script_hash(), "invalid_method", vm.ArrayStackItem(engine.reference_counter), contracts.native.CallFlags) self.assertEqual( "[System.Contract.Call] Not allowed to call target method 'invalid_method' according to manifest", str(context.exception)) # restore current contract to its original form and try to call a non-existing contract current_contract = storage.ContractState(hello_world_nef.script, hello_world_manifest) engine.snapshot.contracts.delete(new_current_contract.script_hash()) engine.snapshot.contracts.put(current_contract) with self.assertRaises(ValueError) as context: contract_call_internal(engine, target_contract.script_hash(), "invalid_method", vm.ArrayStackItem(engine.reference_counter), contracts.native.CallFlags) self.assertEqual( "[System.Contract.Call] requested target method 'invalid_method' does not exist on target contract", str(context.exception)) # call the target method with invalid number of arguments array = vm.ArrayStackItem(engine.reference_counter) array.append([vm.NullStackItem(), vm.NullStackItem()]) with self.assertRaises(ValueError) as context: contract_call_internal(engine, target_contract.script_hash(), "test_func", array, contracts.native.CallFlags) self.assertEqual( "[System.Contract.Call] Invalid number of contract arguments. Expected 0 actual 2", str(context.exception))
def test_contractstate_clone(self): manifest = contracts.ContractManifest() state = storage.ContractState(b'\x01', manifest) clone = state.clone() self.assertNotEqual(id(state), id(clone)) self.assertNotEqual(id(state.manifest), id(clone.manifest))
def test_transfer_partial_balance_to_account_with_balance(self): gas = contracts.GasToken() manifest = contracts.ContractManifest() manifest.features = contracts.ContractFeatures.PAYABLE state_to = storage.ContractState(b'\x00' * 20, manifest) account_to = state_to.script_hash() storage_key_to = storage.StorageKey( gas.script_hash, gas._PREFIX_ACCOUNT + account_to.to_array()) account_to_state = gas._state() account_to_state.balance = vm.BigInteger(100) storage_item_to = storage.StorageItem(account_to_state.to_array()) account_from = types.UInt160(b'\x01' * 20) storage_key_from = storage.StorageKey( gas.script_hash, gas._PREFIX_ACCOUNT + account_from.to_array()) account_from_state = gas._state() account_from_state.balance = vm.BigInteger(123) storage_item_from = storage.StorageItem(account_from_state.to_array()) amount = vm.BigInteger(50) engine = self.transfer_helper(gas, account_from, account_to, amount) # ensure the destination contract exists engine.snapshot.contracts.put(state_to) # ensure the source and destination account have balances engine.snapshot.storages.put(storage_key_from, storage_item_from) engine.snapshot.storages.put(storage_key_to, storage_item_to) transfer_event = () def notify_listener(contract_script_hash, event, state): nonlocal transfer_event transfer_event = (contract_script_hash, event, state) msgrouter.interop_notify += notify_listener engine.execute() self.assertEqual(1, len(engine.result_stack)) result = engine.result_stack.pop() self.assertTrue(result) self.assertEqual(gas.script_hash, transfer_event[0]) self.assertEqual("Transfer", transfer_event[1]) state_items = list(transfer_event[2]) self.assertEqual(account_from, types.UInt160(state_items[0].to_array())) self.assertEqual(account_to, types.UInt160(state_items[1].to_array())) self.assertEqual(amount, state_items[2].to_biginteger()) # validate from account is deducted by `amount` new_storage_account_from = engine.snapshot.storages.get( storage_key_from) new_account_state_from = gas._state.deserialize_from_bytes( new_storage_account_from.value) self.assertEqual(account_from_state.balance - amount, new_account_state_from.balance) # validate to account is credited with `amount` new_storage_account_to = engine.snapshot.storages.get(storage_key_to) new_account_state_to = gas._state.deserialize_from_bytes( new_storage_account_to.value) self.assertEqual(account_to_state.balance + amount, new_account_state_to.balance)
def test_checkwitness_custom_groups(self): """ We need to setup 2 contracts 1) caller_contract: uses a System.Contract.Call to call callee_contract. This will set the calling script hash on the ExecutionContext of the callee_contract 2) callee_contract: uses a System.Runtime.CheckWitness """ engine = test_engine(has_snapshot=True, default_script=False) tx = test_tx() engine.script_container = tx sb = vm.ScriptBuilder() sb.emit_push(tx.sender.to_array()) sb.emit_syscall(syscall_name_to_int("System.Runtime.CheckWitness")) callee_contract_script = sb.to_array() callee_manifest = contracts.ContractManifest( contract_hash=to_script_hash(callee_contract_script)) callee_manifest.abi.methods = [ contracts.ContractMethodDescriptor( "test_func", 0, [], contracts.ContractParameterType.ANY) ] callee_contract = storage.ContractState(callee_contract_script, callee_manifest) sb = vm.ScriptBuilder() sb.emit(vm.OpCode.NEWARRAY0) # args (empty array) sb.emit_push('test_func') # target method name sb.emit_push(callee_contract.script_hash().to_array()) # contract hash sb.emit_syscall(syscall_name_to_int("System.Contract.Call")) caller_script = sb.to_array() caller_manifest = contracts.ContractManifest( contract_hash=to_script_hash(caller_script)) keypair = cryptography.KeyPair(private_key=b'\x01' * 32) signature = cryptography.sign(caller_script, keypair.private_key) caller_manifest.groups = [ contracts.ContractGroup(public_key=keypair.public_key, signature=signature) ] caller_contract = storage.ContractState(caller_script, caller_manifest) engine.snapshot.contracts.put(caller_contract) engine.snapshot.contracts.put(callee_contract) engine.load_script(vm.Script(caller_script)) tx.signers[0].scope = payloads.WitnessScope.CUSTOM_GROUPS tx.signers[0].allowed_groups = [keypair.public_key] engine.execute() self.assertEqual(vm.VMState.HALT, engine.state) self.assertEqual(1, len(engine.result_stack)) item = engine.result_stack.pop() self.assertTrue(item.to_boolean()) # now try again but make sure it fails if the public key is not listed in the allowed groups engine = test_engine(has_snapshot=True, default_script=False) tx = test_tx() engine.script_container = tx engine.snapshot.contracts.put(caller_contract) engine.snapshot.contracts.put(callee_contract) engine.load_script(vm.Script(caller_script)) tx.signers[0].scope = payloads.WitnessScope.CUSTOM_GROUPS keypair = cryptography.KeyPair(private_key=b'\x02' * 32) tx.signers[0].allowed_groups = [keypair.public_key] engine.execute() self.assertEqual(vm.VMState.HALT, engine.state) self.assertEqual(1, len(engine.result_stack)) item = engine.result_stack.pop() self.assertFalse(item.to_boolean())