-
Notifications
You must be signed in to change notification settings - Fork 0
/
AnyUpdateRenter.py
188 lines (154 loc) · 6.35 KB
/
AnyUpdateRenter.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
from typing import List, Any, cast
from boa3.builtin.interop.iterator import Iterator
from boa3.builtin import NeoMetadata, metadata, public
from boa3.builtin.interop.contract import GAS, update_contract, call_contract
from boa3.builtin.interop.runtime import time, executing_script_hash, calling_script_hash, check_witness
from boa3.builtin.interop.storage import get, put, find, findoptions, StorageMap, get_context
from boa3.builtin.type import UInt160
context = get_context()
"""
Rent an AnyUpdateSafeForRent contract to enjoy exclusive writing permission!
Storage:
key || value
address[i] UInt160 script hash of AnyUpdateSafeForRent contracts
expire[i] timestamp (millisecond) of the rental expiry of each AnyUpdateSafeForRent contract
owner UInt160 script hash of the owner of this AnyUpdateRenter
price amount of GAS (*1e-8) to pay for each millisecond of rental
"""
@metadata
def manifest_metadata() -> NeoMetadata:
meta = NeoMetadata()
meta.author = 'github.com/Hecate2'
meta.description = 'Manager of AnyUpdateSafeRent contracts'
meta.email = 'chenxinhao@ngd.neo.org'
return meta
@public
def _deploy(data: Any, _update: bool):
if not _update:
put(b'owner', UInt160(b'\xa5\xb7QTKV\xdf\xd22\xe0\xea+\x8e\x0c\x9aG\xa2?\x98\xb1'))
put(b'price', 1) # 1*1e-8 GAS per millisecond; 0.864 GAS per day
"""
Owner's methods
"""
@public
def update(script: bytes, manifest: bytes, data: Any):
assert check_witness(cast(UInt160, get(b'owner')))
update_contract(script, manifest, data)
@public
def setOwner(new_owner: UInt160):
owner = cast(UInt160, get(b'owner'))
assert check_witness(owner)
put(b'owner', new_owner)
@public
def setPricePerBlock(price: int):
"""
:param price: how many GAS (1e8) to pay for 1 block
setting price == 1 means 1*1e-8 GAS per millisecond; 0.864 GAS per day
"""
assert check_witness(cast(UInt160, get(b'owner')))
put(b'price', price)
@public
def withdraw(to: UInt160, amount: int):
"""
Allows to get the GAS fees stored in this contract
:param to: where to send the GAS to
:param amount: how much GAS to send
"""
assert check_witness(cast(UInt160, get(b'owner')))
assert call_contract(GAS, 'transfer', [executing_script_hash, to, amount, None])
@public
def registerContract(i: int, address: UInt160):
"""
register a new contract's scripthash to be rented by tenants
:param i: The key to store this contract.
Owner should be aware not to overwrite other keys
:param address: scripthash of AnyUpdateSafeForRent contract.
The owner of registered AnyUpdateSafeForRent should be this AnyUpdateRenter
"""
assert check_witness(cast(UInt160, get(b'owner')))
i_bytes = i.to_bytes()
StorageMap(context, b'address').put(i_bytes, address)
StorageMap(context, b'expire').put(i_bytes, 0)
@public
def unregisterContract(i: int):
assert check_witness(cast(UInt160, get(b'owner')))
i_bytes = i.to_bytes()
StorageMap(context, b'address').delete(i_bytes)
StorageMap(context, b'expire').delete(i_bytes)
"""
Methods for everyone
"""
@public
def readContractExpire() -> Iterator:
"""
The expiry timestamps of contracts for renting
"""
return find(b'expire', context)
@public
def readContractAddress() -> Iterator:
"""
The addresses of contracts for renting
"""
return find(b'address', context)
@public
def getPricePerBlock() -> int:
return cast(int, get(b'price'))
"""
Tenant's method
"""
@public
def requestRental(payer: UInt160, tenant: UInt160, rental_time: int, desired_contract_ids: List[int]) -> UInt160:
"""
The caller of this method pays the rental
:param payer: who will pay for the rental
:param tenant: who will be the tenant who can use the rented contract
:param rental_time: rent for how many milliseconds
:param desired_contract_ids: which contract ids would you like to rent
if no contract in this list available, then raise Exception
leave it [] to allow all contracts
if you see an exception:
"Specified argument was out of the range of valid values."
when calling this method,
then you probably specified a wrong index of contract in `desired_contract_ids`
:return: the contract address assigned to the tenant
"""
expire_storage_map = StorageMap(context, b'expire')
address_storage_map = StorageMap(context, b'address')
if len(desired_contract_ids) > 0:
# zero_uint160 = UInt160(0) # not necessary
for contract_id in desired_contract_ids:
key = contract_id.to_bytes()
expire_time = cast(int, expire_storage_map.get(key))
if expire_time < time:
rented_contract = cast(UInt160, address_storage_map.get(key))
# if rented_contract == zero_uint160:
# continue
# not necessary
assert call_contract(
GAS, 'transfer',
[payer, executing_script_hash, cast(int, get(b'price')) * rental_time, None])
new_expire_time = time + rental_time
StorageMap(context, b'expire').put(key, new_expire_time)
call_contract(rented_contract, 'setTenant', [tenant, new_expire_time])
return rented_contract
else:
iterator = find(b'expire', context, findoptions.FindOptions.REMOVE_PREFIX)
while iterator.next():
expire_time = cast(int, iterator.value[1])
if expire_time < time:
assert call_contract(
GAS, 'transfer',
[payer, executing_script_hash, cast(int, get(b'price')) * rental_time, None])
key_bytes = cast(bytes, iterator.value[0])
# key_bytes = key_bytes[6:] # remove b'expire' at the beginning of the key
rented_contract = cast(UInt160, address_storage_map.get(key_bytes))
new_expire_time = time + rental_time
expire_storage_map.put(key_bytes, new_expire_time)
call_contract(rented_contract, 'setTenant', [tenant, new_expire_time])
return rented_contract
raise Exception('No contract available')
return tenant
@public
def onNEP17Payment(from_address: UInt160, amount: int, data: Any):
if calling_script_hash != GAS:
raise Exception('Only GAS allowed')