การเข้าถึง SmartPy IDE
ขั้นแรก เปิด SmartPy ออนไลน์ IDE ที่ https://smartpy.io/ide/
นี่คือแพลตฟอร์มที่เราจะใช้เขียน ทดสอบ และปรับใช้สัญญาอัจฉริยะของเรา
การเริ่มต้นเทมเพลต FA1.2
คลิกที่ “เทมเพลตตามประเภท” บนแถบด้านข้างซ้าย และเลือก “FA1.2” แท็บใหม่จะเปิดขึ้นพร้อมกับเทมเพลตสัญญา FA1.2 เป็นสัญญาพร้อมใช้ตามมาตรฐาน FA1.2
ทำความเข้าใจกับเทมเพลต FA1.2
เทมเพลตนี้มีฟังก์ชันพื้นฐานสำหรับโทเค็นที่ใช้แทนกันได้ ซึ่งรวมถึงการโอนโทเค็น การอนุมัติการโอน การตรวจสอบยอดคงเหลือ และการดูอุปทานทั้งหมดของโทเค็น สัญญายังรวมถึงฟังก์ชันเพิ่มเติมสำหรับการสร้างเหรียญและการเบิร์นโทเค็น เช่นเดียวกับการจัดการการกำกับดูแล
ศึกษาเทมเพลตนี้และทำความเข้าใจฟังก์ชันต่างๆ ของเทมเพลตนี้ ไม่เป็นไรหากคุณไม่เข้าใจทุกอย่าง ณ จุดนี้ แต่พยายามทำความเข้าใจโดยทั่วไปเกี่ยวกับการดำเนินการที่สัญญานี้สามารถดำเนินการได้
ตัวอย่างเช่น คุณสามารถคัดลอกโค้ดจากเทมเพลตบน SmartPy IDE หรือที่นี่ด้านล่าง:
Python
# Fungible Assets - FA12
# แรงบันดาลใจจาก https://gitlab.com/tzip/tzip/blob/master/A/FA1.2.md
นำเข้า smartpy as sp
# ข้อมูลเมตาด้านล่างเป็นเพียงตัวอย่างเท่านั้น ทำหน้าที่เป็นฐาน
# เนื้อหาใช้ในการสร้างข้อมูลเมตา JSON ที่ผู้ใช้
# สามารถคัดลอกและอัปโหลดไปยัง IPFS
TZIP16_Metadata_Base = {
"name": "SmartPy FA1.2 Token Template",
"description": "Example Template for an FA1.2 Contract from SmartPy",
"authors": ["SmartPy Dev Team"],
"homepage": "https://smartpy.io",
"interfaces": ["TZIP-007-2021-04-17", "TZIP-016-2021-04-17"],
}
@sp.module
def m():
คลาส AdminInterface(sp.Contract):
@sp.private(with_storage="read-only")
def is_administrator_(ตนเอง, ผู้ส่ง):
sp .cast(sp.ผู้ส่ง, sp.address)
"""ไม่ใช่มาตรฐาน อาจถูกกำหนดใหม่ผ่านการสืบทอด"""
ส่งคืน True
คลาส CommonInterface(AdminInterface):
def __init__(self):
AdminInterface __init__(ตนเอง)
self.data.balances = sp.cast (
sp.big_map(),
sp.big_map[
sp.address,
sp.record(การอนุมัติ=sp.map[sp.address, sp.nat], balance=sp.nat),
],
)
ตนเอง.data.total_supply = 0
self.data.token_metadata = sp.cast (
sp.big_map(),
sp.big_map[
sp.nat,
sp.record(token_id=sp.nat, token_info=sp.map[sp.string, sp.bytes]),
],
)
self.data.metadata = sp.cast (
sp.big_map(),
sp.big_map[sp.string, sp.bytes],
)
self.data.balances = sp.cast (
sp.big_map(),
sp.big_map[
sp.address,
sp.record(การอนุมัติ=sp.map[sp.address, sp.nat], balance=sp.nat),
],
)
ตนเอง.data.total_supply = 0
self.data.token_metadata = sp.cast (
sp.big_map(),
sp.big_map[
sp.nat,
sp.record(token_id=sp.nat, token_info=sp.map[sp.string, sp.bytes]),
],
)
self.data.metadata = sp.cast (
sp.big_map(),
sp.big_map[sp.string, sp.bytes],
)
@sp.private(with_storage="read-only")
def is_paused_(self):
"""ไม่ใช่มาตรฐาน อาจถูกกำหนดใหม่ผ่านการสืบทอด"""
กลับเท็จ
คลาส Fa1_2 (CommonInterface):
def __init__(ตนเอง, ข้อมูลเมตา, บัญชีแยกประเภท, token_metadata):
"""
ข้อมูลจำเพาะ token_metadata: https://gitlab.com/tzip/tzip/-/blob/master/ ข้อเสนอ/tzip-12/tzip-12.md#token-ข้อมูลเมตา
ข้อมูลเมตาเฉพาะโทเค็นจะถูกจัดเก็บ/แสดงเป็นค่าประเภท Michelson (ไบต์สตริงของแผนที่)
คีย์บางส่วนถูกสงวนและกำหนดไว้ล่วงหน้า:
- "" : ควรสอดคล้องกับ TZIP-016 URI ซึ่งชี้ไปที่การแสดง JSON ของข้อมูลเมตาโทเค็น
- "name" : ควรเป็นสตริง UTF-8 ที่ให้ "ชื่อที่แสดง" ให้กับโทเค็น
- "สัญลักษณ์" : ควรเป็นสตริง UTF-8 สำหรับตัวระบุแบบสั้นของโทเค็น (เช่น XTZ, ยูโร, …)
- "ทศนิยม" : ควรเป็นจำนวนเต็ม (แปลงเป็นสตริง UTF-8 ในรูปแบบทศนิยม)
ซึ่งกำหนดตำแหน่งของจุดทศนิยมในยอดโทเค็นเพื่อการแสดงผล
ข้อมูลจำเพาะของสัญญา_metadata: https://gitlab.com/tzip/tzip/-/blob/master/proposals/tzip-16/tzip-16.md
"""
CommonInterface __init__(ตนเอง)
self.data.metadata = ข้อมูลเมตา
self.data.token_metadata = sp.big_map(
{0: sp.record(token_id=0, token_info=token_metadata)}
)
สำหรับเจ้าของใน ledger.items():
self.data.balances [owner.key] = Owner.value
self.data.total_supply += Owner.value.balance
# TODO: เปิดใช้งานเมื่อมีการใช้คุณลักษณะนี้
# self.init_metadata("ข้อมูลเมตา", ข้อมูลเมตา)
@sp.entrypoint การถ่ายโอน def
(ตนเอง, พารามิเตอร์):
sp.cast (
พารามิเตอร์,
sp.record (from_=sp.address, to_=sp.ที่อยู่ value=sp.nat).เค้าโครง(
("จาก_จาก", ("ถึง_จากถึง", "ค่า"))
),
)
balance_from = self.data.balances.get(
พารามิเตอร์.from_, ค่าเริ่มต้น = sp.record (ยอดคงเหลือ = 0, การอนุมัติ={})
)
balance_to = self.data.balances.get(
param.to_, ค่าเริ่มต้น = sp.record (ยอดคงเหลือ = 0, การอนุมัติ={})
)
balance_from.balance = sp.as_nat(
balance_from.balance - พารามิเตอร์ค่า error="FA1.2_InsufficientBalance"
)
balance_to.balance += param.value
หากไม่ใช่ self.is_administrator_(sp.sender):
ยืนยันว่าไม่ใช่ self.is_paused_() "FA1.2_หยุดชั่วคราว"
ถ้า param.from_ != sp.sender:
balance_from.approvals[sp.sender] = sp.as_nat(
balance_from.approvals[sp.sender] - param.value,
ข้อผิดพลาด = "FA1.2_NotAllowed",
)
self.data.balances [param.from_] = balance_จาก
self.data.balances[param.to_] = balance_to
@sp.entrypoint
def อนุมัติ (ตนเอง, พารามิเตอร์):
sp.cast (
พารามิเตอร์,
sp.record (spender=sp.address, value=sp.nat).เค้าโครง(
("spender", "value")
),
)
ยืนยันไม่ใช่ self.is_paused_(), "FA1.2_หยุดชั่วคราว"
exper_balance = self.data.balances.get (
sp.sender ค่าเริ่มต้น = sp.record (ยอดคงเหลือ = 0, การอนุมัติ={})
)
อนุมัติแล้ว = allowanceer_balance.approvals.get(param.spender, ค่าเริ่มต้น = 0)
ยืนยัน (
อนุมัติแล้ว == 0 หรือ param.value == 0
), "FA1.2_UnsafeAllowanceChange"
exper_balance.approvals[param.spender] = param.value
self.data.balances[sp.sender] =สปีดเดอร์_บาลานซ์
@sp.entrypoint
def getBalance(ตนเอง, พารามิเตอร์):
(ที่อยู่, โทรกลับ) = พารามิเตอร์
ผลลัพธ์ = self.data.balances.get(
ที่อยู่ ค่าเริ่มต้น = sp.record (ยอดคงเหลือ = 0, การอนุมัติ={})
).balance
sp.transfer(result, sp.tez(0), callback)
@sp.entrypoint
def getAllowance(self, param):
(args, callback) = param
ผลลัพธ์ = self.data.balances.get (
args.เจ้าของ, ค่าเริ่มต้น = sp.record (ยอดคงเหลือ = 0, การอนุมัติ={})
).approvals.get(args.spender, ค่าเริ่มต้น = 0)
sp.transfer (ผลลัพธ์, sp.tez (0), โทรกลับ)
@ sp.entrypoint
def getTotalSupply (ตนเอง, พารามิเตอร์):
sp.cast (param, sp.pair [sp.unit, sp.contract[sp.nat]])
sp.transfer (self.data.total_supply, sp.tez(0), sp.snd(param))
@sp.offchain_view()
def token_metadata(self, token_id):
"""ส่งคืน URI โทเค็น-เมตาดาต้าสำหรับโทเค็นที่กำหนด (token_id ต้องเป็น 0)"""
sp.cast (token_id, sp.nat)
ส่งคืน self.data.token_metadata [token_id]
##########
# มิกซ์อิน #
##########
class Admin(sp.Contract):
def __init__(self, administrator):
self.data.administrator = administrator
@sp.private(with_storage="read-only")
def is_administrator_(self, sender):
return sender == self.data.administrator
@sp.entrypoint
def setAdministrator(self, params):
sp.cast(params, sp.address)
assert self.is_administrator_(sp.sender), "Fa1.2_NotAdmin"
self.data.administrator = params
@sp.entrypoint()
def getAdministrator(self, param):
sp.cast(param, sp.pair[sp.unit, sp.contract[sp.address]])
sp.transfer(self.data.administrator, sp.tez(0), sp.snd(param))
@sp.onchain_view()
def get_administrator(self):
return self.data.administrator
class Pause(AdminInterface):
def __init__(self):
AdminInterface.__init__(self)
self.data.paused = False
@sp.private(with_storage="read-only")
def is_paused_(self):
return self.data.paused
@sp.entrypoint
def setPause(self, param):
sp.cast(param, sp.bool)
assert self.is_administrator_(sp.sender), "Fa1.2_NotAdmin"
self.data.paused = param
class Mint(CommonInterface):
def __init__(self):
CommonInterface.__init__(self)
@sp.entrypoint
def mint(self, param):
sp.cast(param, sp.record(address=sp.address, value=sp.nat))
assert self.is_administrator_(sp.sender), "Fa1.2_NotAdmin"
receiver_balance = self.data.balances.get(
param.address, default=sp.record(balance=0, approvals={})
)
receiver_balance.balance += param.value
self.data.balances[param.address] = receiver_balance
self.data.total_supply += param.value
class Burn(CommonInterface):
def __init__(self):
CommonInterface.__init__(self)
@sp.entrypoint
def burn(self, param):
sp.cast(param, sp.record(address=sp.address, value=sp.nat))
assert self.is_administrator_(sp.sender), "Fa1.2_NotAdmin"
receiver_balance = self.data.balances.get(
param.address, default=sp.record(balance=0, approvals={})
)
receiver_balance.balance = sp.as_nat(
receiver_balance.balance - param.value,
error="FA1.2_InsufficientBalance",
)
self.data.balances[param.address] = receiver_balance
self.data.total_supply = sp.as_nat(self.data.total_supply - param.value)
class ChangeMetadata(CommonInterface):
def __init__(self):
CommonInterface.__init__(self)
@sp.entrypoint
def update_metadata(self, key, value):
"""An entrypoint to allow the contract metadata to be updated."""
assert self.is_administrator_(sp.sender), "Fa1.2_NotAdmin"
self.data.metadata[key] = value
##########
# การทดสอบ #
##########
คลาส Fa1_2TestFull (ผู้ดูแลระบบ, หยุดชั่วคราว, Fa1_2, มิ้นท์, เบิร์น, ChangeMetadata):
def __init__(ตนเอง, ผู้ดูแลระบบ, ข้อมูลเมตา, บัญชีแยกประเภท, token_metadata):
ChangeMetadata __init__(ตนเอง)
เผา.__init__(ตนเอง)
สะระแหน่.__init__(ตนเอง)
ฟ้า1_2.__init__(ตนเอง ข้อมูลเมตา, บัญชีแยกประเภท, token_metadata)
หยุดชั่วคราว __init__(ตนเอง)
ผู้ดูแลระบบ __init__(ตนเอง ผู้ดูแลระบบ)
คลาส Viewer_nat (sp.Contract):
def __init__(ตนเอง):
self.data.last = sp.cast (ไม่มี, sp.option[sp.nat])
@ sp.entrypoint
def target (self, params):
self.data.last = sp.Some (params)
คลาส Viewer_address (sp.Contract):
def __init__(self):
self.data.last = sp.cast(ไม่มี, sp.option[sp.address])
@sp.entrypoint
def target(self, params):
self.data.last = sp.Some(params)
ถ้า "เทมเพลต" ไม่ได้อยู่ใน __name__:
@sp.add_test(name="FA12")
การทดสอบ def ():
sc = sp.test_scenario (m)
sc.h1("เทมเพลต FA1.2 - สินทรัพย์ที่สามารถทดแทนได้")
# sp.test_account สร้างคู่คีย์ ED25519 ตามที่กำหนด:
admin = sp.test_account("Administrator")
อลิซ = sp.test_account("อลิซ")
บ๊อบ = sp.test_account("โรเบิร์ต")
# มาแสดงบัญชีกันดีกว่า:
sc.h1("Accounts")
sc.show([admin, alice, bob])
sc.h1("Contract")
token_metadata = {
"decimals": sp.utils.bytes_of_string("18"), # Mandatory by the spec
"name": sp.utils.bytes_of_string("My Great Token"), # Recommended
"symbol": sp.utils.bytes_of_string("MGT"), # Recommended
# Extra fields
"icon": sp.utils.bytes_of_string(
"https://smartpy.io/static/img/logo-only.svg"
),
}
Contract_metadata = sp.utils ข้อมูลเมตา_of_url(
"ipfs://QmaiAUj1FFNGYTu8rLBjc3eeN9cSKwaF8EGMBNDmhzPNFd"
)
c1 = m.Fa1_2TestFull(
ผู้ดูแลระบบ=admin.address,
metadata=contract_metadata,
token_metadata=token_metadata,
ledger={},
)
sc += c1
sc .h1("มุมมอง Offchain - token_metadata")
sc.verify_equal(
sp.View(c1, "token_metadata")(0),
sp.record(
token_id=0,
token_info=sp.map(
{
"decimals": sp.utils.bytes_of_string("18"),
"name": sp.utils.bytes_of_string("My Great Token"),
"symbol": sp.utils.bytes_of_string("MGT"),
"icon": sp.utils.bytes_of_string(
"https://smartpy.io/static/img/logo-only.svg"
),
}
),
),
)
sc.h1("พยายามอัปเดตข้อมูลเมตา")
sc.verify(
c1.data.metadata[""]
== sp.utils.bytes_of_string (
"ipfs://QmaiAUj1FFNGYTu8rLBjc3eeN9cSKwaF8EGMBNDmhzPNFd"
)
)
c1.update_metadata(key="", value=sp.bytes("0x00")).run(sender=admin)
sc.verify(c1.data.metadata[ ""] == sp.bytes("0x00"))
sc.h1("จุดเข้าใช้งาน")
sc.h2("ผู้ดูแลระบบสร้างเหรียญสองสามเหรียญ")
c1.mint(address=alice.address, value=12).run (ผู้ส่ง=ผู้ดูแลระบบ)
c1.mint(ที่อยู่=alice.address, value=3).run(ผู้ส่ง=admin)
c1.mint(ที่อยู่=alice.address, value=3).run(ผู้ส่ง=admin)
sc.h2("อลิซโอนไปยังบ๊อบ")
c1.transfer(from_=alice.address, ถึง_=bob.address, ค่า=4).รัน(ผู้ส่ง=อลิซ)
sc.verify(c1.data.balances[alice.address].balance == 14)
sc.h2("Bob พยายามย้ายจาก Alice แต่เขาไม่ได้รับการอนุมัติจากเธอ")
c1.transfer(from_=alice.address, ถึง_=bob.address, ค่า=4).รัน(
sender=bob, valid=False
)
sc.h2("Alice อนุมัติการโอน Bob และ Bob")
c1.approve(spender=bob.address, value=5).run(sender=alice)
c1.transfer(จาก_=อลิซ.ที่อยู่, ถึง_=bob.address, ค่า=4).รัน(ผู้ส่ง=บ๊อบ)
sc.h2("Bob พยายามถ่ายโอนจากอลิซมากเกินไป")
c1.transfer(from_=alice.address, ถึง_=bob.address, ค่า=4).รัน(
sender=bob, valid=False
)
sc.h2("ผู้ดูแลระบบเบิร์นโทเค็น Bob")
c1.burn(address=bob.address, ค่า=1).รัน(ผู้ส่ง=ผู้ดูแลระบบ)
sc.verify(c1.data.balances[alice.address].balance == 10)
sc.h2("อลิซพยายามเผาโทเค็น Bob")
c1.burn(address=bob.address, ค่า=1).รัน(ผู้ส่ง=อลิซ, valid=False)
sc.h2("ผู้ดูแลระบบหยุดสัญญาชั่วคราวและอลิซไม่สามารถถ่ายโอนได้อีกต่อไป")
c1.setPause(True).run(sender=admin)
c1.transfer(จาก_=อลิซ.ที่อยู่, ถึง_=bob.address, ค่า=4).รัน(
ผู้ส่ง = อลิซ ถูกต้อง = เท็จ
)
sc.verify (c1.data.balances [alice.address].balance == 10)
sc.h2("ผู้ดูแลระบบถ่ายโอนขณะหยุดชั่วคราว")
c1.transfer(from_=alice.address, ถึง_=bob.address, ค่า=1).รัน(ผู้ส่ง=ผู้ดูแลระบบ)
sc.h2("ผู้ดูแลระบบยกเลิกการหยุดสัญญาชั่วคราวและอนุญาตให้ถ่ายโอนได้")
c1.setPause(False).run(sender=admin)
sc.verify(c1.data.balances[alice.address].balance == 9)
c1.transfer(from_=alice.address, ถึง_=bob.address, ค่า=1).รัน(ผู้ส่ง=อลิซ)
sc.ตรวจสอบ(c1.data.total_supply == 17)
sc.verify(c1.data.balances[alice.address].balance == 8)
sc.verify(c1.data.balances[bob.address].balance == 9)
sc.h1("มุมมอง")
sc.h2("Balance")
view_balance = m.Viewer_nat()
sc += view_balance
เป้าหมาย = sp.contract(sp.TNat, view_balance.address, "เป้าหมาย").open_some()
c1.getBalance((alice.address, เป้าหมาย))
sc.verify_equal(view_balance.data.last, sp.some(8))
sc.h2("ผู้ดูแลระบบ")
view_administrator = m.Viewer_address()
sc += view_administrator
เป้าหมาย = sp.contract(
sp.TAddress, view_administrator.address, "เป้าหมาย"
).open_some()
c1.getAdministrator((sp.unit, เป้าหมาย))
sc.verify_equal(view_administrator.data.last, sp.some(admin.address))
sc.h2("อุปทานทั้งหมด")
view_totalSupply = m.Viewer_nat()
sc += view_totalSupply
เป้าหมาย = sp.contract(sp.TNat, view_totalSupply.address, "เป้าหมาย").open_some()
c1.getTotalSupply((sp.unit, เป้าหมาย))
sc.verify_equal(view_totalSupply.data.last, sp.some(17))
sc.h2("ค่าเผื่อ")
view_allowance = m.Viewer_nat()
sc += view_allowance
เป้าหมาย = sp.contract(sp.TNat, view_allowance.address, "เป้าหมาย").open_some()
c1.getAllowance((sp.record(owner=alice.address, allowance=bob.address), เป้าหมาย))
sc.verify_equal(view_allowance.data.last, sp.บาง(1))
สัญญา FA1.2 ดั้งเดิมมีฟังก์ชันพื้นฐาน เช่น การโอนโทเค็น การอนุมัติการโอน การตรวจสอบยอดคงเหลือ และการดูอุปทานทั้งหมดของโทเค็น ตอนนี้เราจะปรับปรุงฟังก์ชันนี้
ยินดีด้วย! คุณได้สร้างโทเค็นที่ใช้งานได้เป็นครั้งแรกบน Tezos โดยใช้มาตรฐาน FA1.2!
ในบทเรียนถัดไป เราจะได้เรียนรู้วิธีโต้ตอบกับสัญญาโทเค็นที่เราเพิ่งสร้างขึ้น ซึ่งจะรวมถึงการโอนโทเค็น การอนุมัติการโอนโทเค็น และการตรวจสอบยอดโทเค็นและอุปทานทั้งหมด คอยติดตาม!
การเข้าถึง SmartPy IDE
ขั้นแรก เปิด SmartPy ออนไลน์ IDE ที่ https://smartpy.io/ide/
นี่คือแพลตฟอร์มที่เราจะใช้เขียน ทดสอบ และปรับใช้สัญญาอัจฉริยะของเรา
การเริ่มต้นเทมเพลต FA1.2
คลิกที่ “เทมเพลตตามประเภท” บนแถบด้านข้างซ้าย และเลือก “FA1.2” แท็บใหม่จะเปิดขึ้นพร้อมกับเทมเพลตสัญญา FA1.2 เป็นสัญญาพร้อมใช้ตามมาตรฐาน FA1.2
ทำความเข้าใจกับเทมเพลต FA1.2
เทมเพลตนี้มีฟังก์ชันพื้นฐานสำหรับโทเค็นที่ใช้แทนกันได้ ซึ่งรวมถึงการโอนโทเค็น การอนุมัติการโอน การตรวจสอบยอดคงเหลือ และการดูอุปทานทั้งหมดของโทเค็น สัญญายังรวมถึงฟังก์ชันเพิ่มเติมสำหรับการสร้างเหรียญและการเบิร์นโทเค็น เช่นเดียวกับการจัดการการกำกับดูแล
ศึกษาเทมเพลตนี้และทำความเข้าใจฟังก์ชันต่างๆ ของเทมเพลตนี้ ไม่เป็นไรหากคุณไม่เข้าใจทุกอย่าง ณ จุดนี้ แต่พยายามทำความเข้าใจโดยทั่วไปเกี่ยวกับการดำเนินการที่สัญญานี้สามารถดำเนินการได้
ตัวอย่างเช่น คุณสามารถคัดลอกโค้ดจากเทมเพลตบน SmartPy IDE หรือที่นี่ด้านล่าง:
Python
# Fungible Assets - FA12
# แรงบันดาลใจจาก https://gitlab.com/tzip/tzip/blob/master/A/FA1.2.md
นำเข้า smartpy as sp
# ข้อมูลเมตาด้านล่างเป็นเพียงตัวอย่างเท่านั้น ทำหน้าที่เป็นฐาน
# เนื้อหาใช้ในการสร้างข้อมูลเมตา JSON ที่ผู้ใช้
# สามารถคัดลอกและอัปโหลดไปยัง IPFS
TZIP16_Metadata_Base = {
"name": "SmartPy FA1.2 Token Template",
"description": "Example Template for an FA1.2 Contract from SmartPy",
"authors": ["SmartPy Dev Team"],
"homepage": "https://smartpy.io",
"interfaces": ["TZIP-007-2021-04-17", "TZIP-016-2021-04-17"],
}
@sp.module
def m():
คลาส AdminInterface(sp.Contract):
@sp.private(with_storage="read-only")
def is_administrator_(ตนเอง, ผู้ส่ง):
sp .cast(sp.ผู้ส่ง, sp.address)
"""ไม่ใช่มาตรฐาน อาจถูกกำหนดใหม่ผ่านการสืบทอด"""
ส่งคืน True
คลาส CommonInterface(AdminInterface):
def __init__(self):
AdminInterface __init__(ตนเอง)
self.data.balances = sp.cast (
sp.big_map(),
sp.big_map[
sp.address,
sp.record(การอนุมัติ=sp.map[sp.address, sp.nat], balance=sp.nat),
],
)
ตนเอง.data.total_supply = 0
self.data.token_metadata = sp.cast (
sp.big_map(),
sp.big_map[
sp.nat,
sp.record(token_id=sp.nat, token_info=sp.map[sp.string, sp.bytes]),
],
)
self.data.metadata = sp.cast (
sp.big_map(),
sp.big_map[sp.string, sp.bytes],
)
self.data.balances = sp.cast (
sp.big_map(),
sp.big_map[
sp.address,
sp.record(การอนุมัติ=sp.map[sp.address, sp.nat], balance=sp.nat),
],
)
ตนเอง.data.total_supply = 0
self.data.token_metadata = sp.cast (
sp.big_map(),
sp.big_map[
sp.nat,
sp.record(token_id=sp.nat, token_info=sp.map[sp.string, sp.bytes]),
],
)
self.data.metadata = sp.cast (
sp.big_map(),
sp.big_map[sp.string, sp.bytes],
)
@sp.private(with_storage="read-only")
def is_paused_(self):
"""ไม่ใช่มาตรฐาน อาจถูกกำหนดใหม่ผ่านการสืบทอด"""
กลับเท็จ
คลาส Fa1_2 (CommonInterface):
def __init__(ตนเอง, ข้อมูลเมตา, บัญชีแยกประเภท, token_metadata):
"""
ข้อมูลจำเพาะ token_metadata: https://gitlab.com/tzip/tzip/-/blob/master/ ข้อเสนอ/tzip-12/tzip-12.md#token-ข้อมูลเมตา
ข้อมูลเมตาเฉพาะโทเค็นจะถูกจัดเก็บ/แสดงเป็นค่าประเภท Michelson (ไบต์สตริงของแผนที่)
คีย์บางส่วนถูกสงวนและกำหนดไว้ล่วงหน้า:
- "" : ควรสอดคล้องกับ TZIP-016 URI ซึ่งชี้ไปที่การแสดง JSON ของข้อมูลเมตาโทเค็น
- "name" : ควรเป็นสตริง UTF-8 ที่ให้ "ชื่อที่แสดง" ให้กับโทเค็น
- "สัญลักษณ์" : ควรเป็นสตริง UTF-8 สำหรับตัวระบุแบบสั้นของโทเค็น (เช่น XTZ, ยูโร, …)
- "ทศนิยม" : ควรเป็นจำนวนเต็ม (แปลงเป็นสตริง UTF-8 ในรูปแบบทศนิยม)
ซึ่งกำหนดตำแหน่งของจุดทศนิยมในยอดโทเค็นเพื่อการแสดงผล
ข้อมูลจำเพาะของสัญญา_metadata: https://gitlab.com/tzip/tzip/-/blob/master/proposals/tzip-16/tzip-16.md
"""
CommonInterface __init__(ตนเอง)
self.data.metadata = ข้อมูลเมตา
self.data.token_metadata = sp.big_map(
{0: sp.record(token_id=0, token_info=token_metadata)}
)
สำหรับเจ้าของใน ledger.items():
self.data.balances [owner.key] = Owner.value
self.data.total_supply += Owner.value.balance
# TODO: เปิดใช้งานเมื่อมีการใช้คุณลักษณะนี้
# self.init_metadata("ข้อมูลเมตา", ข้อมูลเมตา)
@sp.entrypoint การถ่ายโอน def
(ตนเอง, พารามิเตอร์):
sp.cast (
พารามิเตอร์,
sp.record (from_=sp.address, to_=sp.ที่อยู่ value=sp.nat).เค้าโครง(
("จาก_จาก", ("ถึง_จากถึง", "ค่า"))
),
)
balance_from = self.data.balances.get(
พารามิเตอร์.from_, ค่าเริ่มต้น = sp.record (ยอดคงเหลือ = 0, การอนุมัติ={})
)
balance_to = self.data.balances.get(
param.to_, ค่าเริ่มต้น = sp.record (ยอดคงเหลือ = 0, การอนุมัติ={})
)
balance_from.balance = sp.as_nat(
balance_from.balance - พารามิเตอร์ค่า error="FA1.2_InsufficientBalance"
)
balance_to.balance += param.value
หากไม่ใช่ self.is_administrator_(sp.sender):
ยืนยันว่าไม่ใช่ self.is_paused_() "FA1.2_หยุดชั่วคราว"
ถ้า param.from_ != sp.sender:
balance_from.approvals[sp.sender] = sp.as_nat(
balance_from.approvals[sp.sender] - param.value,
ข้อผิดพลาด = "FA1.2_NotAllowed",
)
self.data.balances [param.from_] = balance_จาก
self.data.balances[param.to_] = balance_to
@sp.entrypoint
def อนุมัติ (ตนเอง, พารามิเตอร์):
sp.cast (
พารามิเตอร์,
sp.record (spender=sp.address, value=sp.nat).เค้าโครง(
("spender", "value")
),
)
ยืนยันไม่ใช่ self.is_paused_(), "FA1.2_หยุดชั่วคราว"
exper_balance = self.data.balances.get (
sp.sender ค่าเริ่มต้น = sp.record (ยอดคงเหลือ = 0, การอนุมัติ={})
)
อนุมัติแล้ว = allowanceer_balance.approvals.get(param.spender, ค่าเริ่มต้น = 0)
ยืนยัน (
อนุมัติแล้ว == 0 หรือ param.value == 0
), "FA1.2_UnsafeAllowanceChange"
exper_balance.approvals[param.spender] = param.value
self.data.balances[sp.sender] =สปีดเดอร์_บาลานซ์
@sp.entrypoint
def getBalance(ตนเอง, พารามิเตอร์):
(ที่อยู่, โทรกลับ) = พารามิเตอร์
ผลลัพธ์ = self.data.balances.get(
ที่อยู่ ค่าเริ่มต้น = sp.record (ยอดคงเหลือ = 0, การอนุมัติ={})
).balance
sp.transfer(result, sp.tez(0), callback)
@sp.entrypoint
def getAllowance(self, param):
(args, callback) = param
ผลลัพธ์ = self.data.balances.get (
args.เจ้าของ, ค่าเริ่มต้น = sp.record (ยอดคงเหลือ = 0, การอนุมัติ={})
).approvals.get(args.spender, ค่าเริ่มต้น = 0)
sp.transfer (ผลลัพธ์, sp.tez (0), โทรกลับ)
@ sp.entrypoint
def getTotalSupply (ตนเอง, พารามิเตอร์):
sp.cast (param, sp.pair [sp.unit, sp.contract[sp.nat]])
sp.transfer (self.data.total_supply, sp.tez(0), sp.snd(param))
@sp.offchain_view()
def token_metadata(self, token_id):
"""ส่งคืน URI โทเค็น-เมตาดาต้าสำหรับโทเค็นที่กำหนด (token_id ต้องเป็น 0)"""
sp.cast (token_id, sp.nat)
ส่งคืน self.data.token_metadata [token_id]
##########
# มิกซ์อิน #
##########
class Admin(sp.Contract):
def __init__(self, administrator):
self.data.administrator = administrator
@sp.private(with_storage="read-only")
def is_administrator_(self, sender):
return sender == self.data.administrator
@sp.entrypoint
def setAdministrator(self, params):
sp.cast(params, sp.address)
assert self.is_administrator_(sp.sender), "Fa1.2_NotAdmin"
self.data.administrator = params
@sp.entrypoint()
def getAdministrator(self, param):
sp.cast(param, sp.pair[sp.unit, sp.contract[sp.address]])
sp.transfer(self.data.administrator, sp.tez(0), sp.snd(param))
@sp.onchain_view()
def get_administrator(self):
return self.data.administrator
class Pause(AdminInterface):
def __init__(self):
AdminInterface.__init__(self)
self.data.paused = False
@sp.private(with_storage="read-only")
def is_paused_(self):
return self.data.paused
@sp.entrypoint
def setPause(self, param):
sp.cast(param, sp.bool)
assert self.is_administrator_(sp.sender), "Fa1.2_NotAdmin"
self.data.paused = param
class Mint(CommonInterface):
def __init__(self):
CommonInterface.__init__(self)
@sp.entrypoint
def mint(self, param):
sp.cast(param, sp.record(address=sp.address, value=sp.nat))
assert self.is_administrator_(sp.sender), "Fa1.2_NotAdmin"
receiver_balance = self.data.balances.get(
param.address, default=sp.record(balance=0, approvals={})
)
receiver_balance.balance += param.value
self.data.balances[param.address] = receiver_balance
self.data.total_supply += param.value
class Burn(CommonInterface):
def __init__(self):
CommonInterface.__init__(self)
@sp.entrypoint
def burn(self, param):
sp.cast(param, sp.record(address=sp.address, value=sp.nat))
assert self.is_administrator_(sp.sender), "Fa1.2_NotAdmin"
receiver_balance = self.data.balances.get(
param.address, default=sp.record(balance=0, approvals={})
)
receiver_balance.balance = sp.as_nat(
receiver_balance.balance - param.value,
error="FA1.2_InsufficientBalance",
)
self.data.balances[param.address] = receiver_balance
self.data.total_supply = sp.as_nat(self.data.total_supply - param.value)
class ChangeMetadata(CommonInterface):
def __init__(self):
CommonInterface.__init__(self)
@sp.entrypoint
def update_metadata(self, key, value):
"""An entrypoint to allow the contract metadata to be updated."""
assert self.is_administrator_(sp.sender), "Fa1.2_NotAdmin"
self.data.metadata[key] = value
##########
# การทดสอบ #
##########
คลาส Fa1_2TestFull (ผู้ดูแลระบบ, หยุดชั่วคราว, Fa1_2, มิ้นท์, เบิร์น, ChangeMetadata):
def __init__(ตนเอง, ผู้ดูแลระบบ, ข้อมูลเมตา, บัญชีแยกประเภท, token_metadata):
ChangeMetadata __init__(ตนเอง)
เผา.__init__(ตนเอง)
สะระแหน่.__init__(ตนเอง)
ฟ้า1_2.__init__(ตนเอง ข้อมูลเมตา, บัญชีแยกประเภท, token_metadata)
หยุดชั่วคราว __init__(ตนเอง)
ผู้ดูแลระบบ __init__(ตนเอง ผู้ดูแลระบบ)
คลาส Viewer_nat (sp.Contract):
def __init__(ตนเอง):
self.data.last = sp.cast (ไม่มี, sp.option[sp.nat])
@ sp.entrypoint
def target (self, params):
self.data.last = sp.Some (params)
คลาส Viewer_address (sp.Contract):
def __init__(self):
self.data.last = sp.cast(ไม่มี, sp.option[sp.address])
@sp.entrypoint
def target(self, params):
self.data.last = sp.Some(params)
ถ้า "เทมเพลต" ไม่ได้อยู่ใน __name__:
@sp.add_test(name="FA12")
การทดสอบ def ():
sc = sp.test_scenario (m)
sc.h1("เทมเพลต FA1.2 - สินทรัพย์ที่สามารถทดแทนได้")
# sp.test_account สร้างคู่คีย์ ED25519 ตามที่กำหนด:
admin = sp.test_account("Administrator")
อลิซ = sp.test_account("อลิซ")
บ๊อบ = sp.test_account("โรเบิร์ต")
# มาแสดงบัญชีกันดีกว่า:
sc.h1("Accounts")
sc.show([admin, alice, bob])
sc.h1("Contract")
token_metadata = {
"decimals": sp.utils.bytes_of_string("18"), # Mandatory by the spec
"name": sp.utils.bytes_of_string("My Great Token"), # Recommended
"symbol": sp.utils.bytes_of_string("MGT"), # Recommended
# Extra fields
"icon": sp.utils.bytes_of_string(
"https://smartpy.io/static/img/logo-only.svg"
),
}
Contract_metadata = sp.utils ข้อมูลเมตา_of_url(
"ipfs://QmaiAUj1FFNGYTu8rLBjc3eeN9cSKwaF8EGMBNDmhzPNFd"
)
c1 = m.Fa1_2TestFull(
ผู้ดูแลระบบ=admin.address,
metadata=contract_metadata,
token_metadata=token_metadata,
ledger={},
)
sc += c1
sc .h1("มุมมอง Offchain - token_metadata")
sc.verify_equal(
sp.View(c1, "token_metadata")(0),
sp.record(
token_id=0,
token_info=sp.map(
{
"decimals": sp.utils.bytes_of_string("18"),
"name": sp.utils.bytes_of_string("My Great Token"),
"symbol": sp.utils.bytes_of_string("MGT"),
"icon": sp.utils.bytes_of_string(
"https://smartpy.io/static/img/logo-only.svg"
),
}
),
),
)
sc.h1("พยายามอัปเดตข้อมูลเมตา")
sc.verify(
c1.data.metadata[""]
== sp.utils.bytes_of_string (
"ipfs://QmaiAUj1FFNGYTu8rLBjc3eeN9cSKwaF8EGMBNDmhzPNFd"
)
)
c1.update_metadata(key="", value=sp.bytes("0x00")).run(sender=admin)
sc.verify(c1.data.metadata[ ""] == sp.bytes("0x00"))
sc.h1("จุดเข้าใช้งาน")
sc.h2("ผู้ดูแลระบบสร้างเหรียญสองสามเหรียญ")
c1.mint(address=alice.address, value=12).run (ผู้ส่ง=ผู้ดูแลระบบ)
c1.mint(ที่อยู่=alice.address, value=3).run(ผู้ส่ง=admin)
c1.mint(ที่อยู่=alice.address, value=3).run(ผู้ส่ง=admin)
sc.h2("อลิซโอนไปยังบ๊อบ")
c1.transfer(from_=alice.address, ถึง_=bob.address, ค่า=4).รัน(ผู้ส่ง=อลิซ)
sc.verify(c1.data.balances[alice.address].balance == 14)
sc.h2("Bob พยายามย้ายจาก Alice แต่เขาไม่ได้รับการอนุมัติจากเธอ")
c1.transfer(from_=alice.address, ถึง_=bob.address, ค่า=4).รัน(
sender=bob, valid=False
)
sc.h2("Alice อนุมัติการโอน Bob และ Bob")
c1.approve(spender=bob.address, value=5).run(sender=alice)
c1.transfer(จาก_=อลิซ.ที่อยู่, ถึง_=bob.address, ค่า=4).รัน(ผู้ส่ง=บ๊อบ)
sc.h2("Bob พยายามถ่ายโอนจากอลิซมากเกินไป")
c1.transfer(from_=alice.address, ถึง_=bob.address, ค่า=4).รัน(
sender=bob, valid=False
)
sc.h2("ผู้ดูแลระบบเบิร์นโทเค็น Bob")
c1.burn(address=bob.address, ค่า=1).รัน(ผู้ส่ง=ผู้ดูแลระบบ)
sc.verify(c1.data.balances[alice.address].balance == 10)
sc.h2("อลิซพยายามเผาโทเค็น Bob")
c1.burn(address=bob.address, ค่า=1).รัน(ผู้ส่ง=อลิซ, valid=False)
sc.h2("ผู้ดูแลระบบหยุดสัญญาชั่วคราวและอลิซไม่สามารถถ่ายโอนได้อีกต่อไป")
c1.setPause(True).run(sender=admin)
c1.transfer(จาก_=อลิซ.ที่อยู่, ถึง_=bob.address, ค่า=4).รัน(
ผู้ส่ง = อลิซ ถูกต้อง = เท็จ
)
sc.verify (c1.data.balances [alice.address].balance == 10)
sc.h2("ผู้ดูแลระบบถ่ายโอนขณะหยุดชั่วคราว")
c1.transfer(from_=alice.address, ถึง_=bob.address, ค่า=1).รัน(ผู้ส่ง=ผู้ดูแลระบบ)
sc.h2("ผู้ดูแลระบบยกเลิกการหยุดสัญญาชั่วคราวและอนุญาตให้ถ่ายโอนได้")
c1.setPause(False).run(sender=admin)
sc.verify(c1.data.balances[alice.address].balance == 9)
c1.transfer(from_=alice.address, ถึง_=bob.address, ค่า=1).รัน(ผู้ส่ง=อลิซ)
sc.ตรวจสอบ(c1.data.total_supply == 17)
sc.verify(c1.data.balances[alice.address].balance == 8)
sc.verify(c1.data.balances[bob.address].balance == 9)
sc.h1("มุมมอง")
sc.h2("Balance")
view_balance = m.Viewer_nat()
sc += view_balance
เป้าหมาย = sp.contract(sp.TNat, view_balance.address, "เป้าหมาย").open_some()
c1.getBalance((alice.address, เป้าหมาย))
sc.verify_equal(view_balance.data.last, sp.some(8))
sc.h2("ผู้ดูแลระบบ")
view_administrator = m.Viewer_address()
sc += view_administrator
เป้าหมาย = sp.contract(
sp.TAddress, view_administrator.address, "เป้าหมาย"
).open_some()
c1.getAdministrator((sp.unit, เป้าหมาย))
sc.verify_equal(view_administrator.data.last, sp.some(admin.address))
sc.h2("อุปทานทั้งหมด")
view_totalSupply = m.Viewer_nat()
sc += view_totalSupply
เป้าหมาย = sp.contract(sp.TNat, view_totalSupply.address, "เป้าหมาย").open_some()
c1.getTotalSupply((sp.unit, เป้าหมาย))
sc.verify_equal(view_totalSupply.data.last, sp.some(17))
sc.h2("ค่าเผื่อ")
view_allowance = m.Viewer_nat()
sc += view_allowance
เป้าหมาย = sp.contract(sp.TNat, view_allowance.address, "เป้าหมาย").open_some()
c1.getAllowance((sp.record(owner=alice.address, allowance=bob.address), เป้าหมาย))
sc.verify_equal(view_allowance.data.last, sp.บาง(1))
สัญญา FA1.2 ดั้งเดิมมีฟังก์ชันพื้นฐาน เช่น การโอนโทเค็น การอนุมัติการโอน การตรวจสอบยอดคงเหลือ และการดูอุปทานทั้งหมดของโทเค็น ตอนนี้เราจะปรับปรุงฟังก์ชันนี้
ยินดีด้วย! คุณได้สร้างโทเค็นที่ใช้งานได้เป็นครั้งแรกบน Tezos โดยใช้มาตรฐาน FA1.2!
ในบทเรียนถัดไป เราจะได้เรียนรู้วิธีโต้ตอบกับสัญญาโทเค็นที่เราเพิ่งสร้างขึ้น ซึ่งจะรวมถึงการโอนโทเค็น การอนุมัติการโอนโทเค็น และการตรวจสอบยอดโทเค็นและอุปทานทั้งหมด คอยติดตาม!