wormhole/algorand/local_blob.py

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(),
)