From 1e98b9938cc76ab29b80b22cc7caf2ee8220c7ca Mon Sep 17 00:00:00 2001 From: Erik van den Brink Date: Mon, 25 Jan 2021 13:02:07 +0100 Subject: [PATCH] https://github.com/neo-project/neo/pull/2060 --- neo3/contracts/__init__.py | 1 - neo3/contracts/interop/contract.py | 24 ++++------- neo3/contracts/interop/storage.py | 5 --- neo3/contracts/manifest.py | 24 +---------- neo3/contracts/native/designate.py | 2 - neo3/contracts/native/nativecontract.py | 6 --- neo3/contracts/native/oracle.py | 1 - neo3/storage/contractstate.py | 12 +----- .../interop/test_contract_interop.py | 41 ------------------- tests/contracts/interop/test_native.py | 6 --- tests/contracts/interop/test_storage.py | 19 +-------- tests/contracts/test_manifest.py | 10 +---- 12 files changed, 14 insertions(+), 137 deletions(-) diff --git a/neo3/contracts/__init__.py b/neo3/contracts/__init__.py index 54a63767..b49168aa 100644 --- a/neo3/contracts/__init__.py +++ b/neo3/contracts/__init__.py @@ -3,7 +3,6 @@ from .contracttypes import (TriggerType) from .descriptor import (ContractPermissionDescriptor) from .manifest import (ContractGroup, - ContractFeatures, ContractManifest, ContractPermission, WildcardContainer) diff --git a/neo3/contracts/interop/contract.py b/neo3/contracts/interop/contract.py index 73e5544d..fa1fb1a9 100644 --- a/neo3/contracts/interop/contract.py +++ b/neo3/contracts/interop/contract.py @@ -59,7 +59,6 @@ def contract_update(engine: contracts.ApplicationEngine, script: bytes, manifest 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_ @@ -67,14 +66,13 @@ def contract_update(engine: contracts.ApplicationEngine, script: bytes, manifest # 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) + 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) @@ -84,9 +82,6 @@ def contract_update(engine: contracts.ApplicationEngine, script: bytes, manifest 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") if len(script) != 0: method_descriptor = contract.manifest.abi.get_method("_deploy") @@ -110,9 +105,8 @@ def contract_destroy(engine: contracts.ApplicationEngine) -> None: engine.snapshot.contracts.delete(hash_) - if contract.has_storage: - for key, _ in engine.snapshot.storages.find(contract.script_hash(), b''): - engine.snapshot.storages.delete(key) + for key, _ in engine.snapshot.storages.find(contract.script_hash(), b''): + engine.snapshot.storages.delete(key) def contract_call_internal(engine: contracts.ApplicationEngine, diff --git a/neo3/contracts/interop/storage.py b/neo3/contracts/interop/storage.py index 5ab2cfc6..482732e4 100644 --- a/neo3/contracts/interop/storage.py +++ b/neo3/contracts/interop/storage.py @@ -11,17 +11,12 @@ @register("System.Storage.GetContext", 400, contracts.native.CallFlags.ALLOW_STATES, False, []) def get_context(engine: contracts.ApplicationEngine) -> storage.StorageContext: - contract = engine.snapshot.contracts.try_get(engine.current_scripthash, read_only=True) - if not contract.has_storage: - raise ValueError("Cannot get context for smart contract without storage") return storage.StorageContext(engine.current_scripthash, False) @register("System.Storage.GetReadOnlyContext", 400, contracts.native.CallFlags.ALLOW_STATES, False, []) def get_read_only_context(engine: contracts.ApplicationEngine) -> storage.StorageContext: contract = engine.snapshot.contracts.try_get(engine.current_scripthash, read_only=True) - if not contract.has_storage: - raise ValueError("Cannot get context for smart contract without storage") return storage.StorageContext(contract.script_hash(), True) diff --git a/neo3/contracts/manifest.py b/neo3/contracts/manifest.py index bcd0d26b..2c1613b1 100644 --- a/neo3/contracts/manifest.py +++ b/neo3/contracts/manifest.py @@ -134,14 +134,6 @@ def from_json(cls, json: dict) -> ContractPermission: return cls(cpd, methods) -class ContractFeatures(IntFlag): - NO_PROPERTY = 0, - #: Indicate the contract has storage. - HAS_STORAGE = 1 << 0 - #: Indicate the contract accepts tranfers. - PAYABLE = 1 << 2 - - class WildcardContainer(IJson): """ An internal helper class for ContractManifest attributes. @@ -262,8 +254,7 @@ def __init__(self, contract_hash: types.UInt160 = types.UInt160.zero()): """ Creates a default contract manifest if no arguments are supplied. - A default contract is not Payable and has no storage as configured by its features. - It may not be called by any other contracts + A default contract may not be called by any other contracts Args: contract_hash: the contract script hash to create a manifest for. @@ -272,9 +263,6 @@ def __init__(self, contract_hash: types.UInt160 = types.UInt160.zero()): #: same group to invoke it. self.groups: List[ContractGroup] = [] - #: Features describe what contract abilities are available. TODO: link to contract features - self.features: ContractFeatures = ContractFeatures.NO_PROPERTY - #: The list of NEP standards supported e.g. "NEP-3" self.supported_standards: List[str] = [] @@ -304,7 +292,6 @@ def __eq__(self, other): if not isinstance(other, type(self)): return False return (self.groups == other.groups - and self.features == other.features and self.abi == other.abi and self.permissions == other.permissions and self.trusts == other.trusts @@ -336,12 +323,7 @@ def _deserialize_from_json(self, json: dict) -> None: self.abi = contracts.ContractABI.from_json(json['abi']) self.contract_hash = self.abi.contract_hash self.groups = list(map(lambda g: ContractGroup.from_json(g), json['groups'])) - self.features = ContractFeatures.NO_PROPERTY self.supported_standards = json['supportedstandards'] - if json['features']['storage']: - self.features |= ContractFeatures.HAS_STORAGE - if json['features']['payable']: - self.features |= ContractFeatures.PAYABLE self.permissions = list(map(lambda p: ContractPermission.from_json(p), json['permissions'])) self.trusts = WildcardContainer.from_json_as_type( @@ -359,10 +341,6 @@ def to_json(self) -> dict: trusts = list(map(lambda m: "0x" + m, self.trusts.to_json()['wildcard'])) json = { "groups": list(map(lambda g: g.to_json(), self.groups)), - "features": { - "storage": contracts.ContractFeatures.HAS_STORAGE in self.features, - "payable": contracts.ContractFeatures.PAYABLE in self.features, - }, "supportedstandards": self.supported_standards, "abi": self.abi.to_json(), "permissions": list(map(lambda p: p.to_json(), self.permissions)), diff --git a/neo3/contracts/native/designate.py b/neo3/contracts/native/designate.py index 2d0c0e4e..855583a5 100644 --- a/neo3/contracts/native/designate.py +++ b/neo3/contracts/native/designate.py @@ -16,8 +16,6 @@ class DesignateContract(NativeContract): _id = -5 def init(self): - self.manifest.features = contracts.ContractFeatures.HAS_STORAGE - self._register_contract_method(self.get_designated_by_role, "getDesignatedByRole", 1000000, diff --git a/neo3/contracts/native/nativecontract.py b/neo3/contracts/native/nativecontract.py index f345d673..37301d3a 100644 --- a/neo3/contracts/native/nativecontract.py +++ b/neo3/contracts/native/nativecontract.py @@ -284,7 +284,6 @@ class PolicyContract(NativeContract): def init(self): super(PolicyContract, self).init() - self.manifest.features = contracts.ContractFeatures.HAS_STORAGE self._register_contract_method(self.get_max_block_size, "getMaxBlockSize", @@ -581,7 +580,6 @@ class Nep5Token(NativeContract): def init(self): super(Nep5Token, self).init() - self.manifest.features = contracts.ContractFeatures.HAS_STORAGE self.manifest.supported_standards = ["NEP-5"] self.manifest.abi.events = [ contracts.ContractEventDescriptor( @@ -773,10 +771,6 @@ def transfer(self, if account_from != engine.calling_scripthash and not engine.checkwitness(account_from): return False - contract_state_to = engine.snapshot.contracts.try_get(account_to, read_only=True) - if contract_state_to and not contract_state_to.is_payable: - return False - storage_key_from = storage.StorageKey(self.script_hash, self._PREFIX_ACCOUNT + account_from.to_array()) storage_item_from = engine.snapshot.storages.try_get(storage_key_from, read_only=False) diff --git a/neo3/contracts/native/oracle.py b/neo3/contracts/native/oracle.py index bbb6c533..aa564500 100644 --- a/neo3/contracts/native/oracle.py +++ b/neo3/contracts/native/oracle.py @@ -66,7 +66,6 @@ class OracleContract(NativeContract): def init(self): super(OracleContract, self).init() - self.manifest.features = contracts.ContractFeatures.HAS_STORAGE self.manifest.abi.events = [ contracts.ContractEventDescriptor( "OracleRequest", diff --git a/neo3/storage/contractstate.py b/neo3/storage/contractstate.py index 26b6e9c1..4c6b2c24 100644 --- a/neo3/storage/contractstate.py +++ b/neo3/storage/contractstate.py @@ -28,14 +28,6 @@ def __eq__(self, other): def __deepcopy__(self, memodict={}): return ContractState.deserialize_from_bytes(self.to_array()) - @property - def has_storage(self) -> bool: - return contracts.ContractFeatures.HAS_STORAGE in self.manifest.features - - @property - def is_payable(self) -> bool: - return contracts.ContractFeatures.PAYABLE in self.manifest.features - def serialize(self, writer: BinaryWriter) -> None: writer.write_var_bytes(self.script) writer.write_serializable(self.manifest) @@ -62,9 +54,7 @@ def to_stack_item(self, reference_counter: vm.ReferenceCounter) -> vm.StackItem: array = vm.ArrayStackItem(reference_counter) script = vm.ByteStringStackItem(self.script) manifest = vm.ByteStringStackItem(str(self.manifest)) - has_storage = vm.BooleanStackItem(self.has_storage) - is_payable = vm.BooleanStackItem(self.is_payable) - array.append([script, manifest, has_storage, is_payable]) + array.append([script, manifest]) return array @classmethod diff --git a/tests/contracts/interop/test_contract_interop.py b/tests/contracts/interop/test_contract_interop.py index 9bdb7b77..f71bc29f 100644 --- a/tests/contracts/interop/test_contract_interop.py +++ b/tests/contracts/interop/test_contract_interop.py @@ -19,10 +19,6 @@ def main() -> str: raw_hello_world_manifest = { "groups": [], - "features": { - "storage": False, - "payable": False - }, "abi": { "hash": "0x20caf3711a574b0be8c5746d85db2ee1e85aed3b", "methods": [], @@ -46,10 +42,6 @@ def main() -> str: raw_bye_world_nef = b'NEF3neo3-boa by COZ\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x0c\tbye world@\x9f\xff\x95\xd0' raw_bye_world_manifest = { "groups": [], - "features": { - "storage": False, - "payable": False - }, "abi": { "hash": "0xbf15664f6d3ecb0ff82ebe001257263b50a314c4", "methods": [], @@ -89,10 +81,6 @@ def test_func2(value: int) -> int: raw_contract3_nef = b'NEF3neo3-boa by COZ\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x11@\x12@W\x00\x01\x11x\x9e@P\x9c\xb5\xb0' raw_contract3_manifest = { "groups": [], - "features": { - "storage": False, - "payable": False - }, "abi": { "hash": "0xad8c3929e008a0a981dcb5e3c3a0928becdc2a41", "methods": [ @@ -195,8 +183,6 @@ def test_contract_create_ok(self): # returns a serialized contract state self.assertEqual(hello_world_nef.script, item[0].to_array()) self.assertEqual(hello_world_manifest, contracts.ContractManifest.from_json(json.loads(item[1].to_array()))) - self.assertEqual(contracts.ContractFeatures.HAS_STORAGE in hello_world_manifest.features, item[2]) - self.assertEqual(contracts.ContractFeatures.PAYABLE in hello_world_manifest.features, item[3]) return engine def test_contract_create_already_exits(self): @@ -241,7 +227,6 @@ 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') @@ -363,32 +348,6 @@ def test_contract_update_exceptions5(self): engine.invoke_syscall_by_name("System.Contract.Update") self.assertEqual("Error: manifest does not match with script", 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_contract_call(self): engine = test_engine(has_snapshot=True, default_script=False) # current executing contract diff --git a/tests/contracts/interop/test_native.py b/tests/contracts/interop/test_native.py index 8f612c90..798b7343 100644 --- a/tests/contracts/interop/test_native.py +++ b/tests/contracts/interop/test_native.py @@ -554,7 +554,6 @@ def test_to_account_not_payable(self): def test_transfer_from_empty_account(self): gas = contracts.GasToken() manifest = contracts.ContractManifest() - manifest.features = contracts.ContractFeatures.PAYABLE state = storage.ContractState(b'\x00', manifest) engine = self.transfer_helper(gas, types.UInt160.zero(), state.script_hash(), vm.BigInteger(1)) @@ -573,7 +572,6 @@ def test_transfer_zero_amount(self): 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 = vm.BigInteger(0) @@ -611,7 +609,6 @@ def test_transfer_more_than_balance(self): 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 @@ -630,7 +627,6 @@ def test_transfer_to_self(self): gas = contracts.GasToken() manifest = contracts.ContractManifest() - manifest.features = contracts.ContractFeatures.PAYABLE state_to = storage.ContractState(b'\x00' * 20, manifest) account = state_to.script_hash() @@ -669,7 +665,6 @@ 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() @@ -712,7 +707,6 @@ 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()) diff --git a/tests/contracts/interop/test_storage.py b/tests/contracts/interop/test_storage.py index b885d691..17767dbf 100644 --- a/tests/contracts/interop/test_storage.py +++ b/tests/contracts/interop/test_storage.py @@ -14,26 +14,9 @@ def shortDescription(self): 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_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_get_context_ok(self): + def test_get_context(self): engine = test_engine(has_snapshot=True) engine.snapshot.contracts.put(self.contract) ctx = engine.invoke_syscall_by_name("System.Storage.GetContext") diff --git a/tests/contracts/test_manifest.py b/tests/contracts/test_manifest.py index 5b6472f1..4f1a9ee7 100644 --- a/tests/contracts/test_manifest.py +++ b/tests/contracts/test_manifest.py @@ -212,7 +212,6 @@ def setUpClass(cls) -> None: var manifest = new ContractManifest() { Groups = new ContractGroup[0], - Features = ContractFeatures.NoProperty, SupportedStandards = Array.Empty(), Abi = new ContractAbi() { @@ -228,13 +227,13 @@ def setUpClass(cls) -> None: Console.WriteLine($"{manifest.Size}"); Console.WriteLine($"{manifest.ToJson()}"); """ - cls.expected_json = {"groups":[],"features":{"storage":False,"payable":False},"supportedstandards":[],"abi":{"hash":"0x0000000000000000000000000000000000000000","methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safemethods":[],"extra":None} + cls.expected_json = {"groups":[],"supportedstandards":[],"abi":{"hash":"0x0000000000000000000000000000000000000000","methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safemethods":[],"extra":None} def test_create_default(self): cm = contracts.ContractManifest(types.UInt160.zero()) self.assertEqual(self.expected_json, cm.to_json()) # see setupClass for C# reference code - self.assertEqual(259, len(cm)) + self.assertEqual(212, len(cm)) def test_serialize(self): # if test_create_default() passes, then we know `to_json()` is ok, which serialize internally uses @@ -260,14 +259,9 @@ def test_to_json_with_trusts_safemethods_extra(self): def test_from_json(self): expected_json = deepcopy(self.expected_json) - # we update the defaults to also test the features - new_features = {'storage': True, 'payable':True} - expected_json['features'] = new_features cm = contracts.ContractManifest.from_json(expected_json) default = contracts.ContractManifest(types.UInt160.zero()) self.assertEqual(default.groups, cm.groups) - self.assertIn(contracts.ContractFeatures.HAS_STORAGE, cm.features) - self.assertIn(contracts.ContractFeatures.PAYABLE, cm.features) self.assertEqual(default.permissions, cm.permissions) self.assertEqual(default.trusts, cm.trusts) self.assertEqual(default.safe_methods, cm.safe_methods)