-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathportal_core.py
455 lines (372 loc) · 20.1 KB
/
portal_core.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
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
from time import time, sleep
from typing import List, Tuple, Dict, Any, Optional, Union
from base64 import b64decode
import base64
import random
import hashlib
import uuid
import sys
import json
import uvarint
from local_blob import LocalBlob
from TmplSig import TmplSig
from algosdk.v2client.algod import AlgodClient
from algosdk.kmd import KMDClient
from algosdk import account, mnemonic
from algosdk.encoding import decode_address
from algosdk.future import transaction
from pyteal import compileTeal, Mode, Expr
from pyteal import *
from algosdk.logic import get_application_address
from algosdk.future.transaction import LogicSigAccount
import pprint
max_keys = 16
max_bytes_per_key = 127
bits_per_byte = 8
bits_per_key = max_bytes_per_key * bits_per_byte
max_bytes = max_bytes_per_key * max_keys
max_bits = bits_per_byte * max_bytes
def fullyCompileContract(client: AlgodClient, contract: Expr) -> bytes:
teal = compileTeal(contract, mode=Mode.Application, version=6)
response = client.compile(teal)
return response
def getCoreContracts( client: AlgodClient,
seed_amt: int = 0,
tmpl_sig: TmplSig = None,
) -> Tuple[bytes, bytes]:
def vaa_processor_program(seed_amt: int, tmpl_sig: TmplSig):
blob = LocalBlob()
@Subroutine(TealType.bytes)
def encode_uvarint(val: Expr, b: Expr):
buff = ScratchVar()
return Seq(
buff.store(b),
Concat(
buff.load(),
If(
val >= Int(128),
encode_uvarint(
val >> Int(7),
Extract(Itob((val & Int(255)) | Int(128)), Int(7), Int(1)),
),
Extract(Itob(val & Int(255)), Int(7), Int(1)),
),
),
)
@Subroutine(TealType.bytes)
def get_sig_address(acct_seq_start: Expr, emitter: Expr):
# We could iterate over N items and encode them for a more general interface
# but we inline them directly here
return Sha512_256(
Concat(
Bytes("Program"),
# ADDR_IDX aka sequence start
tmpl_sig.get_bytecode_chunk(0),
encode_uvarint(acct_seq_start, Bytes("")),
# EMMITTER_ID
tmpl_sig.get_bytecode_chunk(1),
encode_uvarint(Len(emitter), Bytes("")),
emitter,
# SEED_AMT
tmpl_sig.get_bytecode_chunk(2),
encode_uvarint(Int(seed_amt), Bytes("")),
# APP_ID
tmpl_sig.get_bytecode_chunk(3),
encode_uvarint(Global.current_application_id(), Bytes("")),
# TMPL_APP_ADDRESS
tmpl_sig.get_bytecode_chunk(4),
encode_uvarint(Len(Global.current_application_address()), Bytes("")),
Global.current_application_address(),
tmpl_sig.get_bytecode_chunk(5),
)
)
@Subroutine(TealType.uint64)
def optin():
# Alias for readability
algo_seed = Gtxn[0]
optin = Gtxn[1]
well_formed_optin = And(
# Check that we're paying it
algo_seed.type_enum() == TxnType.Payment,
algo_seed.amount() == Int(seed_amt),
# Check that its an opt in to us
optin.type_enum() == TxnType.ApplicationCall,
optin.on_completion() == OnComplete.OptIn,
# Not strictly necessary since we wouldn't be seeing this unless it was us, but...
optin.application_id() == Global.current_application_id(),
)
return Seq(
# Make sure its a valid optin
Assert(well_formed_optin),
# Init by writing to the full space available for the sender (Int(0))
blob.zero(Int(0)),
# we gucci
Int(1)
)
def nop():
return Seq([Approve()])
def publishMessage():
seq = ScratchVar()
return Seq([
# Lets see if we were handed the correct account to store the sequence number in
Assert(Txn.accounts[1] == get_sig_address(Int(0), Txn.sender())),
# emitter sequence number
seq.store(Itob(Btoi(blob.read(Int(1), Int(0), Int(8))) + Int(1))),
Pop(blob.write(Int(1), Int(0), seq.load())),
# Log it so that we can look for this on the guardian network
Log(seq.load()),
Approve()
# Reject()
])
def hdlGovernance():
off = ScratchVar()
a = ScratchVar()
emitter = ScratchVar()
idx = ScratchVar()
set = ScratchVar()
len = ScratchVar()
v = ScratchVar()
return Seq([
# All governance must be done with the most recent guardian set
set.store(App.globalGet(Bytes("currentGuardianSetIndex"))),
If(set.load() != Bytes(""), Seq([
idx.store(Extract(Txn.application_args[1], Int(1), Int(4))),
Assert(idx.load() == set.load()),
])),
# The offset of the chain
off.store(Btoi(Extract(Txn.application_args[1], Int(5), Int(1))) * Int(66) + Int(14)),
# Correct chain?
Assert(Extract(Txn.application_args[1], off.load(), Int(2)) == Bytes("base16", "0001")),
# Correct emitter?
Assert(Extract(Txn.application_args[1], off.load() + Int(2), Int(32)) == Bytes("base16", "0000000000000000000000000000000000000000000000000000000000000004")),
# Get us to the payload
off.store(off.load() + Int(43)),
# Is this a governance message?
Assert(Extract(Txn.application_args[1], off.load(), Int(32)) == Bytes("base16", "00000000000000000000000000000000000000000000000000000000436f7265")),
off.store(off.load() + Int(32)),
a.store(Btoi(Extract(Txn.application_args[1], off.load(), Int(1)))),
Cond(
[a.load() == Int(1), Seq([
# ContractUpgrade is a VAA that instructs an implementation on a specific chain to upgrade itself
#
# In the case of Algorand, it contains the hash of the program that we are allowed to upgrade ourselves to. We would then run the upgrade program itself
# to perform the actual upgrade
Assert(Extract(Txn.application_args[1], off.load() + Int(1), Int(2)) == Bytes("base16", "0008")),
off.store(off.load() + Int(3)),
App.globalPut(Bytes("validUpdateApproveHash"), Extract(Txn.application_args[1], off.load(), Int(32)))
])],
[a.load() == Int(2), Seq([
# We are updating the guardian set
# This should point at all chains
Assert(Extract(Txn.application_args[1], off.load() + Int(1), Int(2)) == Bytes("base16", "0000")),
# move off to point at the NewGuardianSetIndex and grab it
off.store(off.load() + Int(3)),
v.store(Extract(Txn.application_args[1], off.load(), Int(4))),
idx.store(Btoi(v.load())),
# Lets see if the user handed us the correct memory... no hacky hacky
Assert(Txn.accounts[3] == get_sig_address(idx.load(), Bytes("guardian"))),
# Write this away till the next time
App.globalPut(Bytes("currentGuardianSetIndex"), v.load()),
# Write everything out to the auxilliary storage
off.store(off.load() + Int(4)),
len.store(Btoi(Extract(Txn.application_args[1], off.load(), Int(1)))),
Pop(blob.write(Int(3), Int(0), Extract(Txn.application_args[1], off.load(), Int(1) + (Int(20) * len.load())))),
# Make this block expire.. as long as it is
# not being used to sign itself. We stick the
# expiration 1000 bytes into the account...
#
# 19200 is approx 24 hours assuming a 4.5 seconds per block (24 * 3600 / 4.5) = 19200
If(Txn.accounts[3] != Txn.accounts[2],
Pop(blob.write(Int(2), Int(1000), Itob(Txn.first_valid() + Int(19200)))))
])]
),
Approve()
])
def init():
return Seq([
# You better lose yourself in the music, the moment
App.globalPut(Bytes("vphash"), Txn.application_args[2]),
# You own it, you better never let it go
Assert(Txn.sender() == Global.creator_address()),
# You only get one shot, do not miss your chance to blow
Assert(App.globalGet(Bytes("booted")) == Int(0)),
App.globalPut(Bytes("booted"), Bytes("true")),
# This opportunity comes once in a lifetime
checkForDuplicate(),
# You can do anything you set your mind to...
hdlGovernance()
])
def verifySigs():
return Seq([
Approve(),
])
@Subroutine(TealType.none)
def checkForDuplicate():
off = ScratchVar()
emitter = ScratchVar()
sequence = ScratchVar()
b = ScratchVar()
byte_offset = ScratchVar()
return Seq(
# VM only is version 1
Assert(Btoi(Extract(Txn.application_args[1], Int(0), Int(1))) == Int(1)),
off.store(Btoi(Extract(Txn.application_args[1], Int(5), Int(1))) * Int(66) + Int(14)), # The offset of the emitter
# emitter is chain/contract-address
emitter.store(Extract(Txn.application_args[1], off.load(), Int(34))),
sequence.store(Btoi(Extract(Txn.application_args[1], off.load() + Int(34), Int(8)))),
# They passed us the correct account? In this case, byte_offset points at the whole block
byte_offset.store(sequence.load() / Int(max_bits)),
Assert(Txn.accounts[1] == get_sig_address(byte_offset.load(), emitter.load())),
# Now, lets go grab the raw byte
byte_offset.store((sequence.load() / Int(8)) % Int(max_bytes)),
b.store(blob.get_byte(Int(1), byte_offset.load())),
# I would hope we've never seen this packet before... throw an exception if we have
Assert(GetBit(b.load(), sequence.load() % Int(8)) == Int(0)),
# Lets mark this bit so that we never see it again
blob.set_byte(Int(1), byte_offset.load(), SetBit(b.load(), sequence.load() % Int(8), Int(1)))
)
STATELESS_LOGIC_HASH = App.globalGet(Bytes("vphash"))
def verifyVAA():
i = ScratchVar()
a = ScratchVar()
total_guardians = ScratchVar()
guardian_keys = ScratchVar()
num_sigs = ScratchVar()
off = ScratchVar()
digest = ScratchVar()
hits = ScratchVar()
s = ScratchVar()
eoff = ScratchVar()
guardian = ScratchVar()
return Seq([
# We have a guardian set? We have OUR guardian set?
Assert(Txn.accounts[2] == get_sig_address(Btoi(Extract(Txn.application_args[1], Int(1), Int(4))), Bytes("guardian"))),
# Lets grab the total keyset
total_guardians.store(blob.get_byte(Int(2), Int(0))),
guardian_keys.store(blob.read(Int(2), Int(1), Int(1) + Int(20) * total_guardians.load())),
# I wonder if this is an expired guardian set
s.store(Btoi(blob.read(Int(2), Int(1000), Int(1008)))),
If(s.load() != Int(0),
Assert(Txn.first_valid() < s.load())),
hits.store(Bytes("base16", "0x00000000")),
# How many signatures are in this vaa?
num_sigs.store(Btoi(Extract(Txn.application_args[1], Int(5), Int(1)))),
# Lets create a digest of THIS vaa...
off.store(Int(6) + (num_sigs.load() * Int(66))),
digest.store(Keccak256(Keccak256(Extract(Txn.application_args[1], off.load(), Len(Txn.application_args[1]) - off.load())))),
# We have enough signatures?
Assert(And(
total_guardians.load() > Int(0),
num_sigs.load() <= total_guardians.load(),
num_sigs.load() > ((total_guardians.load() * Int(2)) / Int(3)),
)),
# There should always be 1 payment txid at the start for at least 3000 to the vphash...
Assert(And(
Gtxn[0].type_enum() == TxnType.Payment,
Gtxn[0].amount() >= Int(3000),
Gtxn[0].receiver() == STATELESS_LOGIC_HASH
)),
# Point it at the start of the signatures in the VAA
off.store(Int(6)),
For(
i.store(Int(1)),
i.load() <= Txn.group_index(),
i.store(i.load() + Int(1))).Do(Seq([
Assert(And(
Gtxn[i.load()].type_enum() == TxnType.ApplicationCall,
Gtxn[i.load()].rekey_to() == Global.zero_address(),
Gtxn[i.load()].application_id() == Txn.application_id(),
Gtxn[i.load()].accounts[1] == Txn.accounts[1],
Gtxn[i.load()].accounts[2] == Txn.accounts[2],
)),
a.store(Gtxn[i.load()].application_args[0]),
Cond(
[a.load() == Bytes("verifySigs"), Seq([
# Lets see if they are actually verifying the correct signatures!
s.store(Gtxn[i.load()].application_args[1]), # find a different way to get length
Assert(Extract(Txn.application_args[1], off.load(), Len(s.load())) == s.load()),
eoff.store(off.load() + Len(s.load())),
s.store(Bytes("")),
While(off.load() < eoff.load()).Do(Seq( [
# Lets see if we ever reuse the same signature more then once (same guardian over and over)
guardian.store(Btoi(Extract(Txn.application_args[1], off.load(), Int(1)))),
Assert(GetBit(hits.load(), guardian.load()) == Int(0)),
hits.store(SetBit(hits.load(), guardian.load(), Int(1))),
s.store(Concat(s.load(), Extract(guardian_keys.load(), guardian.load() * Int(20), Int(20)))),
off.store(off.load() + Int(66))
])),
Assert(And(
Gtxn[i.load()].application_args[2] == s.load(), # Does the keyset passed into the verify routines match what it should be?
Gtxn[i.load()].sender() == STATELESS_LOGIC_HASH, # Was it signed with our code?
Gtxn[i.load()].application_args[3] == digest.load() # Was it verifying the same vaa?
)),
])],
[a.load() == Bytes("nop"), Seq([])], # if there is a function call not listed here, it will throw an error
[a.load() == Bytes("verifyVAA"), Seq([])],
)
])
),
# Did we verify all the signatures?
Assert(off.load() == Int(6) + (num_sigs.load() * Int(66))),
Approve(),
])
def governance():
return Seq([
checkForDuplicate(), # Verify this is not a duplicate message and then make sure we never see it again
Assert(And(
Gtxn[Txn.group_index() - Int(1)].type_enum() == TxnType.ApplicationCall,
Gtxn[Txn.group_index() - Int(1)].application_id() == Txn.application_id(),
Gtxn[Txn.group_index() - Int(1)].application_args[0] == Bytes("verifyVAA"),
Gtxn[Txn.group_index() - Int(1)].sender() == Txn.sender(),
Gtxn[Txn.group_index() - Int(1)].rekey_to() == Global.zero_address(),
# Lets see if the vaa we are about to process was actually verified by the core
Gtxn[Txn.group_index() - Int(1)].application_args[1] == Txn.application_args[1],
# What checks should I give myself
Gtxn[Txn.group_index()].rekey_to() == Global.zero_address(),
Gtxn[Txn.group_index()].sender() == Txn.sender(),
# We all opted into the same accounts?
Gtxn[Txn.group_index() - Int(1)].accounts[0] == Txn.accounts[0],
Gtxn[Txn.group_index() - Int(1)].accounts[1] == Txn.accounts[1],
Gtxn[Txn.group_index() - Int(1)].accounts[2] == Txn.accounts[2],
(Global.group_size() - Int(1)) == Txn.group_index() # governance should be the last entry...
)),
hdlGovernance(),
Approve(),
])
METHOD = Txn.application_args[0]
on_delete = Seq([Reject()])
router = Cond(
[METHOD == Bytes("publishMessage"), publishMessage()],
[METHOD == Bytes("nop"), nop()],
[METHOD == Bytes("init"), init()],
[METHOD == Bytes("verifySigs"), verifySigs()],
[METHOD == Bytes("verifyVAA"), verifyVAA()],
[METHOD == Bytes("governance"), governance()],
)
on_create = Seq( [
App.globalPut(Bytes("vphash"), Bytes("")),
App.globalPut(Bytes("currentGuardianSetIndex"), Bytes("")),
App.globalPut(Bytes("validUpdateApproveHash"), Bytes("")),
App.globalPut(Bytes("validUpdateClearHash"), Bytes("BJATCHES5YJZJ7JITYMVLSSIQAVAWBQRVGPQUDT5AZ2QSLDSXWWM46THOY")), # empty clear state program
Return(Int(1))
])
on_update = Seq( [
Assert(Sha512_256(Concat(Bytes("Program"), Txn.approval_program())) == App.globalGet(Bytes("validUpdateApproveHash"))),
Assert(Sha512_256(Concat(Bytes("Program"), Txn.clear_state_program())) == App.globalGet(Bytes("validUpdateClearHash"))),
Return(Int(1))
] )
on_optin = Seq( [
Return(optin())
])
return Cond(
[Txn.application_id() == Int(0), on_create],
[Txn.on_completion() == OnComplete.UpdateApplication, on_update],
[Txn.on_completion() == OnComplete.DeleteApplication, on_delete],
[Txn.on_completion() == OnComplete.OptIn, on_optin],
[Txn.on_completion() == OnComplete.NoOp, router]
)
def clear_state_program():
return Int(1)
APPROVAL_PROGRAM = fullyCompileContract(client, vaa_processor_program(seed_amt, tmpl_sig))
CLEAR_STATE_PROGRAM = fullyCompileContract(client, clear_state_program())
return APPROVAL_PROGRAM, CLEAR_STATE_PROGRAM