def test_storage_find(self): # settings.storage.default_provider = 'leveldb' engine = test_engine(has_snapshot=True) engine.snapshot.contracts.put(self.contract) storage_key1 = storage.StorageKey(self.contract.id, b'\x01') storage_item1 = storage.StorageItem(b'\x11') engine.snapshot.storages.put(storage_key1, storage_item1) storage_key2 = storage.StorageKey(self.contract.id, b'\x02') storage_item2 = storage.StorageItem(b'\x22') engine.snapshot.storages.put(storage_key2, storage_item2) ctx = engine.invoke_syscall_by_name("System.Storage.GetContext") engine.push(vm.IntegerStackItem(contracts.FindOptions.NONE)) engine.push(vm.ByteStringStackItem(storage_key1.key)) engine.push(vm.StackItem.from_interface(ctx)) it = engine.invoke_syscall_by_name("System.Storage.Find") self.assertIsInstance(it, interop.StorageIterator) with self.assertRaises(ValueError) as context: it.value() self.assertEqual( "Cannot call 'value' without having advanced the iterator at least once", str(context.exception)) self.assertTrue(it.next()) struct = it.value() # 0 key, 1 value self.assertEqual(storage_item1.value, struct[1].to_array())
def test_eq(self): contract = types.UInt160.zero() key_val = b'\0x01\x02\x03' sk = storage.StorageKey(contract, key_val) sk2 = storage.StorageKey(contract, key_val) self.assertFalse(sk == object()) self.assertTrue(sk == sk2)
def test_storage_find(self): engine = test_engine(has_snapshot=True) engine.snapshot.contracts.put(self.contract) storage_key1 = storage.StorageKey(self.contract.script_hash(), b'\x01') storage_item1 = storage.StorageItem(b'\x11', is_constant=False) engine.snapshot.storages.put(storage_key1, storage_item1) storage_key2 = storage.StorageKey(self.contract.script_hash(), b'\x02') storage_item2 = storage.StorageItem(b'\x22', is_constant=False) engine.snapshot.storages.put(storage_key2, storage_item2) ctx = engine.invoke_syscall_by_name("System.Storage.GetContext") engine.push(vm.ByteStringStackItem(storage_key1.key)) engine.push(vm.StackItem.from_interface(ctx)) it = engine.invoke_syscall_by_name("System.Storage.Find") self.assertIsInstance(it, interop.StorageIterator) it.next() self.assertEqual(storage_key1.key, it.key().to_array()) self.assertEqual(storage_item1.value, it.value().to_array()) it.next() with self.assertRaises(ValueError) as context: it.key() self.assertEqual( "Cannot call 'key' without having advanced the iterator at least once", str(context.exception)) with self.assertRaises(ValueError) as context: it.value() self.assertEqual( "Cannot call 'value' without having advanced the iterator at least once", str(context.exception))
def test_contract_update_exceptions6(self): # asking to update with a new script but with an invalid manifest (new manifest does not support storage, # while the old contract has existing storage) engine = test_engine(has_snapshot=True, default_script=False) contract_old = storage.ContractState(hello_world_nef.script, deepcopy(hello_world_manifest)) contract_old.manifest.features |= contracts.ContractFeatures.HAS_STORAGE engine.snapshot.contracts.put(contract_old) storage_key = storage.StorageKey(contract_old.script_hash(), b'firstkey') storage_item = storage.StorageItem(b'firstitem') engine.snapshot.storages.put(storage_key, storage_item) # 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 funcztion # we take the matching manifest and change it to have no storage bad_manifest = deepcopy(bye_world_manifest) bad_manifest.features &= ~contracts.ContractFeatures.HAS_STORAGE engine.push(vm.ByteStringStackItem(str(bad_manifest).encode())) engine.push(vm.ByteStringStackItem(bye_world_nef.script)) with self.assertRaises(ValueError) as context: engine.invoke_syscall_by_name("System.Contract.Update") self.assertEqual( "Error: New contract does not support storage while old contract has existing storage", str(context.exception))
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())) nef = contracts.NEF(script=script.to_array()) contract_hash = to_script_hash(nef.script) contract = contracts.ContractState(1, nef, self.manifest, 0, contract_hash) engine.snapshot.contracts.put(contract) storage_key = storage.StorageKey(contract.id, 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 storage_put(engine: contracts.ApplicationEngine, context: storage.StorageContext, key: bytes, value: bytes) -> None: if len(key) > MAX_STORAGE_KEY_SIZE: raise ValueError( f"Storage key length exceeds maximum of {MAX_STORAGE_KEY_SIZE}") if len(value) > MAX_STORAGE_VALUE_SIZE: raise ValueError( f"Storage value length exceeds maximum of {MAX_STORAGE_VALUE_SIZE}" ) if context.is_read_only: raise ValueError("Cannot persist to read-only storage context") storage_key = storage.StorageKey(context.id, key) item = engine.snapshot.storages.try_get(storage_key, read_only=False) if item is None: new_data_len = len(key) + len(value) item = storage.StorageItem(b'') engine.snapshot.storages.put(storage_key, item) else: if len(value) == 0: new_data_len = 0 elif len(value) <= len(item.value): new_data_len = (len(value) - 1) // 4 + 1 elif len(item.value) == 0: new_data_len = len(value) else: new_data_len = (len(item.value) - 1) // 4 + 1 + len(value) - len( item.value) engine.add_gas(new_data_len * engine.STORAGE_PRICE) item.value = value
def test_storage_put_new(self): # see `test_storage_get_key_not_found()` for a description on why the storage is setup with a script as is # setup engine = test_engine(has_snapshot=True) script = vm.ScriptBuilder() script.emit(vm.OpCode.PUSH2) # storage put value script.emit(vm.OpCode.PUSH1) # storage put key script.emit_syscall(syscall_name_to_int("System.Storage.GetContext")) script.emit_syscall(syscall_name_to_int("System.Storage.Put")) engine.load_script(vm.Script(script.to_array())) nef = contracts.NEF(script=script.to_array()) manifest = contracts.ContractManifest(f"contractname1") manifest.abi.methods = [ contracts.ContractMethodDescriptor( "test_func", 0, [], contracts.ContractParameterType.ANY, True) ] hash_ = to_script_hash(nef.script) contract = contracts.ContractState(1, nef, manifest, 0, hash_) engine.snapshot.contracts.put(contract) engine.execute() self.assertEqual(vm.VMState.HALT, engine.state) storage_key = storage.StorageKey(1, b'\x01') item = engine.snapshot.storages.try_get(storage_key) self.assertIsNotNone(item) self.assertEqual(b'\x02', item.value)
def test_policy_unblock_account(self): # we've tested the full round trip via "System.Contract.Call" in the test # test_policy_set_and_get_blocked_accounts() # Here we take the shortcut and test the unblock account function directly engine = test_engine(has_snapshot=True) block = test_block(0) engine.snapshot.persisting_block = block engine.invoke_syscall_by_name("Neo.Native.Deploy") # we must add a script_container with valid signature to pass the check_comittee() validation check # in the function itself 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] policy = contracts.PolicyContract() account_not_found = types.UInt160(data=b'\x11' * 20) account = types.UInt160.zero() self.assertTrue(policy._block_account(engine, account)) self.assertFalse(policy._unblock_account(engine, account_not_found)) self.assertTrue(policy._unblock_account(engine, account)) storage_key = storage.StorageKey(policy.script_hash, policy._PREFIX_BLOCKED_ACCOUNTS) storage_item = engine.snapshot.storages.try_get(storage_key) self.assertIsNotNone(storage_item) self.assertEqual(b'\x00', storage_item.value)
def _storage_put_internal(engine: contracts.ApplicationEngine, context: storage.StorageContext, key: bytes, value: bytes, flags: storage.StorageFlags) -> None: if len(key) > MAX_STORAGE_KEY_SIZE: raise ValueError( f"Storage key length exceeds maximum of {MAX_STORAGE_KEY_SIZE}") if len(value) > MAX_STORAGE_VALUE_SIZE: raise ValueError( f"Storage value length exceeds maximum of {MAX_STORAGE_VALUE_SIZE}" ) if context.is_read_only: raise ValueError("Cannot persist to read-only storage context") storage_key = storage.StorageKey(context.script_hash, key) item = engine.snapshot.storages.try_get(storage_key, read_only=False) is_constant = storage.StorageFlags.CONSTANT in flags if item is None: new_data_len = len(key) + len(value) item = storage.StorageItem(b'', is_constant) engine.snapshot.storages.put(storage_key, item) else: if item.is_constant: raise ValueError("StorageItem is marked as constant") if len(value) <= len(item.value): new_data_len = 1 else: new_data_len = len(value) - len(item.value) engine.add_gas(new_data_len * STORAGE_PRICE) item.value = value item.is_constant = is_constant
def test_storage_get_key_not_found(self): engine = test_engine(has_snapshot=True) script = vm.ScriptBuilder() # key parameter for the `Get` syscall script.emit(vm.OpCode.PUSH2) script.emit_syscall(syscall_name_to_int("System.Storage.GetContext")) # at this point our stack looks like follows # * storage context # * key script.emit_syscall(syscall_name_to_int("System.Storage.Get")) engine.load_script(vm.Script(script.to_array())) # we set the script parameter of the ContractState to our script # which ensures that `engine.current_scripthash` matches the script we manually build above # this basically means the engine thinks it is running a smart contract that we can find in our storage # which in turns enables us to call the `System.Storage.GetContext` syscall 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.assertIsInstance(item, vm.NullStackItem)
def test_storage_put_overwrite(self): # test with new data being shorter than the old data engine = test_engine(has_snapshot=True) key = b'\x01' storage_key = storage.StorageKey(types.UInt160.zero(), key) storage_item = storage.StorageItem(b'\x11\x22\x33', is_constant=False) engine.snapshot.storages.put(storage_key, storage_item) ctx = storage.StorageContext(types.UInt160.zero(), is_read_only=False) new_item_value = b'\x11\x22' contracts.interop._storage_put_internal(engine, ctx, key, new_item_value, storage.StorageFlags.NONE) item = engine.snapshot.storages.get(storage_key) self.assertIsNotNone(item) self.assertEqual(new_item_value, item.value) # now test with data being longer than before longer_item_value = b'\x11\x22\x33\x44' contracts.interop._storage_put_internal(engine, ctx, key, longer_item_value, storage.StorageFlags.NONE) item = engine.snapshot.storages.get(storage_key) self.assertIsNotNone(item) self.assertEqual(longer_item_value, item.value)
def test_storage_put_new(self): # see `test_storage_get_key_not_found()` for a description on why the storage is setup with a script as is for i in range(2): # setup engine = test_engine(has_snapshot=True) script = vm.ScriptBuilder() if i == 0: script.emit(vm.OpCode.PUSH2) # storage put value script.emit(vm.OpCode.PUSH1) # storage put key script.emit_syscall( syscall_name_to_int("System.Storage.GetContext")) script.emit_syscall(syscall_name_to_int("System.Storage.Put")) else: script.emit(vm.OpCode.PUSH0) # storage put call flags script.emit(vm.OpCode.PUSH2) # storage put value script.emit(vm.OpCode.PUSH1) # storage put key script.emit_syscall( syscall_name_to_int("System.Storage.GetContext")) script.emit_syscall( syscall_name_to_int("System.Storage.PutEx")) engine.load_script(vm.Script(script.to_array())) contract = storage.ContractState(script=script.to_array(), _manifest=self.manifest) engine.snapshot.contracts.put(contract) engine.execute() self.assertEqual(vm.VMState.HALT, engine.state) storage_key = storage.StorageKey(contract.script_hash(), b'\x01') item = engine.snapshot.storages.try_get(storage_key) self.assertIsNotNone(item) self.assertEqual(b'\x02', item.value)
def test_storage_get_key_not_found(self): engine = test_engine(has_snapshot=True, has_container=True) script = vm.ScriptBuilder() # key parameter for the `Storage.Get` syscall script.emit(vm.OpCode.PUSH2) script.emit_syscall(syscall_name_to_int("System.Storage.GetContext")) # at this point our stack looks like follows # * storage context # * key script.emit_syscall(syscall_name_to_int("System.Storage.Get")) engine.load_script(vm.Script(script.to_array())) # we have to store our contract or some sanity checks will fail (like getting a StorageContext nef = contracts.NEF(script=script.to_array()) contract = contracts.ContractState(1, nef, self.manifest, 0, to_script_hash(nef.script)) engine.snapshot.contracts.put(contract) storage_key = storage.StorageKey(contract.id, 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.assertIsInstance(item, vm.NullStackItem)
def init(self): super(NonFungibleToken, self).init() self.key_total_suppply = storage.StorageKey(self._id, b'\x0b') self.key_token = storage.StorageKey(self._id, b'\x05') self.key_account = storage.StorageKey(self._id, b'\x07') self.manifest.abi.events = [ contracts.ContractEventDescriptor( "Transfer", parameters=[ contracts.ContractParameterDefinition("from", contracts.ContractParameterType.HASH160), contracts.ContractParameterDefinition("to", contracts.ContractParameterType.HASH160), contracts.ContractParameterDefinition("amount", contracts.ContractParameterType.INTEGER), contracts.ContractParameterDefinition("tokenId", contracts.ContractParameterType.BYTEARRAY) ] ) ]
def storage_get(engine: contracts.ApplicationEngine, context: storage.StorageContext, key: bytes) -> Optional[bytes]: storage_key = storage.StorageKey(context.script_hash, key) item = engine.snapshot.storages.try_get(storage_key, read_only=True) if item is not None: return item.value return None
def test_len(self): contract = 1 key_val = b'\x01\x02\x03' sk = storage.StorageKey(contract, key_val) # contract id is serialized to int32 expected_len = 4 + len(key_val) self.assertEqual(expected_len, len(sk))
def create_key(self, prefix: bytes) -> storage.StorageKey: """ Helper to create a storage key for the contract Args: prefix: the storage prefix to be used """ return storage.StorageKey(self._id, prefix)
def init(self): super(FungibleToken, self).init() self.key_account = storage.StorageKey(self._id, b'\x14') self.key_total_supply = storage.StorageKey(self._id, b'\x0B') self.manifest.supported_standards = ["NEP-17"] self.manifest.abi.events = [ contracts.ContractEventDescriptor( "Transfer", parameters=[ contracts.ContractParameterDefinition("from", contracts.ContractParameterType.HASH160), contracts.ContractParameterDefinition("to", contracts.ContractParameterType.HASH160), contracts.ContractParameterDefinition("amount", contracts.ContractParameterType.INTEGER) ] ) ] self.factor = pow(vm.BigInteger(10), vm.BigInteger(self._decimals))
def test_serialization(self): contract = 1 key_val = b'\x01\x02\x03' sk = storage.StorageKey(contract, key_val) # test serialize expected_value = b'\x01\x00\x00\x00' + key_val self.assertEqual(expected_value, sk.to_array()) # test deserialize self.assertEqual(sk, storage.StorageKey.deserialize_from_bytes(expected_value))
def test_various(self): contract = 1 key_val = b'\x01\x02\x03' sk = storage.StorageKey(contract, key_val) # test __repr__ in absence of __str__ self.assertIn("<StorageKey at ", str(sk)) self.assertIn(r"[1] b'\x01\x02\x03'", str(sk)) # test __hash__ self.assertEqual(2161234437, hash(sk))
def storage_delete(engine: contracts.ApplicationEngine, context: storage.StorageContext, key: bytes) -> None: if context.is_read_only: raise ValueError("Cannot delete from read-only storage context") storage_key = storage.StorageKey(context.script_hash, key) item = engine.snapshot.storages.try_get(storage_key) if item and item.is_constant: raise ValueError( "Cannot delete a storage item that is marked constant") engine.snapshot.storages.delete(storage_key)
def test_len(self): contract = types.UInt160.zero() key_val = b'\x01\x02\x03' sk = storage.StorageKey(contract, key_val) group_size = 16 group_remainder_size = 1 # len(key_val) is smaller than group_size # thus we get (see implementation of write_bytes_with_grouping) to understand the logic expected_len = len(contract) + group_size + group_remainder_size self.assertEqual(expected_len, len(sk))
def test_addition(self): sk = storage.StorageKey(1, b'\x01') new_sk = sk + b'\x02' self.assertNotEqual(id(sk), id(new_sk)) self.assertNotEqual(sk.key, new_sk.key) self.assertEqual(new_sk.key, b'\x01\x02') # test with serializable type new_sk2 = sk + types.UInt160.zero() self.assertEqual(new_sk2.key, b'\x01' + b'\x00' * 20) with self.assertRaises(TypeError) as context: sk + 1 self.assertEqual("unsupported operand type(s) for +: 'StorageKey' and 'int'", str(context.exception))
def test_serialization(self): contract = types.UInt160.zero() key_val = b'\x01\x02\x03' key_val_padding = bytearray(16 - len(key_val)) key_val_group_remainder = b'\x03' sk = storage.StorageKey(contract, key_val) # test serialize expected_value = contract.to_array( ) + key_val + key_val_padding + key_val_group_remainder self.assertEqual(expected_value, sk.to_array()) # test deserialize self.assertEqual( sk, storage.StorageKey.deserialize_from_bytes(expected_value))
def test_storage_get_ok(self): engine = test_engine(has_snapshot=True) engine.snapshot.contracts.put(self.contract) storage_key = storage.StorageKey(self.contract.id, b'\x01') storage_item = storage.StorageItem(b'\x11') engine.snapshot.storages.put(storage_key, storage_item) ctx = engine.invoke_syscall_by_name("System.Storage.GetContext") engine.push(vm.ByteStringStackItem(storage_key.key)) engine.push(vm.StackItem.from_interface(ctx)) returned_value = engine.invoke_syscall_by_name("System.Storage.Get") self.assertEqual(storage_item.value, returned_value)
def test_delete_ok(self): engine = test_engine(has_snapshot=True) engine.snapshot.contracts.put(self.contract) storage_key = storage.StorageKey(self.contract.id, b'\x01') storage_item = storage.StorageItem(b'\x11') engine.snapshot.storages.put(storage_key, storage_item) ctx = engine.invoke_syscall_by_name("System.Storage.GetContext") engine.push(vm.ByteStringStackItem(storage_key.key)) engine.push(vm.StackItem.from_interface(ctx)) engine.invoke_syscall_by_name("System.Storage.Delete") self.assertIsNone(engine.snapshot.storages.try_get(storage_key))
def test_storage_delete_constant_item(self): engine = test_engine(has_snapshot=True) engine.snapshot.contracts.put(self.contract) storage_key = storage.StorageKey(self.contract.script_hash(), b'\x01') storage_item = storage.StorageItem(b'\x11', is_constant=True) engine.snapshot.storages.put(storage_key, storage_item) ctx = engine.invoke_syscall_by_name("System.Storage.GetContext") engine.push(vm.ByteStringStackItem(storage_key.key)) engine.push(vm.StackItem.from_interface(ctx)) with self.assertRaises(ValueError) as context: engine.invoke_syscall_by_name("System.Storage.Delete") self.assertEqual( "Cannot delete a storage item that is marked constant", str(context.exception))
def test_storage_delete_readonly_context(self): engine = test_engine(has_snapshot=True) engine.snapshot.contracts.put(self.contract) storage_key = storage.StorageKey(self.contract.id, b'\x01') storage_item = storage.StorageItem(b'\x11') engine.snapshot.storages.put(storage_key, storage_item) ctx = engine.invoke_syscall_by_name( "System.Storage.GetReadOnlyContext") engine.push(vm.ByteStringStackItem(storage_key.key)) engine.push(vm.StackItem.from_interface(ctx)) with self.assertRaises(ValueError) as context: engine.invoke_syscall_by_name("System.Storage.Delete") self.assertEqual("Cannot delete from read-only storage context", 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))