1491 lines
60 KiB
Python
1491 lines
60 KiB
Python
# python3 -m pip install pycryptodomex uvarint pyteal web3 coincurve
|
|
|
|
import os
|
|
from os.path import exists
|
|
from time import time, sleep
|
|
from eth_abi import encode_single, encode_abi
|
|
from typing import List, Tuple, Dict, Any, Optional, Union
|
|
from base64 import b64decode
|
|
import base64
|
|
import random
|
|
import time
|
|
import hashlib
|
|
import uuid
|
|
import sys
|
|
import json
|
|
import uvarint
|
|
from local_blob import LocalBlob
|
|
from wormhole_core import getCoreContracts
|
|
from TmplSig import TmplSig
|
|
import argparse
|
|
from gentest import GenTest
|
|
|
|
from algosdk.v2client.algod import AlgodClient
|
|
from algosdk.kmd import KMDClient
|
|
from algosdk import account, mnemonic, abi
|
|
from algosdk.encoding import decode_address, encode_address
|
|
from algosdk.future import transaction
|
|
from pyteal import compileTeal, Mode, Expr
|
|
from pyteal import *
|
|
from algosdk.logic import get_application_address
|
|
from vaa_verify import get_vaa_verify
|
|
|
|
from Cryptodome.Hash import keccak
|
|
|
|
from algosdk.future.transaction import LogicSig
|
|
|
|
from token_bridge import get_token_bridge
|
|
|
|
from test_contract import get_test_app
|
|
|
|
from algosdk.v2client import indexer
|
|
|
|
import pprint
|
|
|
|
max_keys = 15
|
|
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
|
|
|
|
class Account:
|
|
"""Represents a private key and address for an Algorand account"""
|
|
|
|
def __init__(self, privateKey: str) -> None:
|
|
self.sk = privateKey
|
|
self.addr = account.address_from_private_key(privateKey)
|
|
print (privateKey)
|
|
print (" " + self.getMnemonic())
|
|
print (" " + self.addr)
|
|
|
|
def getAddress(self) -> str:
|
|
return self.addr
|
|
|
|
def getPrivateKey(self) -> str:
|
|
return self.sk
|
|
|
|
def getMnemonic(self) -> str:
|
|
return mnemonic.from_private_key(self.sk)
|
|
|
|
@classmethod
|
|
def FromMnemonic(cls, m: str) -> "Account":
|
|
return cls(mnemonic.to_private_key(m))
|
|
|
|
class PendingTxnResponse:
|
|
def __init__(self, response: Dict[str, Any]) -> None:
|
|
self.poolError: str = response["pool-error"]
|
|
self.txn: Dict[str, Any] = response["txn"]
|
|
|
|
self.applicationIndex: Optional[int] = response.get("application-index")
|
|
self.assetIndex: Optional[int] = response.get("asset-index")
|
|
self.closeRewards: Optional[int] = response.get("close-rewards")
|
|
self.closingAmount: Optional[int] = response.get("closing-amount")
|
|
self.confirmedRound: Optional[int] = response.get("confirmed-round")
|
|
self.globalStateDelta: Optional[Any] = response.get("global-state-delta")
|
|
self.localStateDelta: Optional[Any] = response.get("local-state-delta")
|
|
self.receiverRewards: Optional[int] = response.get("receiver-rewards")
|
|
self.senderRewards: Optional[int] = response.get("sender-rewards")
|
|
|
|
self.innerTxns: List[Any] = response.get("inner-txns", [])
|
|
self.logs: List[bytes] = [b64decode(l) for l in response.get("logs", [])]
|
|
|
|
class PortalCore:
|
|
def __init__(self) -> None:
|
|
self.gt = None
|
|
self.foundation = None
|
|
self.devnet = False
|
|
|
|
self.ALGOD_ADDRESS = "http://localhost:4001"
|
|
self.ALGOD_TOKEN = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
|
self.FUNDING_AMOUNT = 100_000_000_000
|
|
|
|
self.KMD_ADDRESS = "http://localhost:4002"
|
|
self.KMD_TOKEN = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
|
self.KMD_WALLET_NAME = "unencrypted-default-wallet"
|
|
self.KMD_WALLET_PASSWORD = ""
|
|
|
|
self.INDEXER_TOKEN = "a" * 64
|
|
self.INDEXER_ADDRESS = 'http://localhost:8980'
|
|
self.INDEXER_ROUND = 0
|
|
self.NOTE_PREFIX = 'publishMessage'.encode()
|
|
|
|
self.myindexer = None
|
|
|
|
self.seed_amt = int(1002000) # The black magic in this number...
|
|
self.cache = {}
|
|
self.asset_cache = {}
|
|
|
|
self.kmdAccounts : Optional[List[Account]] = None
|
|
|
|
self.accountList : List[Account] = []
|
|
self.zeroPadBytes = "00"*32
|
|
|
|
self.tsig = TmplSig("sig")
|
|
|
|
|
|
def init(self, args) -> None:
|
|
self.args = args
|
|
self.ALGOD_ADDRESS = args.algod_address
|
|
self.ALGOD_TOKEN = args.algod_token
|
|
self.KMD_ADDRESS = args.kmd_address
|
|
self.KMD_TOKEN = args.kmd_token
|
|
self.KMD_WALLET_NAME = args.kmd_name
|
|
self.KMD_WALLET_PASSWORD = args.kmd_password
|
|
self.TARGET_ACCOUNT = args.mnemonic
|
|
self.coreid = args.coreid
|
|
self.tokenid = args.tokenid
|
|
|
|
if exists(self.args.env):
|
|
if self.gt == None:
|
|
self.gt = GenTest(False)
|
|
|
|
with open(self.args.env, encoding = 'utf-8') as f:
|
|
for line in f:
|
|
e = line.rstrip('\n').split("=")
|
|
if "INIT_SIGNERS_CSV" in e[0]:
|
|
self.gt.guardianKeys = e[1].split(",")
|
|
if "INIT_SIGNERS_KEYS_CSV" in e[0]:
|
|
self.gt.guardianPrivKeys = e[1].split(",")
|
|
print("guardianPrivKeys=" + str(self.gt.guardianPrivKeys))
|
|
|
|
def waitForTransaction(
|
|
self, client: AlgodClient, txID: str, timeout: int = 10
|
|
) -> PendingTxnResponse:
|
|
lastStatus = client.status()
|
|
lastRound = lastStatus["last-round"]
|
|
startRound = lastRound
|
|
|
|
while lastRound < startRound + timeout:
|
|
pending_txn = client.pending_transaction_info(txID)
|
|
|
|
if pending_txn.get("confirmed-round", 0) > 0:
|
|
return PendingTxnResponse(pending_txn)
|
|
|
|
if pending_txn["pool-error"]:
|
|
raise Exception("Pool error: {}".format(pending_txn["pool-error"]))
|
|
|
|
lastStatus = client.status_after_block(lastRound + 1)
|
|
|
|
lastRound += 1
|
|
|
|
raise Exception(
|
|
"Transaction {} not confirmed after {} rounds".format(txID, timeout)
|
|
)
|
|
|
|
def getKmdClient(self) -> KMDClient:
|
|
return KMDClient(self.KMD_TOKEN, self.KMD_ADDRESS)
|
|
|
|
def getGenesisAccounts(self) -> List[Account]:
|
|
if self.kmdAccounts is None:
|
|
kmd = self.getKmdClient()
|
|
|
|
wallets = kmd.list_wallets()
|
|
walletID = None
|
|
for wallet in wallets:
|
|
if wallet["name"] == self.KMD_WALLET_NAME:
|
|
walletID = wallet["id"]
|
|
break
|
|
|
|
if walletID is None:
|
|
raise Exception("Wallet not found: {}".format(self.KMD_WALLET_NAME))
|
|
|
|
walletHandle = kmd.init_wallet_handle(walletID, self.KMD_WALLET_PASSWORD)
|
|
|
|
try:
|
|
addresses = kmd.list_keys(walletHandle)
|
|
privateKeys = [
|
|
kmd.export_key(walletHandle, self.KMD_WALLET_PASSWORD, addr)
|
|
for addr in addresses
|
|
]
|
|
self.kmdAccounts = [Account(sk) for sk in privateKeys]
|
|
finally:
|
|
kmd.release_wallet_handle(walletHandle)
|
|
|
|
return self.kmdAccounts
|
|
|
|
def getTemporaryAccount(self, client: AlgodClient) -> Account:
|
|
if len(self.accountList) == 0:
|
|
sks = [account.generate_account()[0] for i in range(3)]
|
|
self.accountList = [Account(sk) for sk in sks]
|
|
|
|
genesisAccounts = self.getGenesisAccounts()
|
|
suggestedParams = client.suggested_params()
|
|
|
|
txns: List[transaction.Transaction] = []
|
|
for i, a in enumerate(self.accountList):
|
|
fundingAccount = genesisAccounts[i % len(genesisAccounts)]
|
|
txns.append(
|
|
transaction.PaymentTxn(
|
|
sender=fundingAccount.getAddress(),
|
|
receiver=a.getAddress(),
|
|
amt=self.FUNDING_AMOUNT,
|
|
sp=suggestedParams,
|
|
)
|
|
)
|
|
|
|
txns = transaction.assign_group_id(txns)
|
|
signedTxns = [
|
|
txn.sign(genesisAccounts[i % len(genesisAccounts)].getPrivateKey())
|
|
for i, txn in enumerate(txns)
|
|
]
|
|
|
|
client.send_transactions(signedTxns)
|
|
|
|
self.waitForTransaction(client, signedTxns[0].get_txid())
|
|
|
|
return self.accountList.pop()
|
|
|
|
def getAlgodClient(self) -> AlgodClient:
|
|
return AlgodClient(self.ALGOD_TOKEN, self.ALGOD_ADDRESS)
|
|
|
|
def getBalances(self, client: AlgodClient, account: str) -> Dict[int, int]:
|
|
balances: Dict[int, int] = dict()
|
|
|
|
accountInfo = client.account_info(account)
|
|
|
|
# set key 0 to Algo balance
|
|
balances[0] = accountInfo["amount"]
|
|
|
|
assets: List[Dict[str, Any]] = accountInfo.get("assets", [])
|
|
for assetHolding in assets:
|
|
assetID = assetHolding["asset-id"]
|
|
amount = assetHolding["amount"]
|
|
balances[assetID] = amount
|
|
|
|
return balances
|
|
|
|
def fullyCompileContract(self, client: AlgodClient, contract: Expr) -> bytes:
|
|
teal = compileTeal(contract, mode=Mode.Application, version=6)
|
|
response = client.compile(teal)
|
|
return response
|
|
|
|
# helper function that formats global state for printing
|
|
def format_state(self, state):
|
|
formatted = {}
|
|
for item in state:
|
|
key = item['key']
|
|
value = item['value']
|
|
formatted_key = base64.b64decode(key).decode('utf-8')
|
|
if value['type'] == 1:
|
|
# byte string
|
|
if formatted_key == 'voted':
|
|
formatted_value = base64.b64decode(value['bytes']).decode('utf-8')
|
|
else:
|
|
formatted_value = value['bytes']
|
|
formatted[formatted_key] = formatted_value
|
|
else:
|
|
# integer
|
|
formatted[formatted_key] = value['uint']
|
|
return formatted
|
|
|
|
# helper function to read app global state
|
|
def read_global_state(self, client, addr, app_id):
|
|
results = client.account_info(addr)
|
|
apps_created = results['created-apps']
|
|
for app in apps_created:
|
|
if app['id'] == app_id and 'global-state' in app['params']:
|
|
return self.format_state(app['params']['global-state'])
|
|
return {}
|
|
|
|
def read_state(self, client, addr, app_id):
|
|
results = client.account_info(addr)
|
|
apps_created = results['created-apps']
|
|
for app in apps_created:
|
|
if app['id'] == app_id:
|
|
return app
|
|
return {}
|
|
|
|
def encoder(self, type, val):
|
|
if type == 'uint8':
|
|
return encode_single(type, val).hex()[62:64]
|
|
if type == 'uint16':
|
|
return encode_single(type, val).hex()[60:64]
|
|
if type == 'uint32':
|
|
return encode_single(type, val).hex()[56:64]
|
|
if type == 'uint64':
|
|
return encode_single(type, val).hex()[64-(16):64]
|
|
if type == 'uint128':
|
|
return encode_single(type, val).hex()[64-(32):64]
|
|
if type == 'uint256' or type == 'bytes32':
|
|
return encode_single(type, val).hex()[64-(64):64]
|
|
raise Exception("invalid type")
|
|
|
|
def devnetUpgradeVAA(self):
|
|
v = self.genUpgradePayload()
|
|
print("core payload: " + str(v[0]))
|
|
print("token payload: " + str(v[1]))
|
|
|
|
if self.gt == None:
|
|
self.gt = GenTest(False)
|
|
|
|
emitter = bytes.fromhex(self.zeroPadBytes[0:(31*2)] + "04")
|
|
|
|
guardianSet = self.getGovSet()
|
|
|
|
print("guardianSet: " + str(guardianSet))
|
|
|
|
nonce = int(random.random() * 20000)
|
|
ret = [
|
|
self.gt.createSignedVAA(guardianSet, self.gt.guardianPrivKeys, int(time.time()), nonce, 1, emitter, int(random.random() * 20000), 32, 8, v[0]),
|
|
self.gt.createSignedVAA(guardianSet, self.gt.guardianPrivKeys, int(time.time()), nonce, 1, emitter, int(random.random() * 20000), 32, 8, v[1]),
|
|
]
|
|
|
|
# pprint.pprint(self.parseVAA(bytes.fromhex(ret[0])))
|
|
# pprint.pprint(self.parseVAA(bytes.fromhex(ret[1])))
|
|
|
|
return ret
|
|
|
|
def getMessageFee(self):
|
|
s = self.client.application_info(self.coreid)["params"]["global-state"]
|
|
k = base64.b64encode(b"MessageFee").decode('utf-8')
|
|
for x in s:
|
|
if x["key"] == k:
|
|
return x["value"]["uint"]
|
|
return -1
|
|
|
|
def getGovSet(self):
|
|
s = self.client.application_info(self.coreid)["params"]["global-state"]
|
|
k = base64.b64encode(b"currentGuardianSetIndex").decode('utf-8')
|
|
for x in s:
|
|
if x["key"] == k:
|
|
return x["value"]["uint"]
|
|
return -1
|
|
|
|
def genUpgradePayload(self):
|
|
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)
|
|
|
|
b = self.zeroPadBytes[0:(28*2)]
|
|
b += self.encoder("uint8", ord("C"))
|
|
b += self.encoder("uint8", ord("o"))
|
|
b += self.encoder("uint8", ord("r"))
|
|
b += self.encoder("uint8", ord("e"))
|
|
b += self.encoder("uint8", 1)
|
|
b += self.encoder("uint16", 8)
|
|
|
|
b += decode_address(approval["hash"]).hex()
|
|
|
|
ret = [b]
|
|
|
|
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)
|
|
|
|
|
|
b = self.zeroPadBytes[0:((32 -11)*2)]
|
|
b += self.encoder("uint8", ord("T"))
|
|
b += self.encoder("uint8", ord("o"))
|
|
b += self.encoder("uint8", ord("k"))
|
|
b += self.encoder("uint8", ord("e"))
|
|
b += self.encoder("uint8", ord("n"))
|
|
b += self.encoder("uint8", ord("B"))
|
|
b += self.encoder("uint8", ord("r"))
|
|
b += self.encoder("uint8", ord("i"))
|
|
b += self.encoder("uint8", ord("d"))
|
|
b += self.encoder("uint8", ord("g"))
|
|
b += self.encoder("uint8", ord("e"))
|
|
|
|
b += self.encoder("uint8", 2) # action
|
|
b += self.encoder("uint16", 8) # target chain
|
|
b += decode_address(approval["hash"]).hex()
|
|
print(decode_address(approval["hash"]).hex())
|
|
|
|
ret.append(b)
|
|
return ret
|
|
|
|
def createPortalCoreApp(
|
|
self,
|
|
client: AlgodClient,
|
|
sender: Account,
|
|
) -> int:
|
|
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)
|
|
|
|
globalSchema = transaction.StateSchema(num_uints=8, num_byte_slices=40)
|
|
localSchema = transaction.StateSchema(num_uints=0, num_byte_slices=16)
|
|
|
|
app_args = [ ]
|
|
|
|
txn = transaction.ApplicationCreateTxn(
|
|
sender=sender.getAddress(),
|
|
on_complete=transaction.OnComplete.NoOpOC,
|
|
approval_program=b64decode(approval["result"]),
|
|
clear_program=b64decode(clear["result"]),
|
|
global_schema=globalSchema,
|
|
local_schema=localSchema,
|
|
extra_pages = 1,
|
|
app_args=app_args,
|
|
sp=client.suggested_params(),
|
|
)
|
|
|
|
signedTxn = txn.sign(sender.getPrivateKey())
|
|
|
|
client.send_transaction(signedTxn)
|
|
|
|
response = self.waitForTransaction(client, signedTxn.get_txid())
|
|
assert response.applicationIndex is not None and response.applicationIndex > 0
|
|
|
|
# Lets give it a bit of money so that it is not a "ghost" account
|
|
txn = transaction.PaymentTxn(sender = sender.getAddress(), sp = client.suggested_params(), receiver = get_application_address(response.applicationIndex), amt = 100000)
|
|
signedTxn = txn.sign(sender.getPrivateKey())
|
|
client.send_transaction(signedTxn)
|
|
|
|
return response.applicationIndex
|
|
|
|
def createTokenBridgeApp(
|
|
self,
|
|
client: AlgodClient,
|
|
sender: Account,
|
|
) -> int:
|
|
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)
|
|
|
|
if len(b64decode(approval["result"])) > 4060:
|
|
print("token bridge contract is too large... This might prevent updates later")
|
|
|
|
globalSchema = transaction.StateSchema(num_uints=4, num_byte_slices=30)
|
|
localSchema = transaction.StateSchema(num_uints=0, num_byte_slices=16)
|
|
|
|
app_args = [self.coreid, decode_address(get_application_address(self.coreid))]
|
|
|
|
txn = transaction.ApplicationCreateTxn(
|
|
sender=sender.getAddress(),
|
|
on_complete=transaction.OnComplete.NoOpOC,
|
|
approval_program=b64decode(approval["result"]),
|
|
clear_program=b64decode(clear["result"]),
|
|
global_schema=globalSchema,
|
|
local_schema=localSchema,
|
|
app_args=app_args,
|
|
extra_pages = 2,
|
|
sp=client.suggested_params(),
|
|
)
|
|
|
|
signedTxn = txn.sign(sender.getPrivateKey())
|
|
|
|
client.send_transaction(signedTxn)
|
|
|
|
response = self.waitForTransaction(client, signedTxn.get_txid())
|
|
#pprint.pprint(response.__dict__)
|
|
assert response.applicationIndex is not None and response.applicationIndex > 0
|
|
|
|
# Lets give it a bit of money so that it is not a "ghost" account
|
|
txn = transaction.PaymentTxn(sender = sender.getAddress(), sp = client.suggested_params(), receiver = get_application_address(response.applicationIndex), amt = 100000)
|
|
signedTxn = txn.sign(sender.getPrivateKey())
|
|
client.send_transaction(signedTxn)
|
|
|
|
return response.applicationIndex
|
|
|
|
def createTestApp(
|
|
self,
|
|
client: AlgodClient,
|
|
sender: Account,
|
|
) -> int:
|
|
approval, clear = get_test_app(client)
|
|
|
|
globalSchema = transaction.StateSchema(num_uints=4, num_byte_slices=30)
|
|
localSchema = transaction.StateSchema(num_uints=0, num_byte_slices=16)
|
|
|
|
txn = transaction.ApplicationCreateTxn(
|
|
sender=sender.getAddress(),
|
|
on_complete=transaction.OnComplete.NoOpOC,
|
|
approval_program=b64decode(approval["result"]),
|
|
clear_program=b64decode(clear["result"]),
|
|
global_schema=globalSchema,
|
|
local_schema=localSchema,
|
|
sp=client.suggested_params(),
|
|
)
|
|
|
|
signedTxn = txn.sign(sender.getPrivateKey())
|
|
|
|
client.send_transaction(signedTxn)
|
|
|
|
response = self.waitForTransaction(client, signedTxn.get_txid())
|
|
assert response.applicationIndex is not None and response.applicationIndex > 0
|
|
|
|
# Lets give it a bit of money so that it is not a "ghost" account
|
|
txn = transaction.PaymentTxn(sender = sender.getAddress(), sp = client.suggested_params(), receiver = get_application_address(response.applicationIndex), amt = 100000)
|
|
signedTxn = txn.sign(sender.getPrivateKey())
|
|
client.send_transaction(signedTxn)
|
|
|
|
return response.applicationIndex
|
|
|
|
def account_exists(self, client, app_id, addr):
|
|
try:
|
|
ai = client.account_info(addr)
|
|
if "apps-local-state" not in ai:
|
|
return False
|
|
|
|
for app in ai["apps-local-state"]:
|
|
if app["id"] == app_id:
|
|
return True
|
|
except:
|
|
print("Failed to find account {}".format(addr))
|
|
return False
|
|
|
|
def optin(self, client, sender, app_id, idx, emitter, doCreate=True):
|
|
aa = decode_address(get_application_address(app_id)).hex()
|
|
|
|
lsa = self.tsig.populate(
|
|
{
|
|
"TMPL_APP_ID": app_id,
|
|
"TMPL_APP_ADDRESS": aa,
|
|
"TMPL_ADDR_IDX": idx,
|
|
"TMPL_EMITTER_ID": emitter,
|
|
}
|
|
)
|
|
|
|
sig_addr = lsa.address()
|
|
|
|
if sig_addr not in self.cache and not self.account_exists(client, app_id, sig_addr):
|
|
if doCreate:
|
|
# pprint.pprint(("Creating", app_id, idx, emitter, sig_addr))
|
|
|
|
# Create it
|
|
sp = client.suggested_params()
|
|
|
|
seed_txn = transaction.PaymentTxn(sender = sender.getAddress(),
|
|
sp = sp,
|
|
receiver = sig_addr,
|
|
amt = self.seed_amt)
|
|
seed_txn.fee = seed_txn.fee * 2
|
|
|
|
optin_txn = transaction.ApplicationOptInTxn(sig_addr, sp, app_id, rekey_to=get_application_address(app_id))
|
|
optin_txn.fee = 0
|
|
|
|
transaction.assign_group_id([seed_txn, optin_txn])
|
|
|
|
signed_seed = seed_txn.sign(sender.getPrivateKey())
|
|
signed_optin = transaction.LogicSigTransaction(optin_txn, lsa)
|
|
|
|
client.send_transactions([signed_seed, signed_optin])
|
|
self.waitForTransaction(client, signed_optin.get_txid())
|
|
|
|
self.cache[sig_addr] = True
|
|
|
|
return sig_addr
|
|
|
|
def parseSeqFromLog(self, txn):
|
|
return int.from_bytes(b64decode(txn.innerTxns[0]["logs"][0]), "big")
|
|
|
|
def getCreator(self, client, sender, asset_id):
|
|
return client.asset_info(asset_id)["params"]["creator"]
|
|
|
|
def sendTxn(self, client, sender, txns, doWait):
|
|
transaction.assign_group_id(txns)
|
|
|
|
grp = []
|
|
pk = sender.getPrivateKey()
|
|
for t in txns:
|
|
grp.append(t.sign(pk))
|
|
|
|
client.send_transactions(grp)
|
|
if doWait:
|
|
return self.waitForTransaction(client, grp[-1].get_txid())
|
|
else:
|
|
return grp[-1].get_txid()
|
|
|
|
def bootGuardians(self, vaa, client, sender, coreid):
|
|
p = self.parseVAA(vaa)
|
|
if "NewGuardianSetIndex" not in p:
|
|
raise Exception("invalid guardian VAA")
|
|
|
|
seq_addr = self.optin(client, sender, coreid, int(p["sequence"] / max_bits), p["chainRaw"].hex() + p["emitter"].hex())
|
|
guardian_addr = self.optin(client, sender, coreid, p["index"], b"guardian".hex())
|
|
newguardian_addr = self.optin(client, sender, coreid, p["NewGuardianSetIndex"], b"guardian".hex())
|
|
|
|
# wormhole is not a cheap protocol... we need to buy ourselves
|
|
# some extra CPU cycles by having an early txn do nothing.
|
|
# This leaves cycles over for later txn's in the same group
|
|
|
|
sp = client.suggested_params()
|
|
|
|
txns = [
|
|
transaction.ApplicationCallTxn(
|
|
sender=sender.getAddress(),
|
|
index=coreid,
|
|
on_complete=transaction.OnComplete.NoOpOC,
|
|
app_args=[b"nop", b"0"],
|
|
sp=sp
|
|
),
|
|
|
|
transaction.ApplicationCallTxn(
|
|
sender=sender.getAddress(),
|
|
index=coreid,
|
|
on_complete=transaction.OnComplete.NoOpOC,
|
|
app_args=[b"nop", b"1"],
|
|
sp=sp
|
|
),
|
|
|
|
transaction.ApplicationCallTxn(
|
|
sender=sender.getAddress(),
|
|
index=coreid,
|
|
on_complete=transaction.OnComplete.NoOpOC,
|
|
app_args=[b"init", vaa, decode_address(self.vaa_verify["hash"])],
|
|
accounts=[seq_addr, guardian_addr, newguardian_addr],
|
|
sp=sp
|
|
),
|
|
|
|
transaction.PaymentTxn(
|
|
sender=sender.getAddress(),
|
|
receiver=self.vaa_verify["hash"],
|
|
amt=100000,
|
|
sp=sp
|
|
)
|
|
]
|
|
|
|
self.sendTxn(client, sender, txns, True)
|
|
|
|
def decodeLocalState(self, client, sender, appid, addr):
|
|
app_state = None
|
|
ai = client.account_info(addr)
|
|
for app in ai["apps-local-state"]:
|
|
if app["id"] == appid:
|
|
app_state = app["key-value"]
|
|
|
|
ret = b''
|
|
if None != app_state:
|
|
vals = {}
|
|
e = bytes.fromhex("00"*127)
|
|
for kv in app_state:
|
|
k = base64.b64decode(kv["key"])
|
|
if k == "meta":
|
|
continue
|
|
key = int.from_bytes(k, "big")
|
|
v = base64.b64decode(kv["value"]["bytes"])
|
|
if v != e:
|
|
vals[key] = v
|
|
for k in sorted(vals.keys()):
|
|
ret = ret + vals[k]
|
|
return ret
|
|
|
|
# There is no client side duplicate suppression, error checking, or validity
|
|
# checking. We need to be able to detect all failure cases in
|
|
# the contract itself and we want to use this to drive the failure test
|
|
# cases
|
|
|
|
def simpleVAA(self, vaa, client, sender, appid):
|
|
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")}
|
|
ret["signatures"] = vaa[6:(ret["siglen"] * 66) + 6]
|
|
ret["sigs"] = []
|
|
for i in range(ret["siglen"]):
|
|
ret["sigs"].append(vaa[(6 + (i * 66)):(6 + (i * 66)) + 66].hex())
|
|
off = (ret["siglen"] * 66) + 6
|
|
ret["digest"] = vaa[off:] # This is what is actually signed...
|
|
ret["timestamp"] = int.from_bytes(vaa[off:(off + 4)], "big")
|
|
off += 4
|
|
ret["nonce"] = int.from_bytes(vaa[off:(off + 4)], "big")
|
|
off += 4
|
|
ret["chainRaw"] = vaa[off:(off + 2)]
|
|
ret["chain"] = int.from_bytes(vaa[off:(off + 2)], "big")
|
|
off += 2
|
|
ret["emitter"] = vaa[off:(off + 32)]
|
|
off += 32
|
|
ret["sequence"] = int.from_bytes(vaa[off:(off + 8)], "big")
|
|
off += 8
|
|
ret["consistency"] = int.from_bytes(vaa[off:(off + 1)], "big")
|
|
off += 1
|
|
|
|
seq_addr = self.optin(client, sender, appid, int(p["sequence"] / max_bits), p["chainRaw"].hex() + p["emitter"].hex())
|
|
# And then the signatures to help us verify the vaa_s
|
|
guardian_addr = self.optin(client, sender, self.coreid, p["index"], b"guardian".hex())
|
|
|
|
accts = [seq_addr, guardian_addr]
|
|
|
|
keys = self.decodeLocalState(client, sender, self.coreid, guardian_addr)
|
|
|
|
sp = client.suggested_params()
|
|
|
|
txns = []
|
|
|
|
# Right now there is not really a good way to estimate the fees,
|
|
# in production, on a conjested network, how much verifying
|
|
# the signatures is going to cost.
|
|
|
|
# So, what we do instead
|
|
# is we top off the verifier back up to 2A so effectively we
|
|
# are paying for the previous persons overrage which on a
|
|
# unconjested network should be zero
|
|
|
|
pmt = 3000
|
|
bal = self.getBalances(client, self.vaa_verify["hash"])
|
|
if ((200000 - bal[0]) >= pmt):
|
|
pmt = 200000 - bal[0]
|
|
|
|
#print("Sending %d algo to cover fees" % (pmt))
|
|
txns.append(
|
|
transaction.PaymentTxn(
|
|
sender = sender.getAddress(),
|
|
sp = sp,
|
|
receiver = self.vaa_verify["hash"],
|
|
amt = pmt
|
|
)
|
|
)
|
|
|
|
# How many signatures can we process in a single txn... we can do 9!
|
|
bsize = (9*66)
|
|
blocks = int(len(p["signatures"]) / bsize) + 1
|
|
|
|
# We don't pass the entire payload in but instead just pass it pre digested. This gets around size
|
|
# limitations with lsigs AND reduces the cost of the entire operation on a conjested network by reducing the
|
|
# bytes passed into the transaction
|
|
digest = keccak.new(digest_bits=256).update(keccak.new(digest_bits=256).update(p["digest"]).digest()).digest()
|
|
|
|
for i in range(blocks):
|
|
# Which signatures will we be verifying in this block
|
|
sigs = p["signatures"][(i * bsize):]
|
|
if (len(sigs) > bsize):
|
|
sigs = sigs[:bsize]
|
|
# keys
|
|
kset = b''
|
|
# Grab the key associated the signature
|
|
for q in range(int(len(sigs) / 66)):
|
|
# Which guardian is this signature associated with
|
|
g = sigs[q * 66]
|
|
key = keys[((g * 20) + 1) : (((g + 1) * 20) + 1)]
|
|
kset = kset + key
|
|
|
|
txns.append(transaction.ApplicationCallTxn(
|
|
sender=self.vaa_verify["hash"],
|
|
index=self.coreid,
|
|
on_complete=transaction.OnComplete.NoOpOC,
|
|
app_args=[b"verifySigs", sigs, kset, digest],
|
|
accounts=accts,
|
|
sp=sp
|
|
))
|
|
|
|
txns.append(transaction.ApplicationCallTxn(
|
|
sender=sender.getAddress(),
|
|
index=self.coreid,
|
|
on_complete=transaction.OnComplete.NoOpOC,
|
|
app_args=[b"verifyVAA", vaa],
|
|
accounts=accts,
|
|
sp=sp
|
|
))
|
|
|
|
return txns
|
|
|
|
def signVAA(self, client, sender, txns):
|
|
transaction.assign_group_id(txns)
|
|
|
|
grp = []
|
|
pk = sender.getPrivateKey()
|
|
for t in txns:
|
|
if ("app_args" in t.__dict__ and len(t.app_args) > 0 and t.app_args[0] == b"verifySigs"):
|
|
grp.append(transaction.LogicSigTransaction(t, self.vaa_verify["lsig"]))
|
|
else:
|
|
grp.append(t.sign(pk))
|
|
|
|
client.send_transactions(grp)
|
|
ret = []
|
|
for x in grp:
|
|
response = self.waitForTransaction(client, x.get_txid())
|
|
if "logs" in response.__dict__ and len(response.__dict__["logs"]) > 0:
|
|
ret.append(response.__dict__["logs"])
|
|
return ret
|
|
|
|
def check_bits_set(self, client, app_id, addr, seq):
|
|
bits_set = {}
|
|
|
|
app_state = None
|
|
ai = client.account_info(addr)
|
|
for app in ai["apps-local-state"]:
|
|
if app["id"] == app_id:
|
|
app_state = app["key-value"]
|
|
if app_state == None:
|
|
return False
|
|
|
|
start = int(seq / max_bits) * max_bits
|
|
s = int((seq - start) / bits_per_key)
|
|
b = int(((seq - start) - (s * bits_per_key)) / 8)
|
|
|
|
k = base64.b64encode(s.to_bytes(1, "big")).decode('utf-8')
|
|
for kv in app_state:
|
|
if kv["key"] != k:
|
|
continue
|
|
v = base64.b64decode(kv["value"]["bytes"])
|
|
bt = 1 << (seq%8)
|
|
return ((v[b] & bt) != 0)
|
|
|
|
return False
|
|
|
|
def submitVAA(self, vaa, client, sender, appid):
|
|
# A lot of our logic here depends on parseVAA and knowing what the payload is..
|
|
p = self.parseVAA(vaa)
|
|
|
|
#pprint.pprint(p)
|
|
|
|
seq_addr = self.optin(client, sender, appid, int(p["sequence"] / max_bits), p["chainRaw"].hex() + p["emitter"].hex())
|
|
|
|
assert self.check_bits_set(client, appid, seq_addr, p["sequence"]) == False
|
|
# And then the signatures to help us verify the vaa_s
|
|
guardian_addr = self.optin(client, sender, self.coreid, p["index"], b"guardian".hex())
|
|
|
|
accts = [seq_addr, guardian_addr]
|
|
|
|
# If this happens to be setting up a new guardian set, we probably need it as well...
|
|
if p["Meta"] == "CoreGovernance" and p["action"] == 2:
|
|
newguardian_addr = self.optin(client, sender, self.coreid, p["NewGuardianSetIndex"], b"guardian".hex())
|
|
accts.append(newguardian_addr)
|
|
|
|
# When we attest for a new token, we need some place to store the info... later we will need to
|
|
# mirror the other way as well
|
|
if p["Meta"] == "TokenBridge Attest" or p["Meta"] == "TokenBridge Transfer" or p["Meta"] == "TokenBridge Transfer With Payload":
|
|
if p["FromChain"] != 8:
|
|
chain_addr = self.optin(client, sender, self.tokenid, p["FromChain"], p["Contract"])
|
|
else:
|
|
asset_id = int.from_bytes(bytes.fromhex(p["Contract"]), "big")
|
|
chain_addr = self.optin(client, sender, self.tokenid, asset_id, b"native".hex())
|
|
accts.append(chain_addr)
|
|
|
|
keys = self.decodeLocalState(client, sender, self.coreid, guardian_addr)
|
|
print("keys: " + keys.hex())
|
|
|
|
sp = client.suggested_params()
|
|
|
|
txns = []
|
|
|
|
# How many signatures can we process in a single txn... we can do 9!
|
|
bsize = (9*66)
|
|
blocks = int(len(p["signatures"]) / bsize) + 1
|
|
|
|
# We don't pass the entire payload in but instead just pass it pre digested. This gets around size
|
|
# limitations with lsigs AND reduces the cost of the entire operation on a conjested network by reducing the
|
|
# bytes passed into the transaction
|
|
digest = keccak.new(digest_bits=256).update(keccak.new(digest_bits=256).update(p["digest"]).digest()).digest()
|
|
|
|
for i in range(blocks):
|
|
# Which signatures will we be verifying in this block
|
|
sigs = p["signatures"][(i * bsize):]
|
|
if (len(sigs) > bsize):
|
|
sigs = sigs[:bsize]
|
|
# keys
|
|
kset = b''
|
|
# Grab the key associated the signature
|
|
for q in range(int(len(sigs) / 66)):
|
|
# Which guardian is this signature associated with
|
|
g = sigs[q * 66]
|
|
key = keys[((g * 20) + 1) : (((g + 1) * 20) + 1)]
|
|
kset = kset + key
|
|
|
|
txns.append(transaction.ApplicationCallTxn(
|
|
sender=self.vaa_verify["hash"],
|
|
index=self.coreid,
|
|
on_complete=transaction.OnComplete.NoOpOC,
|
|
app_args=[b"verifySigs", sigs, kset, digest],
|
|
accounts=accts,
|
|
sp=sp
|
|
))
|
|
txns[-1].fee = 0
|
|
|
|
txns.append(transaction.ApplicationCallTxn(
|
|
sender=sender.getAddress(),
|
|
index=self.coreid,
|
|
on_complete=transaction.OnComplete.NoOpOC,
|
|
app_args=[b"verifyVAA", vaa],
|
|
accounts=accts,
|
|
sp=sp
|
|
))
|
|
txns[-1].fee = txns[-1].fee * (1 + blocks)
|
|
|
|
if p["Meta"] == "CoreGovernance":
|
|
txns.append(transaction.ApplicationCallTxn(
|
|
sender=sender.getAddress(),
|
|
index=self.coreid,
|
|
on_complete=transaction.OnComplete.NoOpOC,
|
|
app_args=[b"governance", vaa],
|
|
accounts=accts,
|
|
sp=sp
|
|
))
|
|
txns.append(transaction.ApplicationCallTxn(
|
|
sender=sender.getAddress(),
|
|
index=self.coreid,
|
|
on_complete=transaction.OnComplete.NoOpOC,
|
|
app_args=[b"nop", 5],
|
|
sp=sp
|
|
))
|
|
|
|
if p["Meta"] == "TokenBridge RegisterChain" or p["Meta"] == "TokenBridge UpgradeContract":
|
|
txns.append(transaction.ApplicationCallTxn(
|
|
sender=sender.getAddress(),
|
|
index=self.tokenid,
|
|
on_complete=transaction.OnComplete.NoOpOC,
|
|
app_args=[b"governance", vaa],
|
|
accounts=accts,
|
|
foreign_apps = [self.coreid],
|
|
sp=sp
|
|
))
|
|
|
|
if p["Meta"] == "TokenBridge Attest":
|
|
# if we DO decode it, we can do a sanity check... of
|
|
# course, the hacker might NOT decode it so we have to
|
|
# handle both cases...
|
|
|
|
asset = (self.decodeLocalState(client, sender, self.tokenid, chain_addr))
|
|
foreign_assets = []
|
|
if (len(asset) > 8):
|
|
foreign_assets.append(int.from_bytes(asset[0:8], "big"))
|
|
|
|
txns.append(
|
|
transaction.PaymentTxn(
|
|
sender = sender.getAddress(),
|
|
sp = sp,
|
|
receiver = chain_addr,
|
|
amt = 100000
|
|
)
|
|
)
|
|
|
|
txns.append(transaction.ApplicationCallTxn(
|
|
sender=sender.getAddress(),
|
|
index=self.tokenid,
|
|
on_complete=transaction.OnComplete.NoOpOC,
|
|
app_args=[b"nop", 1],
|
|
sp=sp
|
|
))
|
|
|
|
txns.append(transaction.ApplicationCallTxn(
|
|
sender=sender.getAddress(),
|
|
index=self.tokenid,
|
|
on_complete=transaction.OnComplete.NoOpOC,
|
|
app_args=[b"nop", 2],
|
|
sp=sp
|
|
))
|
|
|
|
txns.append(transaction.ApplicationCallTxn(
|
|
sender=sender.getAddress(),
|
|
index=self.tokenid,
|
|
on_complete=transaction.OnComplete.NoOpOC,
|
|
app_args=[b"receiveAttest", vaa],
|
|
accounts=accts,
|
|
foreign_assets = foreign_assets,
|
|
sp=sp
|
|
))
|
|
txns[-1].fee = txns[-1].fee * 2
|
|
|
|
if p["Meta"] == "TokenBridge Transfer" or p["Meta"] == "TokenBridge Transfer With Payload":
|
|
foreign_assets = []
|
|
a = 0
|
|
if p["FromChain"] != 8:
|
|
asset = (self.decodeLocalState(client, sender, self.tokenid, chain_addr))
|
|
if (len(asset) > 8):
|
|
a = int.from_bytes(asset[0:8], "big")
|
|
else:
|
|
a = int.from_bytes(bytes.fromhex(p["Contract"]), "big")
|
|
|
|
# The receiver needs to be optin in to receive the coins... Yeah, the relayer pays for this
|
|
|
|
aid = 0
|
|
|
|
if p["ToChain"] == 8 and p["Type"] == 3:
|
|
aid = int.from_bytes(bytes.fromhex(p["ToAddress"]), "big")
|
|
addr = get_application_address(aid)
|
|
else:
|
|
addr = encode_address(bytes.fromhex(p["ToAddress"]))
|
|
|
|
if a != 0:
|
|
foreign_assets.append(a)
|
|
self.asset_optin(client, sender, foreign_assets[0], addr)
|
|
# And this is how the relayer gets paid...
|
|
if p["Fee"] != self.zeroPadBytes:
|
|
self.asset_optin(client, sender, foreign_assets[0], sender.getAddress())
|
|
|
|
accts.append(addr)
|
|
|
|
txns.append(transaction.ApplicationCallTxn(
|
|
sender=sender.getAddress(),
|
|
index=self.tokenid,
|
|
on_complete=transaction.OnComplete.NoOpOC,
|
|
app_args=[b"completeTransfer", vaa],
|
|
accounts=accts,
|
|
foreign_assets = foreign_assets,
|
|
sp=sp
|
|
))
|
|
|
|
if aid != 0:
|
|
txns[-1].foreign_apps = [aid]
|
|
|
|
# We need to cover the inner transactions
|
|
if p["Fee"] != self.zeroPadBytes:
|
|
txns[-1].fee = txns[-1].fee * 3
|
|
else:
|
|
txns[-1].fee = txns[-1].fee * 2
|
|
|
|
if p["Meta"] == "TokenBridge Transfer With Payload":
|
|
m = abi.Method("portal_transfer", [abi.Argument("byte[]")], abi.Returns("byte[]"))
|
|
txns.append(transaction.ApplicationCallTxn(
|
|
sender=sender.getAddress(),
|
|
index=int.from_bytes(bytes.fromhex(p["ToAddress"]), "big"),
|
|
on_complete=transaction.OnComplete.NoOpOC,
|
|
app_args=[m.get_selector(), m.args[0].type.encode(vaa)],
|
|
foreign_assets = foreign_assets,
|
|
sp=sp
|
|
))
|
|
|
|
transaction.assign_group_id(txns)
|
|
|
|
grp = []
|
|
pk = sender.getPrivateKey()
|
|
for t in txns:
|
|
if ("app_args" in t.__dict__ and len(t.app_args) > 0 and t.app_args[0] == b"verifySigs"):
|
|
grp.append(transaction.LogicSigTransaction(t, self.vaa_verify["lsig"]))
|
|
else:
|
|
grp.append(t.sign(pk))
|
|
|
|
client.send_transactions(grp)
|
|
ret = []
|
|
for x in grp:
|
|
response = self.waitForTransaction(client, x.get_txid())
|
|
if "logs" in response.__dict__ and len(response.__dict__["logs"]) > 0:
|
|
ret.append(response.__dict__["logs"])
|
|
|
|
assert self.check_bits_set(client, appid, seq_addr, p["sequence"]) == True
|
|
|
|
return ret
|
|
|
|
def parseVAA(self, vaa):
|
|
# print (vaa.hex())
|
|
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")}
|
|
ret["signatures"] = vaa[6:(ret["siglen"] * 66) + 6]
|
|
ret["sigs"] = []
|
|
for i in range(ret["siglen"]):
|
|
ret["sigs"].append(vaa[(6 + (i * 66)):(6 + (i * 66)) + 66].hex())
|
|
off = (ret["siglen"] * 66) + 6
|
|
ret["digest"] = vaa[off:] # This is what is actually signed...
|
|
ret["timestamp"] = int.from_bytes(vaa[off:(off + 4)], "big")
|
|
off += 4
|
|
ret["nonce"] = int.from_bytes(vaa[off:(off + 4)], "big")
|
|
off += 4
|
|
ret["chainRaw"] = vaa[off:(off + 2)]
|
|
ret["chain"] = int.from_bytes(vaa[off:(off + 2)], "big")
|
|
off += 2
|
|
ret["emitter"] = vaa[off:(off + 32)]
|
|
off += 32
|
|
ret["sequence"] = int.from_bytes(vaa[off:(off + 8)], "big")
|
|
off += 8
|
|
ret["consistency"] = int.from_bytes(vaa[off:(off + 1)], "big")
|
|
off += 1
|
|
|
|
ret["Meta"] = "Unknown"
|
|
|
|
if vaa[off:(off + 32)].hex() == "000000000000000000000000000000000000000000546f6b656e427269646765":
|
|
ret["Meta"] = "TokenBridge"
|
|
ret["module"] = vaa[off:(off + 32)].hex()
|
|
off += 32
|
|
ret["action"] = int.from_bytes(vaa[off:(off + 1)], "big")
|
|
off += 1
|
|
if ret["action"] == 1:
|
|
ret["Meta"] = "TokenBridge RegisterChain"
|
|
ret["targetChain"] = int.from_bytes(vaa[off:(off + 2)], "big")
|
|
off += 2
|
|
ret["EmitterChainID"] = int.from_bytes(vaa[off:(off + 2)], "big")
|
|
off += 2
|
|
ret["targetEmitter"] = vaa[off:(off + 32)].hex()
|
|
off += 32
|
|
if ret["action"] == 2:
|
|
ret["Meta"] = "TokenBridge UpgradeContract"
|
|
ret["targetChain"] = int.from_bytes(vaa[off:(off + 2)], "big")
|
|
off += 2
|
|
ret["newContract"] = vaa[off:(off + 32)].hex()
|
|
off += 32
|
|
pprint.pprint((vaa[off:(off + 32)].hex(), "00000000000000000000000000000000000000000000000000000000436f7265"))
|
|
if vaa[off:(off + 32)].hex() == "00000000000000000000000000000000000000000000000000000000436f7265":
|
|
ret["Meta"] = "CoreGovernance"
|
|
ret["module"] = vaa[off:(off + 32)].hex()
|
|
off += 32
|
|
ret["action"] = int.from_bytes(vaa[off:(off + 1)], "big")
|
|
off += 1
|
|
ret["targetChain"] = int.from_bytes(vaa[off:(off + 2)], "big")
|
|
off += 2
|
|
if ret["action"] == 2:
|
|
ret["NewGuardianSetIndex"] = int.from_bytes(vaa[off:(off + 4)], "big")
|
|
else:
|
|
ret["Contract"] = vaa[off:(off + 32)].hex()
|
|
|
|
if ((len(vaa[off:])) == 100) and int.from_bytes((vaa[off:off+1]), "big") == 2:
|
|
ret["Meta"] = "TokenBridge Attest"
|
|
ret["Type"] = int.from_bytes((vaa[off:off+1]), "big")
|
|
off += 1
|
|
ret["Contract"] = vaa[off:(off + 32)].hex()
|
|
off += 32
|
|
ret["FromChain"] = int.from_bytes(vaa[off:(off + 2)], "big")
|
|
off += 2
|
|
ret["Decimals"] = int.from_bytes((vaa[off:off+1]), "big")
|
|
off += 1
|
|
ret["Symbol"] = vaa[off:(off + 32)].hex()
|
|
off += 32
|
|
ret["Name"] = vaa[off:(off + 32)].hex()
|
|
|
|
if ((len(vaa[off:])) == 133) and int.from_bytes((vaa[off:off+1]), "big") == 1:
|
|
ret["Meta"] = "TokenBridge Transfer"
|
|
ret["Type"] = int.from_bytes((vaa[off:off+1]), "big")
|
|
off += 1
|
|
ret["Amount"] = vaa[off:(off + 32)].hex()
|
|
off += 32
|
|
ret["Contract"] = vaa[off:(off + 32)].hex()
|
|
off += 32
|
|
ret["FromChain"] = int.from_bytes(vaa[off:(off + 2)], "big")
|
|
off += 2
|
|
ret["ToAddress"] = vaa[off:(off + 32)].hex()
|
|
off += 32
|
|
ret["ToChain"] = int.from_bytes(vaa[off:(off + 2)], "big")
|
|
off += 2
|
|
ret["Fee"] = vaa[off:(off + 32)].hex()
|
|
|
|
if int.from_bytes((vaa[off:off+1]), "big") == 3:
|
|
ret["Meta"] = "TokenBridge Transfer With Payload"
|
|
ret["Type"] = int.from_bytes((vaa[off:off+1]), "big")
|
|
off += 1
|
|
ret["Amount"] = vaa[off:(off + 32)].hex()
|
|
off += 32
|
|
ret["Contract"] = vaa[off:(off + 32)].hex()
|
|
off += 32
|
|
ret["FromChain"] = int.from_bytes(vaa[off:(off + 2)], "big")
|
|
off += 2
|
|
ret["ToAddress"] = vaa[off:(off + 32)].hex()
|
|
off += 32
|
|
ret["ToChain"] = int.from_bytes(vaa[off:(off + 2)], "big")
|
|
off += 2
|
|
ret["Fee"] = self.zeroPadBytes;
|
|
ret["FromAddress"] = vaa[off:(off + 32)].hex()
|
|
off += 32
|
|
ret["Payload"] = vaa[off:].hex()
|
|
|
|
return ret
|
|
|
|
def boot(self):
|
|
print("")
|
|
|
|
print("Creating the PortalCore app")
|
|
self.coreid = self.createPortalCoreApp(client=self.client, sender=self.foundation)
|
|
pprint.pprint({"wormhole core": str(self.coreid), "address": get_application_address(self.coreid), "emitterAddress": decode_address(get_application_address(self.coreid)).hex()})
|
|
|
|
print("Create the token bridge")
|
|
self.tokenid = self.createTokenBridgeApp(self.client, self.foundation)
|
|
pprint.pprint({"token bridge": str(self.tokenid), "address": get_application_address(self.tokenid), "emitterAddress": decode_address(get_application_address(self.tokenid)).hex()})
|
|
|
|
if self.devnet or self.args.testnet:
|
|
|
|
if self.devnet:
|
|
print("Create test app")
|
|
self.testid = self.createTestApp(self.client, self.foundation)
|
|
pprint.pprint({"testapp": str(self.testid)})
|
|
|
|
suggestedParams = self.client.suggested_params()
|
|
fundingAccount = self.getGenesisAccounts()[0]
|
|
|
|
txns: List[transaction.Transaction] = []
|
|
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"
|
|
a = Account.FromMnemonic(wallet)
|
|
txns.append(
|
|
transaction.PaymentTxn(
|
|
sender=fundingAccount.getAddress(),
|
|
receiver=a.getAddress(),
|
|
amt=self.FUNDING_AMOUNT,
|
|
sp=suggestedParams,
|
|
)
|
|
)
|
|
txns = transaction.assign_group_id(txns)
|
|
signedTxns = [
|
|
txn.sign(fundingAccount.getPrivateKey()) for i, txn in enumerate(txns)
|
|
]
|
|
|
|
self.client.send_transactions(signedTxns)
|
|
print("Sent some ALGO to: " + wallet)
|
|
|
|
print("Creating a Token...")
|
|
txn = transaction.AssetConfigTxn(
|
|
sender=a.getAddress(),
|
|
sp=suggestedParams,
|
|
total=1000000,
|
|
default_frozen=False,
|
|
unit_name="NORIUM",
|
|
asset_name="ChuckNorium",
|
|
manager=a.getAddress(),
|
|
reserve=a.getAddress(),
|
|
freeze=a.getAddress(),
|
|
clawback=a.getAddress(),
|
|
strict_empty_address_check=False,
|
|
decimals=6)
|
|
stxn = txn.sign(a.getPrivateKey())
|
|
txid = self.client.send_transaction(stxn)
|
|
print("NORIUM creation transaction ID: {}".format(txid))
|
|
confirmed_txn = transaction.wait_for_confirmation(self.client, txid, 4)
|
|
print("TXID: ", txid)
|
|
print("Result confirmed in round: {}".format(confirmed_txn['confirmed-round']))
|
|
|
|
print("Creating an NFT...")
|
|
# JSON file
|
|
dir_path = os.path.dirname(os.path.realpath(__file__))
|
|
f = open (dir_path + 'cnNftMetadata.json', "r")
|
|
|
|
# Reading from file
|
|
metadataJSON = json.loads(f.read())
|
|
metadataStr = json.dumps(metadataJSON)
|
|
|
|
hash = hashlib.new("sha512_256")
|
|
hash.update(b"arc0003/amj")
|
|
hash.update(metadataStr.encode("utf-8"))
|
|
json_metadata_hash = hash.digest()
|
|
print("json_metadata_hash: ", hash.hexdigest())
|
|
|
|
# Create transaction
|
|
txn = transaction.AssetConfigTxn(
|
|
sender=a.getAddress(),
|
|
sp=suggestedParams,
|
|
total=1,
|
|
default_frozen=False,
|
|
unit_name="CNART",
|
|
asset_name="ChuckNoriumArtwork@arc3",
|
|
manager=a.getAddress(),
|
|
reserve=a.getAddress(),
|
|
freeze=a.getAddress(),
|
|
clawback=a.getAddress(),
|
|
strict_empty_address_check=False,
|
|
url="file://cnNftMetadata.json",
|
|
metadata_hash=json_metadata_hash,
|
|
decimals=0)
|
|
stxn = txn.sign(a.getPrivateKey())
|
|
txid = self.client.send_transaction(stxn)
|
|
print("NORIUM NFT creation transaction ID: {}".format(txid))
|
|
confirmed_txn = transaction.wait_for_confirmation(self.client, txid, 4)
|
|
print("TXID: ", txid)
|
|
print("Result confirmed in round: {}".format(confirmed_txn['confirmed-round']))
|
|
|
|
if exists(self.args.env):
|
|
if self.gt == None:
|
|
self.gt = GenTest(False)
|
|
|
|
with open(self.args.env, encoding = 'utf-8') as f:
|
|
for line in f:
|
|
e = line.rstrip('\n').split("=")
|
|
print(e)
|
|
if "TOKEN_BRIDGE" in e[0]:
|
|
v = bytes.fromhex(e[1])
|
|
self.submitVAA(v, self.client, self.foundation, self.tokenid)
|
|
if "INIT_SIGNERS_CSV" in e[0]:
|
|
self.gt.guardianKeys = e[1].split(",")
|
|
if "INIT_SIGNERS_KEYS_CSV" in e[0]:
|
|
print("bootstrapping the guardian set...")
|
|
self.gt.guardianPrivKeys = e[1].split(",")
|
|
seq = int(random.random() * (2**31))
|
|
bootVAA = self.gt.genGuardianSetUpgrade(self.gt.guardianPrivKeys, self.args.guardianSet, self.args.guardianSet, seq, seq)
|
|
self.bootGuardians(bytes.fromhex(bootVAA), self.client, self.foundation, self.coreid)
|
|
seq = int(random.random() * (2**31))
|
|
regChain = self.gt.genRegisterChain(self.gt.guardianPrivKeys, self.args.guardianSet, seq, seq, 8, decode_address(get_application_address(self.tokenid)).hex())
|
|
print("ALGO_TOKEN_BRIDGE_VAA=" + regChain)
|
|
# if self.args.env != ".env":
|
|
# v = bytes.fromhex(regChain)
|
|
# self.submitVAA(v, self.client, self.foundation, self.tokenid)
|
|
# print("We submitted it!")
|
|
|
|
def updateCore(self) -> None:
|
|
print("Updating the core contracts")
|
|
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)
|
|
|
|
# print(decode_address(clear["hash"]).hex())
|
|
# sys.exit(0)
|
|
|
|
txn = transaction.ApplicationUpdateTxn(
|
|
index=self.coreid,
|
|
sender=self.foundation.getAddress(),
|
|
approval_program=b64decode(approval["result"]),
|
|
clear_program=b64decode(clear["result"]),
|
|
app_args=[ ],
|
|
sp=self.client.suggested_params(),
|
|
)
|
|
|
|
signedTxn = txn.sign(self.foundation.getPrivateKey())
|
|
print("sending transaction")
|
|
self.client.send_transaction(signedTxn)
|
|
resp = self.waitForTransaction(self.client, signedTxn.get_txid())
|
|
for x in resp.__dict__["logs"]:
|
|
print(x.hex())
|
|
print("complete")
|
|
|
|
def updateToken(self) -> None:
|
|
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)
|
|
print("Updating the token contracts: " + str(len(b64decode(approval["result"]))))
|
|
|
|
txn = transaction.ApplicationUpdateTxn(
|
|
index=self.tokenid,
|
|
sender=self.foundation.getAddress(),
|
|
approval_program=b64decode(approval["result"]),
|
|
clear_program=b64decode(clear["result"]),
|
|
app_args=[ ],
|
|
sp=self.client.suggested_params(),
|
|
)
|
|
|
|
signedTxn = txn.sign(self.foundation.getPrivateKey())
|
|
print("sending transaction")
|
|
self.client.send_transaction(signedTxn)
|
|
resp = self.waitForTransaction(self.client, signedTxn.get_txid())
|
|
for x in resp.__dict__["logs"]:
|
|
print(x.hex())
|
|
print("complete")
|
|
|
|
def genTeal(self) -> None:
|
|
print((True, self.args.core_approve, self.args.core_clear, self.client, self.seed_amt, self.tsig, self.devnet or self.args.testnet))
|
|
approval, clear = getCoreContracts(True, 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)
|
|
print("Generating the teal for the core contracts")
|
|
approval, clear = get_token_bridge(True, 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)
|
|
print("Generating the teal for the token contracts: " + str(len(b64decode(approval["result"]))))
|
|
|
|
def testnet(self):
|
|
self.ALGOD_ADDRESS = self.args.algod_address = "https://testnet-api.algonode.cloud"
|
|
self.INDEXER_ADDRESS = self.args.algod_address = "https://testnet-idx.algonode.cloud"
|
|
self.coreid = self.args.coreid
|
|
self.tokenid = self.args.tokenid
|
|
|
|
def mainnet(self):
|
|
self.ALGOD_ADDRESS = self.args.algod_address = "https://mainnet-api.algonode.cloud"
|
|
self.INDEXER_ADDRESS = self.args.algod_address = "https://mainnet-idx.algonode.cloud"
|
|
self.coreid = self.args.coreid
|
|
self.tokenid = self.args.tokenid
|
|
|
|
def setup_args(self) -> None:
|
|
parser = argparse.ArgumentParser(description='algorand setup')
|
|
|
|
parser.add_argument('--algod_address', type=str, help='algod address (default: http://localhost:4001)',
|
|
default="http://localhost:4001")
|
|
parser.add_argument('--algod_token', type=str, help='algod access token',
|
|
default="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
|
|
parser.add_argument('--kmd_address', type=str, help='kmd wallet address (default: http://localhost:4002)',
|
|
default="http://localhost:4002")
|
|
parser.add_argument('--kmd_token', type=str, help='kmd wallet access token',
|
|
default="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
|
|
parser.add_argument('--kmd_name', type=str, help='kmd wallet name',
|
|
default="unencrypted-default-wallet")
|
|
parser.add_argument('--kmd_password', type=str, help='kmd wallet password', default="")
|
|
|
|
parser.add_argument('--mnemonic', type=str, help='account mnemonic', default="")
|
|
|
|
parser.add_argument('--core_approve', type=str, help='core approve teal', default="teal/core_approve.teal")
|
|
parser.add_argument('--core_clear', type=str, help='core clear teal', default="teal/core_clear.teal")
|
|
|
|
parser.add_argument('--token_approve', type=str, help='token approve teal', default="teal/token_approve.teal")
|
|
parser.add_argument('--token_clear', type=str, help='token clear teal', default="teal/token_clear.teal")
|
|
|
|
parser.add_argument('--coreid', type=int, help='core contract', default=4)
|
|
parser.add_argument('--tokenid', type=int, help='token bridge contract', default=6)
|
|
parser.add_argument('--devnet', action='store_true', help='setup devnet')
|
|
parser.add_argument('--boot', action='store_true', help='bootstrap')
|
|
parser.add_argument('--upgradePayload', action='store_true', help='gen the upgrade payload for the guardians to sign')
|
|
parser.add_argument('--vaa', type=str, help='Submit the supplied VAA', default="")
|
|
parser.add_argument('--env', type=str, help='deploying using the supplied .env file', default=".env")
|
|
parser.add_argument('--guardianSet', type=int, help='What guardianSet should I syntheticly create if needed', default=0)
|
|
parser.add_argument('--appid', type=str, help='The appid that the vaa submit is applied to', default="")
|
|
parser.add_argument('--submit', action='store_true', help='submit the synthetic vaas')
|
|
parser.add_argument('--updateCore', action='store_true', help='update the Core contracts')
|
|
parser.add_argument('--updateToken', action='store_true', help='update the Token contracts')
|
|
parser.add_argument('--upgradeVAA', action='store_true', help='generate a upgrade vaa for devnet')
|
|
parser.add_argument('--print', action='store_true', help='print')
|
|
parser.add_argument('--genParts', action='store_true', help='Get tssig parts')
|
|
parser.add_argument('--genTeal', action='store_true', help='Generate all the teal from the pyteal')
|
|
parser.add_argument('--fund', action='store_true', help='Generate some accounts and fund them')
|
|
parser.add_argument('--testnet', action='store_true', help='Connect to testnet')
|
|
parser.add_argument('--mainnet', action='store_true', help='Connect to mainnet')
|
|
|
|
args = parser.parse_args()
|
|
self.init(args)
|
|
|
|
self.devnet = args.devnet
|
|
|
|
def main(self) -> None:
|
|
self.setup_args()
|
|
|
|
args = self.args
|
|
|
|
if args.testnet:
|
|
self.testnet()
|
|
|
|
if args.mainnet:
|
|
self.mainnet()
|
|
|
|
self.client = self.getAlgodClient()
|
|
if self.devnet or self.args.testnet:
|
|
self.vaa_verify = self.client.compile(get_vaa_verify())
|
|
else:
|
|
c = AlgodClient("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "https://testnet-api.algonode.cloud")
|
|
self.vaa_verify = c.compile(get_vaa_verify())
|
|
|
|
self.vaa_verify["lsig"] = LogicSig(base64.b64decode(self.vaa_verify["result"]))
|
|
|
|
if args.genTeal or args.boot:
|
|
self.genTeal()
|
|
|
|
# Generate the upgrade payload we need the guardians to sign
|
|
if args.upgradePayload:
|
|
print(self.genUpgradePayload())
|
|
sys.exit(0)
|
|
|
|
# This breaks the tsig up into the various parts so that we
|
|
# can embed it into the Typescript code for reassembly
|
|
if args.genParts:
|
|
print("this.ALGO_VERIFY_HASH = \"%s\""%self.vaa_verify["hash"]);
|
|
print("this.ALGO_VERIFY = new Uint8Array([", end='')
|
|
for x in b64decode(self.vaa_verify["result"]):
|
|
print("%d, "%(x), end='')
|
|
print("])")
|
|
|
|
parts = [
|
|
self.tsig.get_bytecode_raw(0).hex(),
|
|
self.tsig.get_bytecode_raw(1).hex(),
|
|
self.tsig.get_bytecode_raw(2).hex(),
|
|
self.tsig.get_bytecode_raw(3).hex(),
|
|
self.tsig.get_bytecode_raw(4).hex()
|
|
]
|
|
|
|
pprint.pprint(parts)
|
|
sys.exit(0)
|
|
|
|
if args.mnemonic:
|
|
self.foundation = Account.FromMnemonic(args.mnemonic)
|
|
|
|
if args.devnet and self.foundation == None:
|
|
print("Generating the foundation account...")
|
|
self.foundation = self.getTemporaryAccount(self.client)
|
|
|
|
if self.args.fund:
|
|
sys.exit(0)
|
|
|
|
if self.foundation == None:
|
|
print("We dont have a account? Here is a random one I just made up...")
|
|
pk = account.generate_account()[0]
|
|
print(" pk: " + pk)
|
|
print(" address: " + account.address_from_private_key(pk))
|
|
print(" mnemonic: " + mnemonic.from_private_key(pk))
|
|
if args.testnet:
|
|
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)")
|
|
sys.exit(0)
|
|
|
|
bal = self.getBalances(self.client, self.foundation.addr)
|
|
print("foundation address " + self.foundation.addr + " (" + str(float(bal[0]) / 1000000.0) + " ALGO)")
|
|
if bal[0] < 10000000:
|
|
print("you need at least 10 ALGO to do darn near anything...")
|
|
sys.exit(0)
|
|
|
|
if args.upgradeVAA:
|
|
ret = self.devnetUpgradeVAA()
|
|
pprint.pprint(ret)
|
|
if (args.submit) :
|
|
print("submitting vaa to upgrade core")
|
|
self.submitVAA(bytes.fromhex(ret[0]), self.client, self.foundation, self.coreid)
|
|
print("submitting vaa to upgrade token")
|
|
self.submitVAA(bytes.fromhex(ret[1]), self.client, self.foundation, self.tokenid)
|
|
|
|
if args.boot:
|
|
self.boot()
|
|
|
|
if args.updateCore:
|
|
self.updateCore()
|
|
|
|
if args.updateToken:
|
|
self.updateToken()
|
|
|
|
if args.vaa:
|
|
if self.args.appid == "":
|
|
raise Exception("You need to specifiy the appid when you are submitting vaas")
|
|
vaa = bytes.fromhex(args.vaa)
|
|
pprint.pprint(self.parseVAA(vaa))
|
|
self.submitVAA(vaa, self.client, self.foundation, int(self.args.appid))
|
|
|
|
if __name__ == "__main__":
|
|
core = PortalCore()
|
|
core.main()
|