Skip to content

Commit

Permalink
https://github.com/neo-project/neo/pull/2060
Browse files Browse the repository at this point in the history
  • Loading branch information
ixje committed Jan 25, 2021
1 parent 91a6308 commit 1e98b99
Show file tree
Hide file tree
Showing 12 changed files with 14 additions and 137 deletions.
1 change: 0 additions & 1 deletion neo3/contracts/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from .contracttypes import (TriggerType)
from .descriptor import (ContractPermissionDescriptor)
from .manifest import (ContractGroup,
ContractFeatures,
ContractManifest,
ContractPermission,
WildcardContainer)
Expand Down
24 changes: 9 additions & 15 deletions neo3/contracts/interop/contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,22 +59,20 @@ 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_

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)
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)

Expand All @@ -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")
Expand All @@ -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,
Expand Down
5 changes: 0 additions & 5 deletions neo3/contracts/interop/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)


Expand Down
24 changes: 1 addition & 23 deletions neo3/contracts/manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -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] = []

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand All @@ -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)),
Expand Down
2 changes: 0 additions & 2 deletions neo3/contracts/native/designate.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
6 changes: 0 additions & 6 deletions neo3/contracts/native/nativecontract.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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)

Expand Down
1 change: 0 additions & 1 deletion neo3/contracts/native/oracle.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
12 changes: 1 addition & 11 deletions neo3/storage/contractstate.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down
41 changes: 0 additions & 41 deletions tests/contracts/interop/test_contract_interop.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,6 @@ def main() -> str:

raw_hello_world_manifest = {
"groups": [],
"features": {
"storage": False,
"payable": False
},
"abi": {
"hash": "0x20caf3711a574b0be8c5746d85db2ee1e85aed3b",
"methods": [],
Expand All @@ -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": [],
Expand Down Expand Up @@ -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": [
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -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
Expand Down
6 changes: 0 additions & 6 deletions tests/contracts/interop/test_native.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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()

Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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())
Expand Down
19 changes: 1 addition & 18 deletions tests/contracts/interop/test_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Loading

0 comments on commit 1e98b99

Please sign in to comment.