+3

Viết Smart Contract chain Algorand #2

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")))

All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.