admin.py 65 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574
  1. # python3 -m pip install pycryptodomex uvarint pyteal web3 coincurve
  2. import os
  3. from os.path import exists
  4. from time import time, sleep
  5. from eth_abi import encode_single, encode_abi
  6. from typing import List, Tuple, Dict, Any, Optional, Union
  7. from base64 import b64decode
  8. import base64
  9. import random
  10. import time
  11. import hashlib
  12. import uuid
  13. import sys
  14. import json
  15. import uvarint
  16. from local_blob import LocalBlob
  17. from wormhole_core import getCoreContracts
  18. from TmplSig import TmplSig
  19. import argparse
  20. from gentest import GenTest
  21. from algosdk.v2client.algod import AlgodClient
  22. from algosdk.kmd import KMDClient
  23. from algosdk import account, mnemonic, abi
  24. from algosdk.encoding import decode_address, encode_address
  25. from algosdk.future import transaction
  26. from pyteal import compileTeal, Mode, Expr
  27. from pyteal import *
  28. from algosdk.logic import get_application_address
  29. from vaa_verify import get_vaa_verify
  30. from Cryptodome.Hash import keccak
  31. from algosdk.future.transaction import LogicSig
  32. from token_bridge import get_token_bridge
  33. from test_contract import get_test_app
  34. from algosdk.v2client import indexer
  35. import pprint
  36. max_keys = 15
  37. max_bytes_per_key = 127
  38. bits_per_byte = 8
  39. bits_per_key = max_bytes_per_key * bits_per_byte
  40. max_bytes = max_bytes_per_key * max_keys
  41. max_bits = bits_per_byte * max_bytes
  42. class Account:
  43. """Represents a private key and address for an Algorand account"""
  44. def __init__(self, privateKey: str) -> None:
  45. self.sk = privateKey
  46. self.addr = account.address_from_private_key(privateKey)
  47. print (privateKey)
  48. print (" " + self.getMnemonic())
  49. print (" " + self.addr)
  50. def getAddress(self) -> str:
  51. return self.addr
  52. def getPrivateKey(self) -> str:
  53. return self.sk
  54. def getMnemonic(self) -> str:
  55. return mnemonic.from_private_key(self.sk)
  56. @classmethod
  57. def FromMnemonic(cls, m: str) -> "Account":
  58. return cls(mnemonic.to_private_key(m))
  59. class PendingTxnResponse:
  60. def __init__(self, response: Dict[str, Any]) -> None:
  61. self.poolError: str = response["pool-error"]
  62. self.txn: Dict[str, Any] = response["txn"]
  63. self.applicationIndex: Optional[int] = response.get("application-index")
  64. self.assetIndex: Optional[int] = response.get("asset-index")
  65. self.closeRewards: Optional[int] = response.get("close-rewards")
  66. self.closingAmount: Optional[int] = response.get("closing-amount")
  67. self.confirmedRound: Optional[int] = response.get("confirmed-round")
  68. self.globalStateDelta: Optional[Any] = response.get("global-state-delta")
  69. self.localStateDelta: Optional[Any] = response.get("local-state-delta")
  70. self.receiverRewards: Optional[int] = response.get("receiver-rewards")
  71. self.senderRewards: Optional[int] = response.get("sender-rewards")
  72. self.innerTxns: List[Any] = response.get("inner-txns", [])
  73. self.logs: List[bytes] = [b64decode(l) for l in response.get("logs", [])]
  74. class PortalCore:
  75. def __init__(self) -> None:
  76. self.gt = None
  77. self.foundation = None
  78. self.devnet = False
  79. self.ALGOD_ADDRESS = "http://localhost:4001"
  80. self.ALGOD_TOKEN = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
  81. self.FUNDING_AMOUNT = 100_000_000_000
  82. self.KMD_ADDRESS = "http://localhost:4002"
  83. self.KMD_TOKEN = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
  84. self.KMD_WALLET_NAME = "unencrypted-default-wallet"
  85. self.KMD_WALLET_PASSWORD = ""
  86. self.INDEXER_TOKEN = "a" * 64
  87. self.INDEXER_ADDRESS = 'http://localhost:8980'
  88. self.INDEXER_ROUND = 0
  89. self.NOTE_PREFIX = 'publishMessage'.encode()
  90. self.myindexer = None
  91. self.seed_amt = int(1002000) # The black magic in this number...
  92. self.cache = {}
  93. self.asset_cache = {}
  94. self.kmdAccounts : Optional[List[Account]] = None
  95. self.accountList : List[Account] = []
  96. self.zeroPadBytes = "00"*32
  97. self.tsig = TmplSig("sig")
  98. def init(self, args) -> None:
  99. self.args = args
  100. self.ALGOD_ADDRESS = args.algod_address
  101. self.ALGOD_TOKEN = args.algod_token
  102. self.KMD_ADDRESS = args.kmd_address
  103. self.KMD_TOKEN = args.kmd_token
  104. self.KMD_WALLET_NAME = args.kmd_name
  105. self.KMD_WALLET_PASSWORD = args.kmd_password
  106. self.TARGET_ACCOUNT = args.mnemonic
  107. self.coreid = args.coreid
  108. self.tokenid = args.tokenid
  109. if exists(self.args.env):
  110. if self.gt == None:
  111. self.gt = GenTest(False)
  112. with open(self.args.env, encoding = 'utf-8') as f:
  113. for line in f:
  114. e = line.rstrip('\n').split("=")
  115. if "INIT_SIGNERS_CSV" in e[0]:
  116. self.gt.guardianKeys = e[1].split(",")
  117. print("guardianKeys=" + str(self.gt.guardianKeys))
  118. if "INIT_SIGNERS_KEYS_CSV" in e[0]:
  119. self.gt.guardianPrivKeys = e[1].split(",")
  120. print("guardianPrivKeys=" + str(self.gt.guardianPrivKeys))
  121. def waitForTransaction(
  122. self, client: AlgodClient, txID: str, timeout: int = 10
  123. ) -> PendingTxnResponse:
  124. lastStatus = client.status()
  125. lastRound = lastStatus["last-round"]
  126. startRound = lastRound
  127. while lastRound < startRound + timeout:
  128. pending_txn = client.pending_transaction_info(txID)
  129. if pending_txn.get("confirmed-round", 0) > 0:
  130. return PendingTxnResponse(pending_txn)
  131. if pending_txn["pool-error"]:
  132. raise Exception("Pool error: {}".format(pending_txn["pool-error"]))
  133. lastStatus = client.status_after_block(lastRound + 1)
  134. lastRound += 1
  135. raise Exception(
  136. "Transaction {} not confirmed after {} rounds".format(txID, timeout)
  137. )
  138. def getKmdClient(self) -> KMDClient:
  139. return KMDClient(self.KMD_TOKEN, self.KMD_ADDRESS)
  140. def getGenesisAccounts(self) -> List[Account]:
  141. if self.kmdAccounts is None:
  142. kmd = self.getKmdClient()
  143. wallets = kmd.list_wallets()
  144. walletID = None
  145. for wallet in wallets:
  146. if wallet["name"] == self.KMD_WALLET_NAME:
  147. walletID = wallet["id"]
  148. break
  149. if walletID is None:
  150. raise Exception("Wallet not found: {}".format(self.KMD_WALLET_NAME))
  151. walletHandle = kmd.init_wallet_handle(walletID, self.KMD_WALLET_PASSWORD)
  152. try:
  153. addresses = kmd.list_keys(walletHandle)
  154. privateKeys = [
  155. kmd.export_key(walletHandle, self.KMD_WALLET_PASSWORD, addr)
  156. for addr in addresses
  157. ]
  158. self.kmdAccounts = [Account(sk) for sk in privateKeys]
  159. finally:
  160. kmd.release_wallet_handle(walletHandle)
  161. return self.kmdAccounts
  162. def getTemporaryAccount(self, client: AlgodClient) -> Account:
  163. if len(self.accountList) == 0:
  164. sks = [account.generate_account()[0] for i in range(3)]
  165. self.accountList = [Account(sk) for sk in sks]
  166. genesisAccounts = self.getGenesisAccounts()
  167. suggestedParams = client.suggested_params()
  168. txns: List[transaction.Transaction] = []
  169. for i, a in enumerate(self.accountList):
  170. fundingAccount = genesisAccounts[i % len(genesisAccounts)]
  171. txns.append(
  172. transaction.PaymentTxn(
  173. sender=fundingAccount.getAddress(),
  174. receiver=a.getAddress(),
  175. amt=self.FUNDING_AMOUNT,
  176. sp=suggestedParams,
  177. )
  178. )
  179. txns = transaction.assign_group_id(txns)
  180. signedTxns = [
  181. txn.sign(genesisAccounts[i % len(genesisAccounts)].getPrivateKey())
  182. for i, txn in enumerate(txns)
  183. ]
  184. client.send_transactions(signedTxns)
  185. self.waitForTransaction(client, signedTxns[0].get_txid())
  186. return self.accountList.pop()
  187. def getAlgodClient(self) -> AlgodClient:
  188. return AlgodClient(self.ALGOD_TOKEN, self.ALGOD_ADDRESS)
  189. def getBalances(self, client: AlgodClient, account: str) -> Dict[int, int]:
  190. balances: Dict[int, int] = dict()
  191. accountInfo = client.account_info(account)
  192. # set key 0 to Algo balance
  193. balances[0] = accountInfo["amount"]
  194. assets: List[Dict[str, Any]] = accountInfo.get("assets", [])
  195. for assetHolding in assets:
  196. assetID = assetHolding["asset-id"]
  197. amount = assetHolding["amount"]
  198. balances[assetID] = amount
  199. return balances
  200. def fullyCompileContract(self, client: AlgodClient, contract: Expr) -> bytes:
  201. teal = compileTeal(contract, mode=Mode.Application, version=6)
  202. response = client.compile(teal)
  203. return response
  204. # helper function that formats global state for printing
  205. def format_state(self, state):
  206. formatted = {}
  207. for item in state:
  208. key = item['key']
  209. value = item['value']
  210. formatted_key = base64.b64decode(key).decode('utf-8')
  211. if value['type'] == 1:
  212. # byte string
  213. if formatted_key == 'voted':
  214. formatted_value = base64.b64decode(value['bytes']).decode('utf-8')
  215. else:
  216. formatted_value = value['bytes']
  217. formatted[formatted_key] = formatted_value
  218. else:
  219. # integer
  220. formatted[formatted_key] = value['uint']
  221. return formatted
  222. # helper function to read app global state
  223. def read_global_state(self, client, addr, app_id):
  224. results = self.client.application_info(app_id)
  225. return self.format_state(results['params']['global-state'])
  226. def read_state(self, client, addr, app_id):
  227. results = client.account_info(addr)
  228. apps_created = results['created-apps']
  229. for app in apps_created:
  230. if app['id'] == app_id:
  231. return app
  232. return {}
  233. def encoder(self, type, val):
  234. if type == 'uint8':
  235. return encode_single(type, val).hex()[62:64]
  236. if type == 'uint16':
  237. return encode_single(type, val).hex()[60:64]
  238. if type == 'uint32':
  239. return encode_single(type, val).hex()[56:64]
  240. if type == 'uint64':
  241. return encode_single(type, val).hex()[64-(16):64]
  242. if type == 'uint128':
  243. return encode_single(type, val).hex()[64-(32):64]
  244. if type == 'uint256' or type == 'bytes32':
  245. return encode_single(type, val).hex()[64-(64):64]
  246. raise Exception("invalid type")
  247. def devnetUpgradeVAA(self):
  248. v = self.genUpgradePayload()
  249. print("core payload: " + str(v[0]))
  250. print("token payload: " + str(v[1]))
  251. if self.gt == None:
  252. self.gt = GenTest(False)
  253. emitter = bytes.fromhex(self.zeroPadBytes[0:(31*2)] + "04")
  254. guardianSet = self.getGovSet()
  255. print("guardianSet: " + str(guardianSet))
  256. nonce = int(random.random() * 20000)
  257. ret = [
  258. self.gt.createSignedVAA(guardianSet, self.gt.guardianPrivKeys, int(time.time()), nonce, 1, emitter, int(random.random() * 20000), 32, 8, v[0]),
  259. self.gt.createSignedVAA(guardianSet, self.gt.guardianPrivKeys, int(time.time()), nonce, 1, emitter, int(random.random() * 20000), 32, 8, v[1]),
  260. ]
  261. # pprint.pprint(self.parseVAA(bytes.fromhex(ret[0])))
  262. # pprint.pprint(self.parseVAA(bytes.fromhex(ret[1])))
  263. return ret
  264. def getMessageFee(self):
  265. s = self.client.application_info(self.coreid)["params"]["global-state"]
  266. k = base64.b64encode(b"MessageFee").decode('utf-8')
  267. for x in s:
  268. if x["key"] == k:
  269. return x["value"]["uint"]
  270. return -1
  271. def getGovSet(self):
  272. s = self.client.application_info(self.coreid)["params"]["global-state"]
  273. k = base64.b64encode(b"currentGuardianSetIndex").decode('utf-8')
  274. for x in s:
  275. if x["key"] == k:
  276. return x["value"]["uint"]
  277. return -1
  278. def genUpgradePayload(self):
  279. approval1, clear1 = getCoreContracts(False, self.args.core_approve, self.args.core_clear, self.client, seed_amt=self.seed_amt, tmpl_sig=self.tsig, devMode = self.devnet or self.args.testnet)
  280. approval2, clear2 = get_token_bridge(False, self.args.token_approve, self.args.token_clear, self.client, seed_amt=self.seed_amt, tmpl_sig=self.tsig, devMode = self.devnet or self.args.testnet)
  281. return self.genUpgradePayloadBody(approval1, approval2)
  282. def genUpgradePayloadBody(self, approval1, approval2):
  283. b = self.zeroPadBytes[0:(28*2)]
  284. b += self.encoder("uint8", ord("C"))
  285. b += self.encoder("uint8", ord("o"))
  286. b += self.encoder("uint8", ord("r"))
  287. b += self.encoder("uint8", ord("e"))
  288. b += self.encoder("uint8", 1)
  289. b += self.encoder("uint16", 8)
  290. b += decode_address(approval1["hash"]).hex()
  291. print("core hash: " + decode_address(approval1["hash"]).hex())
  292. ret = [b]
  293. b = self.zeroPadBytes[0:((32 -11)*2)]
  294. b += self.encoder("uint8", ord("T"))
  295. b += self.encoder("uint8", ord("o"))
  296. b += self.encoder("uint8", ord("k"))
  297. b += self.encoder("uint8", ord("e"))
  298. b += self.encoder("uint8", ord("n"))
  299. b += self.encoder("uint8", ord("B"))
  300. b += self.encoder("uint8", ord("r"))
  301. b += self.encoder("uint8", ord("i"))
  302. b += self.encoder("uint8", ord("d"))
  303. b += self.encoder("uint8", ord("g"))
  304. b += self.encoder("uint8", ord("e"))
  305. b += self.encoder("uint8", 2) # action
  306. b += self.encoder("uint16", 8) # target chain
  307. b += decode_address(approval2["hash"]).hex()
  308. print("token hash: " + decode_address(approval2["hash"]).hex())
  309. ret.append(b)
  310. return ret
  311. def createPortalCoreApp(
  312. self,
  313. client: AlgodClient,
  314. sender: Account,
  315. ) -> int:
  316. approval, clear = getCoreContracts(False, self.args.core_approve, self.args.core_clear, client, seed_amt=self.seed_amt, tmpl_sig=self.tsig, devMode = self.devnet or self.args.testnet)
  317. globalSchema = transaction.StateSchema(num_uints=8, num_byte_slices=40)
  318. localSchema = transaction.StateSchema(num_uints=0, num_byte_slices=16)
  319. app_args = [ ]
  320. txn = transaction.ApplicationCreateTxn(
  321. sender=sender.getAddress(),
  322. on_complete=transaction.OnComplete.NoOpOC,
  323. approval_program=b64decode(approval["result"]),
  324. clear_program=b64decode(clear["result"]),
  325. global_schema=globalSchema,
  326. local_schema=localSchema,
  327. extra_pages = 1,
  328. app_args=app_args,
  329. sp=client.suggested_params(),
  330. )
  331. signedTxn = txn.sign(sender.getPrivateKey())
  332. client.send_transaction(signedTxn)
  333. response = self.waitForTransaction(client, signedTxn.get_txid())
  334. assert response.applicationIndex is not None and response.applicationIndex > 0
  335. # Lets give it a bit of money so that it is not a "ghost" account
  336. txn = transaction.PaymentTxn(sender = sender.getAddress(), sp = client.suggested_params(), receiver = get_application_address(response.applicationIndex), amt = 100000)
  337. signedTxn = txn.sign(sender.getPrivateKey())
  338. client.send_transaction(signedTxn)
  339. return response.applicationIndex
  340. def createTokenBridgeApp(
  341. self,
  342. client: AlgodClient,
  343. sender: Account,
  344. ) -> int:
  345. approval, clear = get_token_bridge(False, self.args.token_approve, self.args.token_clear, client, seed_amt=self.seed_amt, tmpl_sig=self.tsig, devMode = self.devnet or self.args.testnet)
  346. if len(b64decode(approval["result"])) > 4060:
  347. print("token bridge contract is too large... This might prevent updates later")
  348. globalSchema = transaction.StateSchema(num_uints=4, num_byte_slices=30)
  349. localSchema = transaction.StateSchema(num_uints=0, num_byte_slices=16)
  350. app_args = [self.coreid, decode_address(get_application_address(self.coreid))]
  351. txn = transaction.ApplicationCreateTxn(
  352. sender=sender.getAddress(),
  353. on_complete=transaction.OnComplete.NoOpOC,
  354. approval_program=b64decode(approval["result"]),
  355. clear_program=b64decode(clear["result"]),
  356. global_schema=globalSchema,
  357. local_schema=localSchema,
  358. app_args=app_args,
  359. extra_pages = 2,
  360. sp=client.suggested_params(),
  361. )
  362. signedTxn = txn.sign(sender.getPrivateKey())
  363. client.send_transaction(signedTxn)
  364. response = self.waitForTransaction(client, signedTxn.get_txid())
  365. #pprint.pprint(response.__dict__)
  366. assert response.applicationIndex is not None and response.applicationIndex > 0
  367. # Lets give it a bit of money so that it is not a "ghost" account
  368. txn = transaction.PaymentTxn(sender = sender.getAddress(), sp = client.suggested_params(), receiver = get_application_address(response.applicationIndex), amt = 100000)
  369. signedTxn = txn.sign(sender.getPrivateKey())
  370. client.send_transaction(signedTxn)
  371. return response.applicationIndex
  372. def createTestApp(
  373. self,
  374. client: AlgodClient,
  375. sender: Account,
  376. ) -> int:
  377. approval, clear = get_test_app(client)
  378. globalSchema = transaction.StateSchema(num_uints=4, num_byte_slices=30)
  379. localSchema = transaction.StateSchema(num_uints=0, num_byte_slices=16)
  380. txn = transaction.ApplicationCreateTxn(
  381. sender=sender.getAddress(),
  382. on_complete=transaction.OnComplete.NoOpOC,
  383. approval_program=b64decode(approval["result"]),
  384. clear_program=b64decode(clear["result"]),
  385. global_schema=globalSchema,
  386. local_schema=localSchema,
  387. sp=client.suggested_params(),
  388. )
  389. signedTxn = txn.sign(sender.getPrivateKey())
  390. client.send_transaction(signedTxn)
  391. response = self.waitForTransaction(client, signedTxn.get_txid())
  392. assert response.applicationIndex is not None and response.applicationIndex > 0
  393. # Lets give it a bit of money so that it is not a "ghost" account
  394. txn = transaction.PaymentTxn(sender = sender.getAddress(), sp = client.suggested_params(), receiver = get_application_address(response.applicationIndex), amt = 100000)
  395. signedTxn = txn.sign(sender.getPrivateKey())
  396. client.send_transaction(signedTxn)
  397. return response.applicationIndex
  398. def account_exists(self, client, app_id, addr):
  399. try:
  400. ai = client.account_info(addr)
  401. if "apps-local-state" not in ai:
  402. return False
  403. for app in ai["apps-local-state"]:
  404. if app["id"] == app_id:
  405. return True
  406. except:
  407. print("Failed to find account {}".format(addr))
  408. return False
  409. def optin(self, client, sender, app_id, idx, emitter, doCreate=True):
  410. aa = decode_address(get_application_address(app_id)).hex()
  411. lsa = self.tsig.populate(
  412. {
  413. "TMPL_APP_ID": app_id,
  414. "TMPL_APP_ADDRESS": aa,
  415. "TMPL_ADDR_IDX": idx,
  416. "TMPL_EMITTER_ID": emitter,
  417. }
  418. )
  419. sig_addr = lsa.address()
  420. if sig_addr not in self.cache and not self.account_exists(client, app_id, sig_addr):
  421. if doCreate:
  422. # pprint.pprint(("Creating", app_id, idx, emitter, sig_addr))
  423. # Create it
  424. sp = client.suggested_params()
  425. seed_txn = transaction.PaymentTxn(sender = sender.getAddress(),
  426. sp = sp,
  427. receiver = sig_addr,
  428. amt = self.seed_amt)
  429. seed_txn.fee = seed_txn.fee * 2
  430. optin_txn = transaction.ApplicationOptInTxn(sig_addr, sp, app_id, rekey_to=get_application_address(app_id))
  431. optin_txn.fee = 0
  432. transaction.assign_group_id([seed_txn, optin_txn])
  433. signed_seed = seed_txn.sign(sender.getPrivateKey())
  434. signed_optin = transaction.LogicSigTransaction(optin_txn, lsa)
  435. client.send_transactions([signed_seed, signed_optin])
  436. self.waitForTransaction(client, signed_optin.get_txid())
  437. self.cache[sig_addr] = True
  438. return sig_addr
  439. def parseSeqFromLog(self, txn):
  440. return int.from_bytes(b64decode(txn.innerTxns[0]["logs"][0]), "big")
  441. def getCreator(self, client, sender, asset_id):
  442. return client.asset_info(asset_id)["params"]["creator"]
  443. def sendTxn(self, client, sender, txns, doWait):
  444. transaction.assign_group_id(txns)
  445. grp = []
  446. pk = sender.getPrivateKey()
  447. for t in txns:
  448. grp.append(t.sign(pk))
  449. client.send_transactions(grp)
  450. if doWait:
  451. return self.waitForTransaction(client, grp[-1].get_txid())
  452. else:
  453. return grp[-1].get_txid()
  454. def bootGuardians(self, vaa, client, sender, coreid):
  455. p = self.parseVAA(vaa)
  456. if "NewGuardianSetIndex" not in p:
  457. raise Exception("invalid guardian VAA")
  458. seq_addr = self.optin(client, sender, coreid, int(p["sequence"] / max_bits), p["chainRaw"].hex() + p["emitter"].hex())
  459. guardian_addr = self.optin(client, sender, coreid, p["index"], b"guardian".hex())
  460. newguardian_addr = self.optin(client, sender, coreid, p["NewGuardianSetIndex"], b"guardian".hex())
  461. # wormhole is not a cheap protocol... we need to buy ourselves
  462. # some extra CPU cycles by having an early txn do nothing.
  463. # This leaves cycles over for later txn's in the same group
  464. sp = client.suggested_params()
  465. txns = [
  466. transaction.ApplicationCallTxn(
  467. sender=sender.getAddress(),
  468. index=coreid,
  469. on_complete=transaction.OnComplete.NoOpOC,
  470. app_args=[b"nop", b"0"],
  471. sp=sp
  472. ),
  473. transaction.ApplicationCallTxn(
  474. sender=sender.getAddress(),
  475. index=coreid,
  476. on_complete=transaction.OnComplete.NoOpOC,
  477. app_args=[b"nop", b"1"],
  478. sp=sp
  479. ),
  480. transaction.ApplicationCallTxn(
  481. sender=sender.getAddress(),
  482. index=coreid,
  483. on_complete=transaction.OnComplete.NoOpOC,
  484. app_args=[b"init", vaa, decode_address(self.vaa_verify["hash"])],
  485. accounts=[seq_addr, guardian_addr, newguardian_addr],
  486. sp=sp
  487. ),
  488. transaction.PaymentTxn(
  489. sender=sender.getAddress(),
  490. receiver=self.vaa_verify["hash"],
  491. amt=100000,
  492. sp=sp
  493. )
  494. ]
  495. return self.sendTxn(client, sender, txns, True)
  496. def decodeLocalState(self, client, sender, appid, addr):
  497. app_state = None
  498. ai = client.account_info(addr)
  499. for app in ai["apps-local-state"]:
  500. if app["id"] == appid:
  501. app_state = app["key-value"]
  502. ret = b''
  503. if None != app_state:
  504. vals = {}
  505. e = bytes.fromhex("00"*127)
  506. for kv in app_state:
  507. k = base64.b64decode(kv["key"])
  508. if k == "meta":
  509. continue
  510. key = int.from_bytes(k, "big")
  511. v = base64.b64decode(kv["value"]["bytes"])
  512. if v != e:
  513. vals[key] = v
  514. for k in sorted(vals.keys()):
  515. ret = ret + vals[k]
  516. return ret
  517. # There is no client side duplicate suppression, error checking, or validity
  518. # checking. We need to be able to detect all failure cases in
  519. # the contract itself and we want to use this to drive the failure test
  520. # cases
  521. def simpleVAA(self, vaa, client, sender, appid):
  522. p = {"version": int.from_bytes(vaa[0:1], "big"), "index": int.from_bytes(vaa[1:5], "big"), "siglen": int.from_bytes(vaa[5:6], "big")}
  523. ret["signatures"] = vaa[6:(ret["siglen"] * 66) + 6]
  524. ret["sigs"] = []
  525. for i in range(ret["siglen"]):
  526. ret["sigs"].append(vaa[(6 + (i * 66)):(6 + (i * 66)) + 66].hex())
  527. off = (ret["siglen"] * 66) + 6
  528. ret["digest"] = vaa[off:] # This is what is actually signed...
  529. ret["timestamp"] = int.from_bytes(vaa[off:(off + 4)], "big")
  530. off += 4
  531. ret["nonce"] = int.from_bytes(vaa[off:(off + 4)], "big")
  532. off += 4
  533. ret["chainRaw"] = vaa[off:(off + 2)]
  534. ret["chain"] = int.from_bytes(vaa[off:(off + 2)], "big")
  535. off += 2
  536. ret["emitter"] = vaa[off:(off + 32)]
  537. off += 32
  538. ret["sequence"] = int.from_bytes(vaa[off:(off + 8)], "big")
  539. off += 8
  540. ret["consistency"] = int.from_bytes(vaa[off:(off + 1)], "big")
  541. off += 1
  542. seq_addr = self.optin(client, sender, appid, int(p["sequence"] / max_bits), p["chainRaw"].hex() + p["emitter"].hex())
  543. # And then the signatures to help us verify the vaa_s
  544. guardian_addr = self.optin(client, sender, self.coreid, p["index"], b"guardian".hex())
  545. accts = [seq_addr, guardian_addr]
  546. keys = self.decodeLocalState(client, sender, self.coreid, guardian_addr)
  547. sp = client.suggested_params()
  548. txns = []
  549. # Right now there is not really a good way to estimate the fees,
  550. # in production, on a conjested network, how much verifying
  551. # the signatures is going to cost.
  552. # So, what we do instead
  553. # is we top off the verifier back up to 2A so effectively we
  554. # are paying for the previous persons overrage which on a
  555. # unconjested network should be zero
  556. pmt = 3000
  557. bal = self.getBalances(client, self.vaa_verify["hash"])
  558. if ((200000 - bal[0]) >= pmt):
  559. pmt = 200000 - bal[0]
  560. #print("Sending %d algo to cover fees" % (pmt))
  561. txns.append(
  562. transaction.PaymentTxn(
  563. sender = sender.getAddress(),
  564. sp = sp,
  565. receiver = self.vaa_verify["hash"],
  566. amt = pmt
  567. )
  568. )
  569. # How many signatures can we process in a single txn... we can do 9!
  570. bsize = (9*66)
  571. blocks = int(len(p["signatures"]) / bsize) + 1
  572. # We don't pass the entire payload in but instead just pass it pre digested. This gets around size
  573. # limitations with lsigs AND reduces the cost of the entire operation on a conjested network by reducing the
  574. # bytes passed into the transaction
  575. digest = keccak.new(digest_bits=256).update(keccak.new(digest_bits=256).update(p["digest"]).digest()).digest()
  576. for i in range(blocks):
  577. # Which signatures will we be verifying in this block
  578. sigs = p["signatures"][(i * bsize):]
  579. if (len(sigs) > bsize):
  580. sigs = sigs[:bsize]
  581. # keys
  582. kset = b''
  583. # Grab the key associated the signature
  584. for q in range(int(len(sigs) / 66)):
  585. # Which guardian is this signature associated with
  586. g = sigs[q * 66]
  587. key = keys[((g * 20) + 1) : (((g + 1) * 20) + 1)]
  588. kset = kset + key
  589. txns.append(transaction.ApplicationCallTxn(
  590. sender=self.vaa_verify["hash"],
  591. index=self.coreid,
  592. on_complete=transaction.OnComplete.NoOpOC,
  593. app_args=[b"verifySigs", sigs, kset, digest],
  594. accounts=accts,
  595. sp=sp
  596. ))
  597. txns.append(transaction.ApplicationCallTxn(
  598. sender=sender.getAddress(),
  599. index=self.coreid,
  600. on_complete=transaction.OnComplete.NoOpOC,
  601. app_args=[b"verifyVAA", vaa],
  602. accounts=accts,
  603. sp=sp
  604. ))
  605. return txns
  606. def signVAA(self, client, sender, txns):
  607. transaction.assign_group_id(txns)
  608. grp = []
  609. pk = sender.getPrivateKey()
  610. for t in txns:
  611. if ("app_args" in t.__dict__ and len(t.app_args) > 0 and t.app_args[0] == b"verifySigs"):
  612. grp.append(transaction.LogicSigTransaction(t, self.vaa_verify["lsig"]))
  613. else:
  614. grp.append(t.sign(pk))
  615. client.send_transactions(grp)
  616. ret = []
  617. for x in grp:
  618. response = self.waitForTransaction(client, x.get_txid())
  619. if "logs" in response.__dict__ and len(response.__dict__["logs"]) > 0:
  620. ret.append(response.__dict__["logs"])
  621. return ret
  622. def check_bits_set(self, client, app_id, addr, seq):
  623. bits_set = {}
  624. app_state = None
  625. ai = client.account_info(addr)
  626. for app in ai["apps-local-state"]:
  627. if app["id"] == app_id:
  628. app_state = app["key-value"]
  629. if app_state == None:
  630. return False
  631. start = int(seq / max_bits) * max_bits
  632. s = int((seq - start) / bits_per_key)
  633. b = int(((seq - start) - (s * bits_per_key)) / 8)
  634. k = base64.b64encode(s.to_bytes(1, "big")).decode('utf-8')
  635. for kv in app_state:
  636. if kv["key"] != k:
  637. continue
  638. v = base64.b64decode(kv["value"]["bytes"])
  639. bt = 1 << (seq%8)
  640. return ((v[b] & bt) != 0)
  641. return False
  642. def submitVAA(self, vaa, client, sender, appid):
  643. # A lot of our logic here depends on parseVAA and knowing what the payload is..
  644. p = self.parseVAA(vaa)
  645. #pprint.pprint(p)
  646. seq_addr = self.optin(client, sender, appid, int(p["sequence"] / max_bits), p["chainRaw"].hex() + p["emitter"].hex())
  647. assert self.check_bits_set(client, appid, seq_addr, p["sequence"]) == False
  648. # And then the signatures to help us verify the vaa_s
  649. guardian_addr = self.optin(client, sender, self.coreid, p["index"], b"guardian".hex())
  650. accts = [seq_addr, guardian_addr]
  651. # If this happens to be setting up a new guardian set, we probably need it as well...
  652. if p["Meta"] == "CoreGovernance" and p["action"] == 2:
  653. newguardian_addr = self.optin(client, sender, self.coreid, p["NewGuardianSetIndex"], b"guardian".hex())
  654. accts.append(newguardian_addr)
  655. # When we attest for a new token, we need some place to store the info... later we will need to
  656. # mirror the other way as well
  657. if p["Meta"] == "TokenBridge Attest" or p["Meta"] == "TokenBridge Transfer" or p["Meta"] == "TokenBridge Transfer With Payload":
  658. if p["FromChain"] != 8:
  659. chain_addr = self.optin(client, sender, self.tokenid, p["FromChain"], p["Contract"])
  660. else:
  661. asset_id = int.from_bytes(bytes.fromhex(p["Contract"]), "big")
  662. chain_addr = self.optin(client, sender, self.tokenid, asset_id, b"native".hex())
  663. accts.append(chain_addr)
  664. keys = self.decodeLocalState(client, sender, self.coreid, guardian_addr)
  665. print("keys: " + keys.hex())
  666. sp = client.suggested_params()
  667. txns = []
  668. # How many signatures can we process in a single txn... we can do 9!
  669. bsize = (9*66)
  670. blocks = int(len(p["signatures"]) / bsize) + 1
  671. # We don't pass the entire payload in but instead just pass it pre digested. This gets around size
  672. # limitations with lsigs AND reduces the cost of the entire operation on a conjested network by reducing the
  673. # bytes passed into the transaction
  674. digest = keccak.new(digest_bits=256).update(keccak.new(digest_bits=256).update(p["digest"]).digest()).digest()
  675. for i in range(blocks):
  676. # Which signatures will we be verifying in this block
  677. sigs = p["signatures"][(i * bsize):]
  678. if (len(sigs) > bsize):
  679. sigs = sigs[:bsize]
  680. # keys
  681. kset = b''
  682. # Grab the key associated the signature
  683. for q in range(int(len(sigs) / 66)):
  684. # Which guardian is this signature associated with
  685. g = sigs[q * 66]
  686. key = keys[((g * 20) + 1) : (((g + 1) * 20) + 1)]
  687. kset = kset + key
  688. txns.append(transaction.ApplicationCallTxn(
  689. sender=self.vaa_verify["hash"],
  690. index=self.coreid,
  691. on_complete=transaction.OnComplete.NoOpOC,
  692. app_args=[b"verifySigs", sigs, kset, digest],
  693. accounts=accts,
  694. sp=sp
  695. ))
  696. txns[-1].fee = 0
  697. txns.append(transaction.ApplicationCallTxn(
  698. sender=sender.getAddress(),
  699. index=self.coreid,
  700. on_complete=transaction.OnComplete.NoOpOC,
  701. app_args=[b"verifyVAA", vaa],
  702. accounts=accts,
  703. sp=sp
  704. ))
  705. txns[-1].fee = txns[-1].fee * (1 + blocks)
  706. if p["Meta"] == "CoreGovernance":
  707. txns.append(transaction.ApplicationCallTxn(
  708. sender=sender.getAddress(),
  709. index=self.coreid,
  710. on_complete=transaction.OnComplete.NoOpOC,
  711. app_args=[b"governance", vaa],
  712. accounts=accts,
  713. sp=sp
  714. ))
  715. txns.append(transaction.ApplicationCallTxn(
  716. sender=sender.getAddress(),
  717. index=self.coreid,
  718. on_complete=transaction.OnComplete.NoOpOC,
  719. app_args=[b"nop", 5],
  720. sp=sp
  721. ))
  722. if p["Meta"] == "TokenBridge RegisterChain" or p["Meta"] == "TokenBridge UpgradeContract":
  723. txns.append(transaction.ApplicationCallTxn(
  724. sender=sender.getAddress(),
  725. index=self.tokenid,
  726. on_complete=transaction.OnComplete.NoOpOC,
  727. app_args=[b"governance", vaa],
  728. accounts=accts,
  729. foreign_apps = [self.coreid],
  730. sp=sp
  731. ))
  732. if p["Meta"] == "TokenBridge Attest":
  733. # if we DO decode it, we can do a sanity check... of
  734. # course, the hacker might NOT decode it so we have to
  735. # handle both cases...
  736. asset = (self.decodeLocalState(client, sender, self.tokenid, chain_addr))
  737. foreign_assets = []
  738. if (len(asset) > 8):
  739. foreign_assets.append(int.from_bytes(asset[0:8], "big"))
  740. txns.append(
  741. transaction.PaymentTxn(
  742. sender = sender.getAddress(),
  743. sp = sp,
  744. receiver = chain_addr,
  745. amt = 100000
  746. )
  747. )
  748. txns.append(transaction.ApplicationCallTxn(
  749. sender=sender.getAddress(),
  750. index=self.tokenid,
  751. on_complete=transaction.OnComplete.NoOpOC,
  752. app_args=[b"nop", 1],
  753. sp=sp
  754. ))
  755. txns.append(transaction.ApplicationCallTxn(
  756. sender=sender.getAddress(),
  757. index=self.tokenid,
  758. on_complete=transaction.OnComplete.NoOpOC,
  759. app_args=[b"nop", 2],
  760. sp=sp
  761. ))
  762. txns.append(transaction.ApplicationCallTxn(
  763. sender=sender.getAddress(),
  764. index=self.tokenid,
  765. on_complete=transaction.OnComplete.NoOpOC,
  766. app_args=[b"receiveAttest", vaa],
  767. accounts=accts,
  768. foreign_assets = foreign_assets,
  769. sp=sp
  770. ))
  771. txns[-1].fee = txns[-1].fee * 2
  772. if p["Meta"] == "TokenBridge Transfer" or p["Meta"] == "TokenBridge Transfer With Payload":
  773. foreign_assets = []
  774. a = 0
  775. if p["FromChain"] != 8:
  776. asset = (self.decodeLocalState(client, sender, self.tokenid, chain_addr))
  777. if (len(asset) > 8):
  778. a = int.from_bytes(asset[0:8], "big")
  779. else:
  780. a = int.from_bytes(bytes.fromhex(p["Contract"]), "big")
  781. # The receiver needs to be optin in to receive the coins... Yeah, the relayer pays for this
  782. aid = 0
  783. if p["ToChain"] == 8 and p["Type"] == 3:
  784. aid = int.from_bytes(bytes.fromhex(p["ToAddress"]), "big")
  785. addr = get_application_address(aid)
  786. else:
  787. addr = encode_address(bytes.fromhex(p["ToAddress"]))
  788. if a != 0:
  789. foreign_assets.append(a)
  790. self.asset_optin(client, sender, foreign_assets[0], addr)
  791. # And this is how the relayer gets paid...
  792. if p["Fee"] != self.zeroPadBytes:
  793. self.asset_optin(client, sender, foreign_assets[0], sender.getAddress())
  794. accts.append(addr)
  795. txns.append(transaction.ApplicationCallTxn(
  796. sender=sender.getAddress(),
  797. index=self.tokenid,
  798. on_complete=transaction.OnComplete.NoOpOC,
  799. app_args=[b"completeTransfer", vaa],
  800. accounts=accts,
  801. foreign_assets = foreign_assets,
  802. sp=sp
  803. ))
  804. if aid != 0:
  805. txns[-1].foreign_apps = [aid]
  806. # We need to cover the inner transactions
  807. if p["Fee"] != self.zeroPadBytes:
  808. txns[-1].fee = txns[-1].fee * 3
  809. else:
  810. txns[-1].fee = txns[-1].fee * 2
  811. if p["Meta"] == "TokenBridge Transfer With Payload":
  812. m = abi.Method("portal_transfer", [abi.Argument("byte[]")], abi.Returns("byte[]"))
  813. txns.append(transaction.ApplicationCallTxn(
  814. sender=sender.getAddress(),
  815. index=int.from_bytes(bytes.fromhex(p["ToAddress"]), "big"),
  816. on_complete=transaction.OnComplete.NoOpOC,
  817. app_args=[m.get_selector(), m.args[0].type.encode(vaa)],
  818. foreign_assets = foreign_assets,
  819. sp=sp
  820. ))
  821. transaction.assign_group_id(txns)
  822. grp = []
  823. pk = sender.getPrivateKey()
  824. for t in txns:
  825. if ("app_args" in t.__dict__ and len(t.app_args) > 0 and t.app_args[0] == b"verifySigs"):
  826. grp.append(transaction.LogicSigTransaction(t, self.vaa_verify["lsig"]))
  827. else:
  828. grp.append(t.sign(pk))
  829. client.send_transactions(grp)
  830. ret = []
  831. for x in grp:
  832. response = self.waitForTransaction(client, x.get_txid())
  833. if "logs" in response.__dict__ and len(response.__dict__["logs"]) > 0:
  834. ret.append(response.__dict__["logs"])
  835. assert self.check_bits_set(client, appid, seq_addr, p["sequence"]) == True
  836. return ret
  837. def parseVAA(self, vaa):
  838. # print (vaa.hex())
  839. ret = {"version": int.from_bytes(vaa[0:1], "big"), "index": int.from_bytes(vaa[1:5], "big"), "siglen": int.from_bytes(vaa[5:6], "big")}
  840. ret["signatures"] = vaa[6:(ret["siglen"] * 66) + 6]
  841. ret["sigs"] = []
  842. for i in range(ret["siglen"]):
  843. ret["sigs"].append(vaa[(6 + (i * 66)):(6 + (i * 66)) + 66].hex())
  844. off = (ret["siglen"] * 66) + 6
  845. ret["digest"] = vaa[off:] # This is what is actually signed...
  846. ret["timestamp"] = int.from_bytes(vaa[off:(off + 4)], "big")
  847. off += 4
  848. ret["nonce"] = int.from_bytes(vaa[off:(off + 4)], "big")
  849. off += 4
  850. ret["chainRaw"] = vaa[off:(off + 2)]
  851. ret["chain"] = int.from_bytes(vaa[off:(off + 2)], "big")
  852. off += 2
  853. ret["emitter"] = vaa[off:(off + 32)]
  854. off += 32
  855. ret["sequence"] = int.from_bytes(vaa[off:(off + 8)], "big")
  856. off += 8
  857. ret["consistency"] = int.from_bytes(vaa[off:(off + 1)], "big")
  858. off += 1
  859. ret["Meta"] = "Unknown"
  860. if vaa[off:(off + 32)].hex() == "000000000000000000000000000000000000000000546f6b656e427269646765":
  861. ret["Meta"] = "TokenBridge"
  862. ret["module"] = vaa[off:(off + 32)].hex()
  863. off += 32
  864. ret["action"] = int.from_bytes(vaa[off:(off + 1)], "big")
  865. off += 1
  866. if ret["action"] == 1:
  867. ret["Meta"] = "TokenBridge RegisterChain"
  868. ret["targetChain"] = int.from_bytes(vaa[off:(off + 2)], "big")
  869. off += 2
  870. ret["EmitterChainID"] = int.from_bytes(vaa[off:(off + 2)], "big")
  871. off += 2
  872. ret["targetEmitter"] = vaa[off:(off + 32)].hex()
  873. off += 32
  874. if ret["action"] == 2:
  875. ret["Meta"] = "TokenBridge UpgradeContract"
  876. ret["targetChain"] = int.from_bytes(vaa[off:(off + 2)], "big")
  877. off += 2
  878. ret["newContract"] = vaa[off:(off + 32)].hex()
  879. off += 32
  880. pprint.pprint((vaa[off:(off + 32)].hex(), "00000000000000000000000000000000000000000000000000000000436f7265"))
  881. if vaa[off:(off + 32)].hex() == "00000000000000000000000000000000000000000000000000000000436f7265":
  882. ret["Meta"] = "CoreGovernance"
  883. ret["module"] = vaa[off:(off + 32)].hex()
  884. off += 32
  885. ret["action"] = int.from_bytes(vaa[off:(off + 1)], "big")
  886. off += 1
  887. ret["targetChain"] = int.from_bytes(vaa[off:(off + 2)], "big")
  888. off += 2
  889. if ret["action"] == 2:
  890. ret["NewGuardianSetIndex"] = int.from_bytes(vaa[off:(off + 4)], "big")
  891. else:
  892. ret["Contract"] = vaa[off:(off + 32)].hex()
  893. if ((len(vaa[off:])) == 100) and int.from_bytes((vaa[off:off+1]), "big") == 2:
  894. ret["Meta"] = "TokenBridge Attest"
  895. ret["Type"] = int.from_bytes((vaa[off:off+1]), "big")
  896. off += 1
  897. ret["Contract"] = vaa[off:(off + 32)].hex()
  898. off += 32
  899. ret["FromChain"] = int.from_bytes(vaa[off:(off + 2)], "big")
  900. off += 2
  901. ret["Decimals"] = int.from_bytes((vaa[off:off+1]), "big")
  902. off += 1
  903. ret["Symbol"] = vaa[off:(off + 32)].hex()
  904. off += 32
  905. ret["Name"] = vaa[off:(off + 32)].hex()
  906. if ((len(vaa[off:])) == 133) and int.from_bytes((vaa[off:off+1]), "big") == 1:
  907. ret["Meta"] = "TokenBridge Transfer"
  908. ret["Type"] = int.from_bytes((vaa[off:off+1]), "big")
  909. off += 1
  910. ret["Amount"] = vaa[off:(off + 32)].hex()
  911. off += 32
  912. ret["Contract"] = vaa[off:(off + 32)].hex()
  913. off += 32
  914. ret["FromChain"] = int.from_bytes(vaa[off:(off + 2)], "big")
  915. off += 2
  916. ret["ToAddress"] = vaa[off:(off + 32)].hex()
  917. off += 32
  918. ret["ToChain"] = int.from_bytes(vaa[off:(off + 2)], "big")
  919. off += 2
  920. ret["Fee"] = vaa[off:(off + 32)].hex()
  921. if int.from_bytes((vaa[off:off+1]), "big") == 3:
  922. ret["Meta"] = "TokenBridge Transfer With Payload"
  923. ret["Type"] = int.from_bytes((vaa[off:off+1]), "big")
  924. off += 1
  925. ret["Amount"] = vaa[off:(off + 32)].hex()
  926. off += 32
  927. ret["Contract"] = vaa[off:(off + 32)].hex()
  928. off += 32
  929. ret["FromChain"] = int.from_bytes(vaa[off:(off + 2)], "big")
  930. off += 2
  931. ret["ToAddress"] = vaa[off:(off + 32)].hex()
  932. off += 32
  933. ret["ToChain"] = int.from_bytes(vaa[off:(off + 2)], "big")
  934. off += 2
  935. ret["Fee"] = self.zeroPadBytes;
  936. ret["FromAddress"] = vaa[off:(off + 32)].hex()
  937. off += 32
  938. ret["Payload"] = vaa[off:].hex()
  939. return ret
  940. def boot(self):
  941. print("")
  942. print("Creating the PortalCore app")
  943. self.coreid = self.createPortalCoreApp(client=self.client, sender=self.foundation)
  944. pprint.pprint({"wormhole core": str(self.coreid), "address": get_application_address(self.coreid), "emitterAddress": decode_address(get_application_address(self.coreid)).hex()})
  945. print("Create the token bridge")
  946. self.tokenid = self.createTokenBridgeApp(self.client, self.foundation)
  947. pprint.pprint({"token bridge": str(self.tokenid), "address": get_application_address(self.tokenid), "emitterAddress": decode_address(get_application_address(self.tokenid)).hex()})
  948. if self.devnet or self.args.testnet:
  949. if self.devnet:
  950. print("Create test app")
  951. self.testid = self.createTestApp(self.client, self.foundation)
  952. pprint.pprint({"testapp": str(self.testid)})
  953. suggestedParams = self.client.suggested_params()
  954. fundingAccount = self.getGenesisAccounts()[0]
  955. txns: List[transaction.Transaction] = []
  956. wallet = "castle sing ice patrol mixture artist violin someone what access slow wrestle clap hero sausage oyster boost tone receive rapid bike announce pepper absent involve"
  957. a = Account.FromMnemonic(wallet)
  958. txns.append(
  959. transaction.PaymentTxn(
  960. sender=fundingAccount.getAddress(),
  961. receiver=a.getAddress(),
  962. amt=self.FUNDING_AMOUNT,
  963. sp=suggestedParams,
  964. )
  965. )
  966. txns = transaction.assign_group_id(txns)
  967. signedTxns = [
  968. txn.sign(fundingAccount.getPrivateKey()) for i, txn in enumerate(txns)
  969. ]
  970. self.client.send_transactions(signedTxns)
  971. print("Sent some ALGO to: " + wallet)
  972. print("Creating a Token...")
  973. txn = transaction.AssetConfigTxn(
  974. sender=a.getAddress(),
  975. sp=suggestedParams,
  976. total=1000000,
  977. default_frozen=False,
  978. unit_name="NORIUM",
  979. asset_name="ChuckNorium",
  980. manager=a.getAddress(),
  981. reserve=a.getAddress(),
  982. freeze=a.getAddress(),
  983. clawback=a.getAddress(),
  984. strict_empty_address_check=False,
  985. decimals=6)
  986. stxn = txn.sign(a.getPrivateKey())
  987. txid = self.client.send_transaction(stxn)
  988. print("NORIUM creation transaction ID: {}".format(txid))
  989. confirmed_txn = transaction.wait_for_confirmation(self.client, txid, 4)
  990. print("TXID: ", txid)
  991. print("Result confirmed in round: {}".format(confirmed_txn['confirmed-round']))
  992. print("Creating an NFT...")
  993. # JSON file
  994. dir_path = os.path.dirname(os.path.realpath(__file__))
  995. f = open (dir_path + 'cnNftMetadata.json', "r")
  996. # Reading from file
  997. metadataJSON = json.loads(f.read())
  998. metadataStr = json.dumps(metadataJSON)
  999. hash = hashlib.new("sha512_256")
  1000. hash.update(b"arc0003/amj")
  1001. hash.update(metadataStr.encode("utf-8"))
  1002. json_metadata_hash = hash.digest()
  1003. print("json_metadata_hash: ", hash.hexdigest())
  1004. # Create transaction
  1005. txn = transaction.AssetConfigTxn(
  1006. sender=a.getAddress(),
  1007. sp=suggestedParams,
  1008. total=1,
  1009. default_frozen=False,
  1010. unit_name="CNART",
  1011. asset_name="ChuckNoriumArtwork@arc3",
  1012. manager=a.getAddress(),
  1013. reserve=a.getAddress(),
  1014. freeze=a.getAddress(),
  1015. clawback=a.getAddress(),
  1016. strict_empty_address_check=False,
  1017. url="file://cnNftMetadata.json",
  1018. metadata_hash=json_metadata_hash,
  1019. decimals=0)
  1020. stxn = txn.sign(a.getPrivateKey())
  1021. txid = self.client.send_transaction(stxn)
  1022. print("NORIUM NFT creation transaction ID: {}".format(txid))
  1023. confirmed_txn = transaction.wait_for_confirmation(self.client, txid, 4)
  1024. print("TXID: ", txid)
  1025. print("Result confirmed in round: {}".format(confirmed_txn['confirmed-round']))
  1026. if exists(self.args.env):
  1027. if self.gt == None:
  1028. self.gt = GenTest(False)
  1029. with open(self.args.env, encoding = 'utf-8') as f:
  1030. for line in f:
  1031. e = line.rstrip('\n').split("=")
  1032. print(e)
  1033. if "TOKEN_BRIDGE" in e[0]:
  1034. v = bytes.fromhex(e[1])
  1035. self.submitVAA(v, self.client, self.foundation, self.tokenid)
  1036. if "INIT_SIGNERS_CSV" in e[0]:
  1037. self.gt.guardianKeys = e[1].split(",")
  1038. print("guardianKeys: " + str(self.gt.guardianKeys))
  1039. if "INIT_SIGNERS_KEYS_CSV" in e[0]:
  1040. print("bootstrapping the guardian set...")
  1041. self.gt.guardianPrivKeys = e[1].split(",")
  1042. print("guardianPrivKeys: " + str(self.gt.guardianPrivKeys))
  1043. seq = int(random.random() * (2**31))
  1044. bootVAA = self.gt.genGuardianSetUpgrade(self.gt.guardianPrivKeys, self.args.guardianSet, self.args.guardianSet, seq, seq)
  1045. print("dev vaa: " + bootVAA)
  1046. self.bootGuardians(bytes.fromhex(bootVAA), self.client, self.foundation, self.coreid)
  1047. seq = int(random.random() * (2**31))
  1048. regChain = self.gt.genRegisterChain(self.gt.guardianPrivKeys, self.args.guardianSet, seq, seq, 8, decode_address(get_application_address(self.tokenid)).hex())
  1049. print("ALGO_TOKEN_BRIDGE_VAA=" + regChain)
  1050. # if self.args.env != ".env":
  1051. # v = bytes.fromhex(regChain)
  1052. # self.submitVAA(v, self.client, self.foundation, self.tokenid)
  1053. # print("We submitted it!")
  1054. def updateCore(self) -> None:
  1055. print("Updating the core contracts")
  1056. if self.args.approve == "" and self.args.clear == "":
  1057. approval, clear = getCoreContracts(False, self.args.core_approve, self.args.core_clear, self.client, seed_amt=self.seed_amt, tmpl_sig=self.tsig, devMode = self.devnet or self.args.testnet)
  1058. print("core approval " + decode_address(approval["hash"]).hex())
  1059. print("core clear " + decode_address(clear["hash"]).hex())
  1060. else:
  1061. pprint.pprint([self.args.approve, self.args.clear])
  1062. with open(self.args.approve, encoding = 'utf-8') as f:
  1063. approval = {"result": f.readlines()[0]}
  1064. pprint.pprint(approval)
  1065. with open(self.args.clear, encoding = 'utf-8') as f:
  1066. clear = {"result": f.readlines()[0]}
  1067. pprint.pprint(clear)
  1068. txn = transaction.ApplicationUpdateTxn(
  1069. index=self.coreid,
  1070. sender=self.foundation.getAddress(),
  1071. approval_program=b64decode(approval["result"]),
  1072. clear_program=b64decode(clear["result"]),
  1073. app_args=[ ],
  1074. sp=self.client.suggested_params(),
  1075. )
  1076. signedTxn = txn.sign(self.foundation.getPrivateKey())
  1077. print("sending transaction")
  1078. self.client.send_transaction(signedTxn)
  1079. resp = self.waitForTransaction(self.client, signedTxn.get_txid())
  1080. pprint.pprint(resp)
  1081. for x in resp.__dict__["logs"]:
  1082. print(x.hex())
  1083. print("complete")
  1084. def updateToken(self) -> None:
  1085. approval, clear = get_token_bridge(False, self.args.token_approve, self.args.token_clear, self.client, seed_amt=self.seed_amt, tmpl_sig=self.tsig, devMode = self.devnet or self.args.testnet)
  1086. print("token " + decode_address(approval["hash"]).hex())
  1087. print("Updating the token contracts: " + str(len(b64decode(approval["result"]))))
  1088. txn = transaction.ApplicationUpdateTxn(
  1089. index=self.tokenid,
  1090. sender=self.foundation.getAddress(),
  1091. approval_program=b64decode(approval["result"]),
  1092. clear_program=b64decode(clear["result"]),
  1093. app_args=[ ],
  1094. sp=self.client.suggested_params(),
  1095. )
  1096. signedTxn = txn.sign(self.foundation.getPrivateKey())
  1097. print("sending transaction")
  1098. self.client.send_transaction(signedTxn)
  1099. resp = self.waitForTransaction(self.client, signedTxn.get_txid())
  1100. for x in resp.__dict__["logs"]:
  1101. print(x.hex())
  1102. print("complete")
  1103. def genTeal(self) -> None:
  1104. print((True, self.args.core_approve, self.args.core_clear, self.client, self.seed_amt, self.tsig, self.devnet or self.args.testnet))
  1105. devmode = (self.devnet or self.args.testnet) and not self.args.prodTeal
  1106. approval1, clear1 = getCoreContracts(True, self.args.core_approve, self.args.core_clear, self.client, seed_amt=self.seed_amt, tmpl_sig=self.tsig, devMode = devmode)
  1107. print("Generating the teal for the core contracts")
  1108. approval2, clear2 = get_token_bridge(True, self.args.token_approve, self.args.token_clear, self.client, seed_amt=self.seed_amt, tmpl_sig=self.tsig, devMode = devmode)
  1109. print("Generating the teal for the token contracts: " + str(len(b64decode(approval2["result"]))))
  1110. if self.devnet:
  1111. v = self.genUpgradePayloadBody(approval1, approval2)
  1112. if self.gt == None:
  1113. self.gt = GenTest(False)
  1114. emitter = bytes.fromhex(self.zeroPadBytes[0:(31*2)] + "04")
  1115. guardianSet = 0
  1116. nonce = int(random.random() * 20000)
  1117. coreVAA = self.gt.createSignedVAA(guardianSet, self.gt.guardianPrivKeys, int(time.time()), nonce, 1, emitter, int(random.random() * 20000), 32, 8, v[0])
  1118. tokenVAA = self.gt.createSignedVAA(guardianSet, self.gt.guardianPrivKeys, int(time.time()), nonce, 1, emitter, int(random.random() * 20000), 32, 8, v[1])
  1119. with open("teal/core_devnet.vaa", "w") as fout:
  1120. fout.write(coreVAA)
  1121. with open("teal/token_devnet.vaa", "w") as fout:
  1122. fout.write(tokenVAA)
  1123. def testnet(self):
  1124. self.ALGOD_ADDRESS = self.args.algod_address = "https://testnet-api.algonode.cloud"
  1125. self.INDEXER_ADDRESS = "https://testnet-idx.algonode.cloud"
  1126. self.coreid = self.args.coreid
  1127. self.tokenid = self.args.tokenid
  1128. def mainnet(self):
  1129. self.ALGOD_ADDRESS = self.args.algod_address = "https://mainnet-api.algonode.cloud"
  1130. self.INDEXER_ADDRESS = "https://mainnet-idx.algonode.cloud"
  1131. self.coreid = 842125965
  1132. self.tokenid = 842126029
  1133. if self.args.coreid != 4:
  1134. self.coreid = self.args.coreid
  1135. if self.args.coreid != 6:
  1136. self.tokenid = self.args.tokenid
  1137. def setup_args(self) -> None:
  1138. parser = argparse.ArgumentParser(description='algorand setup')
  1139. parser.add_argument('--algod_address', type=str, help='algod address (default: http://localhost:4001)',
  1140. default="http://localhost:4001")
  1141. parser.add_argument('--algod_token', type=str, help='algod access token',
  1142. default="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
  1143. parser.add_argument('--kmd_address', type=str, help='kmd wallet address (default: http://localhost:4002)',
  1144. default="http://localhost:4002")
  1145. parser.add_argument('--kmd_token', type=str, help='kmd wallet access token',
  1146. default="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
  1147. parser.add_argument('--kmd_name', type=str, help='kmd wallet name',
  1148. default="unencrypted-default-wallet")
  1149. parser.add_argument('--kmd_password', type=str, help='kmd wallet password', default="")
  1150. parser.add_argument('--mnemonic', type=str, help='account mnemonic', default="")
  1151. parser.add_argument('--core_approve', type=str, help='core approve teal', default="teal/core_approve.teal")
  1152. parser.add_argument('--core_clear', type=str, help='core clear teal', default="teal/core_clear.teal")
  1153. parser.add_argument('--token_approve', type=str, help='token approve teal', default="teal/token_approve.teal")
  1154. parser.add_argument('--token_clear', type=str, help='token clear teal', default="teal/token_clear.teal")
  1155. parser.add_argument('--coreid', type=int, help='core contract', default=4)
  1156. parser.add_argument('--tokenid', type=int, help='token bridge contract', default=6)
  1157. parser.add_argument('--devnet', action='store_true', help='setup devnet')
  1158. parser.add_argument('--boot', action='store_true', help='bootstrap')
  1159. parser.add_argument('--upgradePayload', action='store_true', help='gen the upgrade payload for the guardians to sign')
  1160. parser.add_argument('--vaa', type=str, help='Submit the supplied VAA', default="")
  1161. parser.add_argument('--env', type=str, help='deploying using the supplied .env file', default=".env")
  1162. parser.add_argument('--guardianSet', type=int, help='What guardianSet should I syntheticly create if needed', default=0)
  1163. parser.add_argument('--appid', type=str, help='The appid that the vaa submit is applied to', default="")
  1164. parser.add_argument('--submit', action='store_true', help='submit the synthetic vaas')
  1165. parser.add_argument('--updateCore', action='store_true', help='update the Core contracts')
  1166. parser.add_argument('--updateToken', action='store_true', help='update the Token contracts')
  1167. parser.add_argument('--upgradeVAA', action='store_true', help='generate a upgrade vaa for devnet')
  1168. parser.add_argument('--print', action='store_true', help='print')
  1169. parser.add_argument('--genParts', action='store_true', help='Get tssig parts')
  1170. parser.add_argument('--prodTeal', action='store_true', help='use Production Deal')
  1171. parser.add_argument('--genTeal', action='store_true', help='Generate all the teal from the pyteal')
  1172. parser.add_argument('--fund', action='store_true', help='Generate some accounts and fund them')
  1173. parser.add_argument('--testnet', action='store_true', help='Connect to testnet')
  1174. parser.add_argument('--mainnet', action='store_true', help='Connect to mainnet')
  1175. parser.add_argument('--bootGuardian', type=str, help='Submit the supplied VAA', default="")
  1176. parser.add_argument('--rpc', type=str, help='RPC address', default="")
  1177. parser.add_argument('--guardianKeys', type=str, help='GuardianKeys', default="")
  1178. parser.add_argument('--guardianPrivKeys', type=str, help='guardianPrivKeys', default="")
  1179. parser.add_argument('--approve', type=str, help='compiled approve contract', default="")
  1180. parser.add_argument('--clear', type=str, help='compiled clear contract', default="")
  1181. args = parser.parse_args()
  1182. self.init(args)
  1183. self.devnet = args.devnet
  1184. def main(self) -> None:
  1185. self.setup_args()
  1186. args = self.args
  1187. if args.testnet:
  1188. self.testnet()
  1189. if args.mainnet:
  1190. self.mainnet()
  1191. if args.rpc != "":
  1192. self.ALGOD_ADDRESS = self.args.rpc
  1193. self.client = self.getAlgodClient()
  1194. if self.devnet or self.args.testnet:
  1195. self.vaa_verify = self.client.compile(get_vaa_verify())
  1196. else:
  1197. c = AlgodClient("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "https://testnet-api.algonode.cloud")
  1198. self.vaa_verify = c.compile(get_vaa_verify())
  1199. self.vaa_verify["lsig"] = LogicSig(base64.b64decode(self.vaa_verify["result"]))
  1200. if args.genTeal or args.boot:
  1201. self.genTeal()
  1202. # Generate the upgrade payload we need the guardians to sign
  1203. if args.upgradePayload:
  1204. print(self.genUpgradePayload())
  1205. sys.exit(0)
  1206. # This breaks the tsig up into the various parts so that we
  1207. # can embed it into the Typescript code for reassembly
  1208. if args.genParts:
  1209. print("this.ALGO_VERIFY_HASH = \"%s\""%self.vaa_verify["hash"]);
  1210. print("this.ALGO_VERIFY = new Uint8Array([", end='')
  1211. for x in b64decode(self.vaa_verify["result"]):
  1212. print("%d, "%(x), end='')
  1213. print("])")
  1214. parts = [
  1215. self.tsig.get_bytecode_raw(0).hex(),
  1216. self.tsig.get_bytecode_raw(1).hex(),
  1217. self.tsig.get_bytecode_raw(2).hex(),
  1218. self.tsig.get_bytecode_raw(3).hex(),
  1219. self.tsig.get_bytecode_raw(4).hex()
  1220. ]
  1221. pprint.pprint(parts)
  1222. sys.exit(0)
  1223. if args.mnemonic:
  1224. self.foundation = Account.FromMnemonic(args.mnemonic)
  1225. if args.devnet and self.foundation == None:
  1226. print("Generating the foundation account...")
  1227. self.foundation = self.getTemporaryAccount(self.client)
  1228. if self.args.fund:
  1229. sys.exit(0)
  1230. if self.foundation == None:
  1231. print("We dont have a account? Here is a random one I just made up...")
  1232. pk = account.generate_account()[0]
  1233. print(" pk: " + pk)
  1234. print(" address: " + account.address_from_private_key(pk))
  1235. print(" mnemonic: " + mnemonic.from_private_key(pk))
  1236. if args.testnet:
  1237. print("go to https://bank.testnet.algorand.network/ to fill it up (You will probably want to send at least 2 loads to the wallet)")
  1238. sys.exit(0)
  1239. bal = self.getBalances(self.client, self.foundation.addr)
  1240. print("foundation address " + self.foundation.addr + " (" + str(float(bal[0]) / 1000000.0) + " ALGO)")
  1241. if bal[0] < 10000000:
  1242. print("you need at least 10 ALGO to do darn near anything...")
  1243. sys.exit(0)
  1244. if args.guardianKeys != "":
  1245. self.gt.guardianKeys = eval(args.guardianKeys)
  1246. if args.guardianPrivKeys != "":
  1247. self.gt.guardianPrivKeyss = eval(args.guardianPrivKeys)
  1248. if args.upgradeVAA:
  1249. ret = self.devnetUpgradeVAA()
  1250. pprint.pprint(ret)
  1251. if (args.submit) :
  1252. print("submitting vaa to upgrade core: " + str(self.coreid))
  1253. state = self.read_global_state(self.client, self.foundation.addr, self.coreid)
  1254. pprint.pprint( {
  1255. "validUpdateApproveHash": b64decode(state["validUpdateApproveHash"]).hex(),
  1256. "validUpdateClearHash": b64decode(state["validUpdateClearHash"]).hex()
  1257. })
  1258. self.submitVAA(bytes.fromhex(ret[0]), self.client, self.foundation, self.coreid)
  1259. state = self.read_global_state(self.client, self.foundation.addr, self.coreid)
  1260. pprint.pprint( {
  1261. "validUpdateApproveHash": b64decode(state["validUpdateApproveHash"]).hex(),
  1262. "validUpdateClearHash": b64decode(state["validUpdateClearHash"]).hex()
  1263. })
  1264. print("submitting vaa to upgrade token: " + str(self.tokenid))
  1265. state = self.read_global_state(self.client, self.foundation.addr, self.tokenid)
  1266. pprint.pprint( {
  1267. "validUpdateApproveHash": b64decode(state["validUpdateApproveHash"]).hex(),
  1268. "validUpdateClearHash": b64decode(state["validUpdateClearHash"]).hex()
  1269. })
  1270. self.submitVAA(bytes.fromhex(ret[1]), self.client, self.foundation, self.tokenid)
  1271. state = self.read_global_state(self.client, self.foundation.addr, self.tokenid)
  1272. pprint.pprint( {
  1273. "validUpdateApproveHash": b64decode(state["validUpdateApproveHash"]).hex(),
  1274. "validUpdateClearHash": b64decode(state["validUpdateClearHash"]).hex()
  1275. })
  1276. if args.boot:
  1277. self.boot()
  1278. if args.updateCore:
  1279. self.updateCore()
  1280. if args.updateToken:
  1281. self.updateToken()
  1282. if args.vaa:
  1283. if self.args.appid == "":
  1284. raise Exception("You need to specifiy the appid when you are submitting vaas")
  1285. vaa = bytes.fromhex(args.vaa)
  1286. pprint.pprint(self.parseVAA(vaa))
  1287. self.submitVAA(vaa, self.client, self.foundation, int(self.args.appid))
  1288. if args.bootGuardian != "":
  1289. vaa = bytes.fromhex(args.bootGuardian)
  1290. pprint.pprint(self.parseVAA(vaa))
  1291. response = self.bootGuardians(vaa, self.client, self.foundation, self.coreid)
  1292. pprint.pprint(response.__dict__)
  1293. if __name__ == "__main__":
  1294. core = PortalCore()
  1295. core.main()