| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212 |
- from typing import Tuple
- from pyteal import (
- And,
- App,
- Assert,
- Bytes,
- BytesZero,
- Concat,
- Expr,
- Extract,
- For,
- GetByte,
- If,
- Int,
- Itob,
- Len,
- Or,
- ScratchVar,
- Seq,
- SetByte,
- Subroutine,
- Substring,
- TealType,
- )
- _max_keys = 15
- _page_size = 128 - 1 # need 1 byte for key
- _max_bytes = _max_keys * _page_size
- _max_bits = _max_bytes * 8
- max_keys = Int(_max_keys)
- page_size = Int(_page_size)
- max_bytes = Int(_max_bytes)
- def _key_and_offset(idx: Int) -> Tuple[Int, Int]:
- return idx / page_size, idx % page_size
- @Subroutine(TealType.bytes)
- def intkey(i: Expr) -> Expr:
- return Extract(Itob(i), Int(7), Int(1))
- # TODO: Add Keyspace range?
- class LocalBlob:
- """
- Blob is a class holding static methods to work with the local storage of an account as a binary large object
- The `zero` method must be called on an account on opt in and the schema of the local storage should be 16 bytes
- """
- @staticmethod
- @Subroutine(TealType.none)
- def zero(acct: Expr) -> Expr:
- """
- initializes local state of an account to all zero bytes
- This allows us to be lazy later and _assume_ all the strings are the same size
- """
- i = ScratchVar()
- init = i.store(Int(0))
- cond = i.load() < max_keys
- iter = i.store(i.load() + Int(1))
- return For(init, cond, iter).Do(
- App.localPut(acct, intkey(i.load()), BytesZero(page_size))
- )
- @staticmethod
- @Subroutine(TealType.uint64)
- def get_byte(acct: Expr, idx: Expr):
- """
- Get a single byte from local storage of an account by index
- """
- key, offset = _key_and_offset(idx)
- return GetByte(App.localGet(acct, intkey(key)), offset)
- @staticmethod
- @Subroutine(TealType.none)
- def set_byte(acct: Expr, idx: Expr, byte: Expr):
- """
- Set a single byte from local storage of an account by index
- """
- key, offset = _key_and_offset(idx)
- return App.localPut(
- acct, intkey(key), SetByte(App.localGet(acct, intkey(key)), offset, byte)
- )
- @staticmethod
- @Subroutine(TealType.bytes)
- def read(
- acct: Expr, bstart: Expr, bend: Expr
- ) -> Expr:
- """
- read bytes between bstart and bend from local storage of an account by index
- """
- start_key, start_offset = _key_and_offset(bstart)
- stop_key, stop_offset = _key_and_offset(bend)
- key = ScratchVar()
- buff = ScratchVar()
- start = ScratchVar()
- stop = ScratchVar()
- init = key.store(start_key)
- cond = key.load() <= stop_key
- incr = key.store(key.load() + Int(1))
- return Seq(
- buff.store(Bytes("")),
- For(init, cond, incr).Do(
- Seq(
- start.store(If(key.load() == start_key, start_offset, Int(0))),
- stop.store(If(key.load() == stop_key, stop_offset, page_size)),
- buff.store(
- Concat(
- buff.load(),
- Substring(
- App.localGet(acct, intkey(key.load())),
- start.load(),
- stop.load(),
- ),
- )
- ),
- )
- ),
- buff.load(),
- )
- @staticmethod
- @Subroutine(TealType.none)
- def meta(
- acct: Expr, val: Expr
- ):
- return Seq(
- App.localPut(acct, Bytes("meta"), val)
- )
- @staticmethod
- @Subroutine(TealType.none)
- def checkMeta(acct: Expr, val: Expr):
- return Seq(Assert(And(App.localGet(acct, Bytes("meta")) == val, Int(145))))
- @staticmethod
- @Subroutine(TealType.uint64)
- def write(
- acct: Expr, bstart: Expr, buff: Expr
- ) -> Expr:
- """
- write bytes between bstart and len(buff) to local storage of an account
- """
- start_key, start_offset = _key_and_offset(bstart)
- stop_key, stop_offset = _key_and_offset(bstart + Len(buff))
- key = ScratchVar()
- start = ScratchVar()
- stop = ScratchVar()
- written = ScratchVar()
- init = key.store(start_key)
- cond = key.load() <= stop_key
- incr = key.store(key.load() + Int(1))
- delta = ScratchVar()
- return Seq(
- written.store(Int(0)),
- For(init, cond, incr).Do(
- Seq(
- start.store(If(key.load() == start_key, start_offset, Int(0))),
- stop.store(If(key.load() == stop_key, stop_offset, page_size)),
- App.localPut(
- acct,
- intkey(key.load()),
- If(
- Or(stop.load() != page_size, start.load() != Int(0))
- ) # Its a partial write
- .Then(
- Seq(
- delta.store(stop.load() - start.load()),
- Concat(
- Substring(
- App.localGet(acct, intkey(key.load())),
- Int(0),
- start.load(),
- ),
- Extract(buff, written.load(), delta.load()),
- Substring(
- App.localGet(acct, intkey(key.load())),
- stop.load(),
- page_size,
- ),
- ),
- )
- )
- .Else(
- Seq(
- delta.store(page_size),
- Extract(buff, written.load(), page_size),
- )
- ),
- ),
- written.store(written.load() + delta.load()),
- )
- ),
- written.load(),
- )
|