local_blob.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. from typing import Tuple
  2. from pyteal import (
  3. And,
  4. App,
  5. Assert,
  6. Bytes,
  7. BytesZero,
  8. Concat,
  9. Expr,
  10. Extract,
  11. For,
  12. GetByte,
  13. If,
  14. Int,
  15. Itob,
  16. Len,
  17. Or,
  18. ScratchVar,
  19. Seq,
  20. SetByte,
  21. Subroutine,
  22. Substring,
  23. TealType,
  24. )
  25. _max_keys = 15
  26. _page_size = 128 - 1 # need 1 byte for key
  27. _max_bytes = _max_keys * _page_size
  28. _max_bits = _max_bytes * 8
  29. max_keys = Int(_max_keys)
  30. page_size = Int(_page_size)
  31. max_bytes = Int(_max_bytes)
  32. def _key_and_offset(idx: Int) -> Tuple[Int, Int]:
  33. return idx / page_size, idx % page_size
  34. @Subroutine(TealType.bytes)
  35. def intkey(i: Expr) -> Expr:
  36. return Extract(Itob(i), Int(7), Int(1))
  37. # TODO: Add Keyspace range?
  38. class LocalBlob:
  39. """
  40. Blob is a class holding static methods to work with the local storage of an account as a binary large object
  41. The `zero` method must be called on an account on opt in and the schema of the local storage should be 16 bytes
  42. """
  43. @staticmethod
  44. @Subroutine(TealType.none)
  45. def zero(acct: Expr) -> Expr:
  46. """
  47. initializes local state of an account to all zero bytes
  48. This allows us to be lazy later and _assume_ all the strings are the same size
  49. """
  50. i = ScratchVar()
  51. init = i.store(Int(0))
  52. cond = i.load() < max_keys
  53. iter = i.store(i.load() + Int(1))
  54. return For(init, cond, iter).Do(
  55. App.localPut(acct, intkey(i.load()), BytesZero(page_size))
  56. )
  57. @staticmethod
  58. @Subroutine(TealType.uint64)
  59. def get_byte(acct: Expr, idx: Expr):
  60. """
  61. Get a single byte from local storage of an account by index
  62. """
  63. key, offset = _key_and_offset(idx)
  64. return GetByte(App.localGet(acct, intkey(key)), offset)
  65. @staticmethod
  66. @Subroutine(TealType.none)
  67. def set_byte(acct: Expr, idx: Expr, byte: Expr):
  68. """
  69. Set a single byte from local storage of an account by index
  70. """
  71. key, offset = _key_and_offset(idx)
  72. return App.localPut(
  73. acct, intkey(key), SetByte(App.localGet(acct, intkey(key)), offset, byte)
  74. )
  75. @staticmethod
  76. @Subroutine(TealType.bytes)
  77. def read(
  78. acct: Expr, bstart: Expr, bend: Expr
  79. ) -> Expr:
  80. """
  81. read bytes between bstart and bend from local storage of an account by index
  82. """
  83. start_key, start_offset = _key_and_offset(bstart)
  84. stop_key, stop_offset = _key_and_offset(bend)
  85. key = ScratchVar()
  86. buff = ScratchVar()
  87. start = ScratchVar()
  88. stop = ScratchVar()
  89. init = key.store(start_key)
  90. cond = key.load() <= stop_key
  91. incr = key.store(key.load() + Int(1))
  92. return Seq(
  93. buff.store(Bytes("")),
  94. For(init, cond, incr).Do(
  95. Seq(
  96. start.store(If(key.load() == start_key, start_offset, Int(0))),
  97. stop.store(If(key.load() == stop_key, stop_offset, page_size)),
  98. buff.store(
  99. Concat(
  100. buff.load(),
  101. Substring(
  102. App.localGet(acct, intkey(key.load())),
  103. start.load(),
  104. stop.load(),
  105. ),
  106. )
  107. ),
  108. )
  109. ),
  110. buff.load(),
  111. )
  112. @staticmethod
  113. @Subroutine(TealType.none)
  114. def meta(
  115. acct: Expr, val: Expr
  116. ):
  117. return Seq(
  118. App.localPut(acct, Bytes("meta"), val)
  119. )
  120. @staticmethod
  121. @Subroutine(TealType.none)
  122. def checkMeta(acct: Expr, val: Expr):
  123. return Seq(Assert(And(App.localGet(acct, Bytes("meta")) == val, Int(145))))
  124. @staticmethod
  125. @Subroutine(TealType.uint64)
  126. def write(
  127. acct: Expr, bstart: Expr, buff: Expr
  128. ) -> Expr:
  129. """
  130. write bytes between bstart and len(buff) to local storage of an account
  131. """
  132. start_key, start_offset = _key_and_offset(bstart)
  133. stop_key, stop_offset = _key_and_offset(bstart + Len(buff))
  134. key = ScratchVar()
  135. start = ScratchVar()
  136. stop = ScratchVar()
  137. written = ScratchVar()
  138. init = key.store(start_key)
  139. cond = key.load() <= stop_key
  140. incr = key.store(key.load() + Int(1))
  141. delta = ScratchVar()
  142. return Seq(
  143. written.store(Int(0)),
  144. For(init, cond, incr).Do(
  145. Seq(
  146. start.store(If(key.load() == start_key, start_offset, Int(0))),
  147. stop.store(If(key.load() == stop_key, stop_offset, page_size)),
  148. App.localPut(
  149. acct,
  150. intkey(key.load()),
  151. If(
  152. Or(stop.load() != page_size, start.load() != Int(0))
  153. ) # Its a partial write
  154. .Then(
  155. Seq(
  156. delta.store(stop.load() - start.load()),
  157. Concat(
  158. Substring(
  159. App.localGet(acct, intkey(key.load())),
  160. Int(0),
  161. start.load(),
  162. ),
  163. Extract(buff, written.load(), delta.load()),
  164. Substring(
  165. App.localGet(acct, intkey(key.load())),
  166. stop.load(),
  167. page_size,
  168. ),
  169. ),
  170. )
  171. )
  172. .Else(
  173. Seq(
  174. delta.store(page_size),
  175. Extract(buff, written.load(), page_size),
  176. )
  177. ),
  178. ),
  179. written.store(written.load() + delta.load()),
  180. )
  181. ),
  182. written.load(),
  183. )