TmplSig.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. from time import time, sleep
  2. from typing import List, Tuple, Dict, Any, Optional, Union
  3. from base64 import b64decode
  4. import base64
  5. import random
  6. import hashlib
  7. import uuid
  8. import sys
  9. import json
  10. import uvarint
  11. import pprint
  12. from local_blob import LocalBlob
  13. from algosdk.v2client.algod import AlgodClient
  14. from algosdk.kmd import KMDClient
  15. from algosdk import account, mnemonic
  16. from algosdk.encoding import decode_address
  17. from algosdk.future import transaction
  18. from pyteal import compileTeal, Mode, Expr
  19. from pyteal import *
  20. from algosdk.logic import get_application_address
  21. from algosdk.future.transaction import LogicSigAccount
  22. class TmplSig:
  23. """KeySig class reads in a json map containing assembly details of a template smart signature and allows you to populate it with the variables
  24. In this case we are only interested in a single variable, the key which is a byte string to make the address unique.
  25. In this demo we're using random strings but in practice you can choose something meaningful to your application
  26. """
  27. def __init__(self, name):
  28. # Read the source map
  29. # with open("{}.json".format(name)) as f:
  30. # self.map = json.loads(f.read())
  31. self.map = {"name":"lsig.teal","version":6,"source":"","bytecode":"BiABAYEASIAASDEQgQYSRDEZIhJEMRiBABJEMSCAABJEMQGBABJEMQkyAxJEMRUyAxJEIg==",
  32. "template_labels":{
  33. "TMPL_ADDR_IDX":{"source_line":3,"position":5,"bytes":False},
  34. "TMPL_EMITTER_ID":{"source_line":5,"position":8,"bytes":True},
  35. "TMPL_APP_ID":{"source_line":16,"position":24,"bytes":False},
  36. "TMPL_APP_ADDRESS":{"source_line":20,"position":30,"bytes":True}
  37. },
  38. "label_map":{},"line_map":[0,1,4,6,7,9,10,12,14,15,16,18,19,20,21,23,25,26,27,29,31,32,33,35,37,38,39,41,43,44,45,47,49,50,51]
  39. }
  40. self.src = base64.b64decode(self.map["bytecode"])
  41. self.sorted = dict(
  42. sorted(
  43. self.map["template_labels"].items(),
  44. key=lambda item: item[1]["position"],
  45. )
  46. )
  47. def populate(self, values: Dict[str, Union[str, int]]) -> LogicSigAccount:
  48. """populate uses the map to fill in the variable of the bytecode and returns a logic sig with the populated bytecode"""
  49. # Get the template source
  50. contract = list(base64.b64decode(self.map["bytecode"]))
  51. shift = 0
  52. for k, v in self.sorted.items():
  53. if k in values:
  54. pos = v["position"] + shift
  55. if v["bytes"]:
  56. val = bytes.fromhex(values[k])
  57. lbyte = uvarint.encode(len(val))
  58. # -1 to account for the existing 00 byte for length
  59. shift += (len(lbyte) - 1) + len(val)
  60. # +1 to overwrite the existing 00 byte for length
  61. contract[pos : pos + 1] = lbyte + val
  62. else:
  63. val = uvarint.encode(values[k])
  64. # -1 to account for existing 00 byte
  65. shift += len(val) - 1
  66. # +1 to overwrite existing 00 byte
  67. contract[pos : pos + 1] = val
  68. # Create a new LogicSigAccount given the populated bytecode,
  69. #pprint.pprint({"values": values, "contract": bytes(contract).hex()})
  70. return LogicSigAccount(bytes(contract))
  71. def get_bytecode_chunk(self, idx: int) -> Bytes:
  72. start = 0
  73. if idx > 0:
  74. start = list(self.sorted.values())[idx - 1]["position"] + 1
  75. stop = len(self.src)
  76. if idx < len(self.sorted):
  77. stop = list(self.sorted.values())[idx]["position"]
  78. chunk = self.src[start:stop]
  79. return Bytes(chunk)
  80. def get_bytecode_raw(self, idx: int):
  81. start = 0
  82. if idx > 0:
  83. start = list(self.sorted.values())[idx - 1]["position"] + 1
  84. stop = len(self.src)
  85. if idx < len(self.sorted):
  86. stop = list(self.sorted.values())[idx]["position"]
  87. chunk = self.src[start:stop]
  88. return chunk
  89. def get_sig_tmpl(self):
  90. def sig_tmpl():
  91. admin_app_id = ScratchVar()
  92. admin_address= ScratchVar()
  93. return Seq(
  94. # Just putting adding this as a tmpl var to make the address unique and deterministic
  95. # We don't actually care what the value is, pop it
  96. Pop(Tmpl.Int("TMPL_ADDR_IDX")),
  97. Pop(Tmpl.Bytes("TMPL_EMITTER_ID")),
  98. Assert(Txn.type_enum() == TxnType.ApplicationCall),
  99. Assert(Txn.on_completion() == OnComplete.OptIn),
  100. Assert(Txn.application_id() == Tmpl.Int("TMPL_APP_ID")),
  101. Assert(Txn.rekey_to() == Tmpl.Bytes("TMPL_APP_ADDRESS")),
  102. Assert(Txn.fee() == Int(0)),
  103. Assert(Txn.close_remainder_to() == Global.zero_address()),
  104. Assert(Txn.asset_close_to() == Global.zero_address()),
  105. Approve()
  106. )
  107. return compileTeal(sig_tmpl(), mode=Mode.Signature, version=6, assembleConstants=True)
  108. if __name__ == '__main__':
  109. core = TmplSig("sig")
  110. # client = AlgodClient("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "http://localhost:4001")
  111. # pprint.pprint(client.compile( core.get_sig_tmpl()))
  112. with open("sig.tmpl.teal", "w") as f:
  113. f.write(core.get_sig_tmpl())