This repository has been archived by the owner on May 1, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy pathtcv.py
237 lines (188 loc) · 7.28 KB
/
tcv.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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
import json
from pyteal.ast import scratch
import uvarint
import sys
from pyteal import ScratchVar, For, TealType, Int, Seq, Concat, Substring, Assert
from pyteal import Subroutine, Btoi, Bytes, Sha256, If, GetByte, Or, And, Return
# goal clerk compile ../listing.tmpl.teal
# python tcv.py ../listing.tmpl.teal
# goal clerk compile -D listing.tmpl.teal.populated
# diff ../listing.tmpl.teal.tok ../listing.tmpl.teal.blanked
def get_validate_subroutine(tc):
details = get_details(tc+".map.json")
# Create a bytestring of 8 byte integer values for the position of
# Each template variable, can be retrieved with get_int_from_list
positions = b""
isBytes = b""
for _,v in details.items():
positions += (v['position']).to_bytes(8,'big')
isBytes += (int(v['bytes'])).to_bytes(8, 'big')
@Subroutine(TealType.uint64)
def validate(txn, hash):
blanked = ScratchVar(TealType.Bytes)
lastpos = ScratchVar()
nextpos = ScratchVar()
pos_list = Bytes(positions)
type_list = Bytes(isBytes)
x = ScratchVar()
init = Seq(
x.store(0),
blanked.store(Bytes("")),
lastpos.store(0),
nextpos.store(0)
)
cond = x.load()<len(details.items())
iter = x.store(x.load() + Int(1))
return Seq(
For(init, cond, iter).Do(
Seq(
#Take the last pos to the next pos
nextpos.store(get_int_from_list(pos_list, x.load()+Int(1))),
Concat(
blanked.load(),
Substring(txn.ApplicationArgs[0], lastpos.load(), nextpos.load())
),
lastpos.store(nextpos.load() + get_length(txn.ApplicationArgs[0], nextpos.load(), get_int_from_list(type_list, x.load()+Int(1)))),
)
),
Sha256(blanked.load()) == hash
)
return validate
#def parse_uvarint(buf):
# return
@Subroutine(TealType.uint64)
def get_length(s, pos, isbyte):
return If(isbyte,
read_uvarint(Substring(s, pos, pos+Int(10))),
read_uvarint_length(Substring(s, pos, pos+Int(10)))
)
@Subroutine(TealType.uint64)
def read_uvarint(buf):
x = ScratchVar()
s = ScratchVar()
i = ScratchVar()
b = ScratchVar()
init = Seq(
x.store(0),
s.store(0),
i.store(0),
b.store(0)
)
cond = i.load()<=Int(9)
iter = i.store(i.load() + Int(1))
return Seq(
For(init, cond, iter).Do(
Seq(
b.store(GetByte(buf, i.load())),
If(b.load()<Int(128),
If( Or( i.load()>Int(9), And(i.load==Int(9), b.load()>Int(1)) ), Assert(0)), #Failed to parse?
Return(x.load() | (b.load() << s.load()))
),
x.store(x.load() | ((b.load() & Int(127)) << s.load())),
s.store(s.load() + Int(7))
)
),
x.load()
)
@Subroutine(TealType.uint64)
def read_uvarint_length():
return Int(0)
# Takes a bytestring of 8 byte uint64 and treats it as an array,
# converting the idx to a position in the byteslice and returning
# the integer value of the 8 byte substring
@Subroutine(TealType.uint64)
def get_int_from_list(arr, idx):
return Btoi(Substring(arr, idx * Int(8), ((idx + Int(1)) * Int(8))))
# blank_contract takes a path to a template contract and writes out a
# version with the populated template variables replaced with 0s
# it assumes the populated version will be in the same dir with .populated suffix
# it also assumes the .map.json will be there with the template variable details
def blank_contract(tc):
contract = list(get_contract(tc+".populated"))
details = get_details(tc+".map.json")
found = {}
for k,v in details.items():
pos = v['position']
if v['bytes']:
val, l = uvarint.decode(contract[pos:])
total = l + val
found[k] = bytearray(contract[pos+l:pos+total]).hex()
contract[pos:pos+total] = [0]
else:
val, l = uvarint.decode(contract[pos:])
total = l
found[k] = val
contract[pos:pos+total] = [0]
with open(tc+".blanked", "wb") as blanked_contract:
blanked_contract.write(bytearray(contract))
print("Wrote blanked contract to {}".format(tc+".blanked"))
return found
# populate_contract takes a path to a template contract and set of named vars
# and inserts them into the contract bytecode, taking care to set appropriate length
# fields and adjusting the positions for the difference in lengths accrued
# it assumes the compiled bytecode is in the same directory with a `.tok` suffix
# it also assumes the template variable details are present in the same dir with `.map.json` suffix
def populate_contract(tc, tvars):
contract = list(get_contract(tc+".tok"))
details = get_details(tc+".map.json")
shift = 0
for k, v in details.items():
if k in tvars:
pos = v['position'] + shift
if v['bytes']:
val = bytes.fromhex(tvars[k])
lbyte = uvarint.encode(len(val))
# -1 to account for the existing 00 byte for length
shift += (len(lbyte)-1) + len(val)
# +1 to overwrite the existing 00 byte for length
contract[pos:pos+1] = lbyte + val
else:
val = uvarint.encode(tvars[k])
# -1 to account for existing 00 byte
shift += len(val) - 1
#+1 to overwrite existing 00 byte
contract[pos:pos+1] = val
with open(tc+".populated", "wb") as populated_contract:
populated_contract.write(bytearray(contract))
print("Wrote populted contract to {}".format(tc+".populated"))
def get_contract(fname):
contract = None
with open(fname, 'rb') as f:
contract = f.read()
return contract
def get_details(fname):
details = None
with open(fname, 'r') as f:
details = json.load(f)
# Make sure they're sorted into the order they appear in
# the contract or the `shift` will be wrong
return dict(sorted(details['template_labels'].items(), key=lambda item: item[1]['position']))
def check_match(fname):
original = None
blanked = None
with open(fname+".tok", "rb") as f:
original = f.read()
with open(fname+".blanked", "rb") as f:
blanked = f.read()
return original == blanked
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Please supply the path to the template contract")
sys.exit()
print("populating {}".format(sys.argv[1]))
vars = {
"TMPL_APP_ID": 1231123,
"TMPL_ASSET_ID": "0000000000000000",
"TMPL_CREATOR_ADDR": "deadbeef",
"TMPL_FEE_AMT": 1000000,
"TMPL_OWNER_ADDR": "deadbeef",
"TMPL_PRICE_ID": 5,
"TMPL_SEED_AMT": 10000,
"TMPL_OTHER_OWNER_ADDR": "deadbeef",
}
populate_contract(sys.argv[1], vars)
found_vars = blank_contract(sys.argv[1])
for k,v in vars.items():
print("{} match? {}".format(k, v == found_vars[k]))
print("Blanked Match original?: {}".format(check_match(sys.argv[1])))
# check to make sure the argv[1].tok == argv[1].blanked