213 lines
6.2 KiB
Python
213 lines
6.2 KiB
Python
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(),
|
|
)
|