Algorand Smart Contract
Ngôn ngữ lập trình Smart Contract trên Algorand: Teal → PyTeal → Beaker
Application Class
Algorand thì thay vì gọi Smart Contract, họ gọi là Application
Khởi tạo một application
app = beaker.Application("hello_world")
Apply()
Dùng để add các config lên application - ví dụ mình muốn khởi tạo app với các local state mình define ra thì có thể làm như sau
from beaker import Application, GlobalStateValue, unconditional_create_approval
from pyteal import Bytes, Expr, TealType, abi, Int # Define Global State
class AppState: app_state = GlobalStateValue( stack_type=TealType.uint64, default=Int(10), ) # Create Application and Add Global State when Init Application
app = Application("SimpleApp", state=AppState()).apply( unconditional_create_approval, initialize_global_state=True
) # Read Global State
@app.external
def read_state(*, output: abi.Uint64) -> Expr: return output.set(app.state.app_state)
Global State
Có 3 loại Global State
- Global State Value → hold 1 value
- Reserve Global State → key pair
- Global State Blob → sử dụng cho complex data
Global State Value
Dùng để lưu 1 giá trị trên Smart Contract
Để khởi tạo Global State Value, mình tạo một class, sau đó config các thông số cho Global State
- stack_type: Kiểu dữ liệu
- TealType.bytes
- TealTypes.uint64
- default: Giá trị mặc định khi khởi tạo
- static
- True: Biến này sẽ không đổi (const)
- False: Biến này sẽ có thể đổi
Để thay đổi giá trị cho Global State, mình sử dụng hàm set()
- eg: app.state.my_description.set(Bytes(”hello))
Để lấy giá trị của Global State, mình sử dụng hàm set() cho output
- eg: output.set(app.state.my_description)
from beaker import Application, GlobalStateValue, unconditional_create_approval
from pyteal import Bytes, Expr, TealType, abi, Int # Define Global State
class GlobalState: my_description = GlobalStateValue( # Define Type stack_type = TealType.bytes, # Set Value default = Bytes("Henry is the best!"), # Static True mean this Variable will not be changed (const) static = False,
) # Create Application
app = Application("GlobalStateValue",state=GlobalState()).apply( unconditional_create_approval, initialize_global_state = True
) # Set new value for state
@app.external
def set_app_state_val(v: abi.String) -> Expr: return app.state.my_description.set(v.get()) # Get State Value
@app.external
def get_app_state_val(*, output: abi.String) -> Expr: return output.set(app.state.my_description)
Global State Blob
Tương tự với Global State, Global State Blob lưu trên Smart Contract.
Khác với Global State, Global State Blob lưu được nhiều data hơn (32KB data) so với Global State (64 bytes) Global State Blob chỉ lưu dạng dữ liệu Bytes
Global State lưu 1 dữ liệu, còn Global State Blob lưu complex data như array hay maps
Để sử dụng Global State Blob, đầu tiên mình cần define có bao nhiêu keys. (mapping)
Sử dụng app.state.global_blob.write() để gán dữ liệu
- Truyền bao nhiêu argument phụ thuộc vào khi nãy bạn config bao nhiêu keys
Sử dụng app.state.global_blob.read() để đọc dữ liệu
- Truyền 2 tham số, lấy dữ liệu từ số thứ tự bao nhiêu, đến số thứ tự bao nhiêu
- Để lấy số lượng các phần tử có trong Global State Blob, mình sử dụng app.state.global_blob.blob.max_bytes - Int(1)
from beaker import Application, GlobalStateBlob, unconditional_create_approval
from pyteal import Bytes, Expr, TealType, abi, Int # Define Global Blob State
class AppState: global_blob = GlobalStateBlob( keys=2, ) # Create Application
app = Application("Global Blob App", state=AppState()).apply( unconditional_create_approval, initialize_global_state=True
) # Set new value for state
@app.external
def write_app_blob(start: abi.Uint64, v: abi.String) -> Expr: return app.state.global_blob.write(start.get(), v.get()) # Get State Value
@app.external
def get_app_state_val(*, output: abi.String) -> Expr: return output.set( app.state.global_blob.read( Int(0), app.state.global_blob.blob.max_bytes - Int(1) ) )
Local State
Local State lưu dữ liệu theo từng ví
Local State có 3 loại (giống Global State)
- Local State Value
- Reserve Local State
- Local State Blob
Note: User có quyền clear Local State nên anh em becareful khi sử dụng
Local State Value
from beaker import Application, LocalStateValue, unconditional_opt_in_approval
from pyteal import Expr, Int, TealType, Txn, abi # Create Local State
class LocalState: count = LocalStateValue( stack_type=TealType.uint64, default=Int(1), descr="A Counter that keeps track of counts.", ) # Create Application
app = Application("Local State App", state=LocalState()).apply( unconditional_opt_in_approval, initialize_local_state=True
) # Increase Count
@app.external
def incr_local_state(v: abi.Uint64) -> Expr: return app.state.count[Txn.sender()].increment(v.get()) # Read Count
@app.external(read_only=True)
def get_local_state(*, output: abi.Uint64) -> Expr: return output.set(app.state.count[Txn.sender()])
Reserved Local State
from beaker import Application, ReservedLocalStateValue, unconditional_opt_in_approval
from pyteal import Expr, TealType, Txn, abi class ReservedLocalState: favorite_food = ReservedLocalStateValue( stack_type = TealType.bytes, max_keys = 8, descr = "8 key-value pairs of favorite foods ranked from 1 to 8.", ) app = Application("Reserved Local App", state = ReservedLocalState()).apply( unconditional_opt_in_approval, initialize_local_state = True
) @app.external
def set_reserved_local_state_val(k: abi.Uint8, v: abi.String) -> Expr: return app.state.favorite_food[k][Txn.sender()].set(v.get()) @app.external
def get_reserved_local_state_val(k: abi.Uint8, *, output: abi.String) -> Expr: return output.set(app.state.favorite_food[k][Txn.sender()])
Local Blob
Nếu truyền ví là ví call thì [Txn.sender()] có thể bỏ
from beaker import Application, LocalStateBlob, unconditional_opt_in_approval
from pyteal import Expr, Int, abi class LocalBlob: local_blob = LocalStateBlob(keys=2) app = Application("Local Blob App", state = LocalBlob()).apply( unconditional_opt_in_approval, initialize_local_state = True
) @app.external
def write_local_blob(v: abi.String) -> Expr: return app.state.local_blob.write(Int(0), v.get()) @app.external
def read_local_blob(*, output: abi.String) -> Expr: return output.set( app.state.local_blob.read(Int(0),app.state.local_blob.blob.max_bytes - Int(1)) )
Decorators
External
@app.external @app.external(read_only=True)
from beaker import Application
from pyteal import Expr, abi app = Application("External Example App") @app.external
def add(a: abi.Uint8, b: abi.Uint8, * , output: abi.Uint8) -> Expr: return output.set(a.get() +b.get())
Authorization
# Require only call by creator of this smart contract
@app.external(authorize = Authorize.only(Global.creator_address()))
def withdraw() -> Expr: return ....
# Require address hold token 123
@app.external(authorize = Authorize.holds_token(asset_id=123))
# Require address opted in this smart contract
@app.external(authorize = Authorize.opted_in())
OnComplete Decorators
# opted in account and execute function
@app.opt_in
@app.update
@app.close_out
@app.clear_state
@app.delete
from beaker import Application, Authorize
from pyteal import Approve, Expr, Global, Reject app = Application("OnComplete App") @app.opt_in
def opt_in() -> Expr: return Approve() @app.close_out
def close_out() -> Expr: return Reject() @app.clear_state
def clear_state() -> Expr: return Approve() @app.update
def update() -> Expr: return Approve() @app.delete(authorize = Authorize.only(Global.creator_address()))
def delete() -> Expr: return Approve()
Internal
Dùng để call các hàm trong smart contract nội bộ, ngoài không call vào được
Dùng để xử lý các complex logic
@Subroutine(TealType.uint64)
def internal_magic(*, output: abi.String) -> Expr: return ...
from beaker import Application
from pyteal import Expr, Subroutine, TealType, abi app = Application("Internal Subroutine Example App") @app.external
def add(a: abi.Uint8, b: abi.Uint8, * , output: abi.Uint8) -> Expr: return output.set(internal_add(a,b)) @Subroutine(TealType.uint64)
def internal_add(a: abi.Uint8, b: abi.Uint8) -> Expr: return a.get() + b.get()
Box Storage
aka Infinite State Made Easy
- BoxMapping → mapping of key value pairs
- BoxList→ store a list in a box
Box Mapping
from beaker import *
from pyteal import * from beaker.lib.storage import BoxMapping # Our customer Struct
class GroceryItem(abi.NamedTuple): item: abi.Field[abi.String] purchased: abi.Field[abi.Bool] class GroceryStates: grocery_item = BoxMapping(abi.String, GroceryItem) app = Application("Grocery Checklist with bearker", state=GroceryStates()) ### Add Grocery with Boxed ###
@app.external
def addGrocery(item_name: abi.String) -> Expr: purchased = abi.Bool() grocery_tuple = GroceryItem() return Seq( purchased.set(Int(0)), grocery_tuple.set(item_name, purchased), app.state.grocery_item[item_name.get()].set(grocery_tuple), ) ### update Grocery Item ###
@app.external
def updatePurchased(item_name: abi.String, *, output: GroceryItem) -> Expr: existing_grocery_item = GroceryItem() new_purchased = abi.Bool() return Seq( existing_grocery_item.decode(app.state.grocery_item[item_name.get()].get()), new_purchased.set(Int(1)), existing_grocery_item.set(item_name, new_purchased), app.state.grocery_item[item_name.get()].set(existing_grocery_item), app.state.grocery_item[item_name.get()].store_into(output), ) ### Read Grocery Item ###
@app.external
def readItem(item_name: abi.String, *, output: GroceryItem) -> Expr: return app.state.grocery_item[item_name.get()].store_into(output) ### Delete ###
@app.external
def deleteGrocery(item_name: abi.String) -> Expr: return Pop(app.state.grocery_item[item_name.get()].delete())
BoxList
from beaker import *
from pyteal import * from beaker.lib.storage import BoxList class SubscriberStates: idx = GlobalStateValue( stack_type=TealType.uint64, default=Int(0), descr="number of subscribers" ) addr_list = BoxList(abi.Address, 10) app = Application("Subscriber Count App", state=SubscriberStates()) ### Create Box List and Initalize Global state ###
@app.external
def bootstrap() -> Expr: return Seq( Pop(app.state.addr_list.create()), # Create a Box with name "addr_list" app.initialize_global_state(), ) ### Subscribe ###
@app.external
def subscribe(addr: abi.Address) -> Expr: return Seq(app.state.addr_list[app.state.idx].set(addr), app.state.idx.increment()) ### Read Subscriber ###
@app.external
def readSubscriber(idx: abi.Uint32, *, output: abi.Address) -> Expr: return app.state.addr_list[idx.get()].store_into(output) ### Delete ###
@app.external(authorize=Authorize.only(Global.creator_address()))
def deleteBox() -> Expr: return Assert(App.box_delete(Bytes("addr_list")))