第2課

Tạo mã thông báo đầu tiên của bạn với tiêu chuẩn FA1.2

Trong bài học này, chúng ta sẽ thực hiện quy trình tạo mã thông báo có thể thay thế bằng tiêu chuẩn FA1.2 trên Tezos. Chúng tôi sẽ sử dụng IDE trực tuyến SmartPy để viết và triển khai hợp đồng thông minh của mình. Hãy nhớ rằng tiêu chuẩn FA1.2 chủ yếu được sử dụng cho các mã thông báo có thể thay thế, nghĩa là các mã thông báo có thuộc tính giống hệt nhau và có thể được trao đổi trên cơ sở một đổi một.

Hướng dẫn từng bước một

  1. Truy cập IDE SmartPy

  2. Đầu tiên, hãy mở IDE trực tuyến SmartPy tại https://smartpy.io/ide/. Đây là nền tảng mà chúng tôi sẽ sử dụng để viết, kiểm tra và triển khai các hợp đồng thông minh của mình.

  3. Khởi tạo mẫu FA1.2

  4. Nhấp vào “Mẫu theo loại” trên thanh bên trái và chọn “FA1.2”. Một tab mới sẽ mở ra với mẫu hợp đồng FA1.2. Đây là hợp đồng sẵn sàng sử dụng theo tiêu chuẩn FA1.2.

  5. Tìm hiểu mẫu FA1.2

  6. Mẫu này có chức năng cơ bản dành cho mã thông báo có thể thay thế, bao gồm chuyển mã thông báo, phê duyệt chuyển khoản, kiểm tra số dư và xem tổng nguồn cung cấp mã thông báo. Hợp đồng cũng bao gồm chức năng bổ sung để đúc và đốt token cũng như quản lý quản trị.

  7. Nghiên cứu mẫu này và đảm bảo bạn hiểu các chức năng của nó. Sẽ không sao nếu bạn không hiểu mọi thứ vào thời điểm này, nhưng hãy cố gắng hiểu khái quát về các hoạt động mà hợp đồng này có thể thực hiện.
    Ví dụ: bạn có thể sao chép mã từ mẫu trên SmartPy IDE hoặc ở đây bên dưới:

Python 
 # Fungible Assets - FA12 
 # Lấy cảm hứng từ https://gitlab.com/tzip/tzip/blob/master/A/FA1.2.md 

 import smartpy as sp 


 # Siêu dữ liệu bên dưới chỉ là một ví dụ, nó đóng vai trò là cơ sở, 
 # nội dung được sử dụng để xây dựng siêu dữ liệu JSON mà người dùng 
 # có thể sao chép và tải lên 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(): 
 lớp AdminInterface(sp.Contract): 
 @sp.private(with_storage="read-only") 
 def is_administrator_(self, sender): 
 sp .cast(sp.sender, sp.address) 
 """Không chuẩn, có thể được xác định lại thông qua kế thừa."""
            trả về Đúng 

 lớp CommonInterface(AdminInterface): 
 def __init__(self): 
 AdminInterface.__init__(bản thân)
            self.data.balances = sp.cast(
                sp.big_map(), 
 sp.big_map[ 
 sp.address, 
 sp.record(approvals=sp.map[sp.address, sp.nat], số dư=sp.nat),
                ], 
 ) 
 self.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(approvals=sp.map[sp.address, sp.nat], số dư=sp.nat),
                ], 
 ) 
 self.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): 
 """Không chuẩn, có thể được xác định lại thông qua kế thừa."""
            trả về Sai 

 lớp Fa1_2(CommonInterface): 
 def __init__(self, siêu dữ liệu, sổ cái, token_metadata): 
 """ 
 thông số token_metadata: https://gitlab.com/tzip/tzip/-/blob/master/ đề xuất/tzip-12/tzip-12.md#token-metadata
            Siêu dữ liệu dành riêng cho mã thông báo được lưu trữ/trình bày dưới dạng loại giá trị Michelson (byte chuỗi bản đồ).
            Một số khóa được đặt trước và xác định trước: 

 - "" : Phải tương ứng với URI TZIP-016 trỏ đến biểu diễn JSON của siêu dữ liệu mã thông báo.
            - "name" : Phải là chuỗi UTF-8 cung cấp "tên hiển thị" cho mã thông báo.
            - "biểu tượng" : Phải là chuỗi UTF-8 cho mã định danh ngắn của mã thông báo (ví dụ: XTZ, EUR,…). 
 - "số thập phân" : Phải là số nguyên (được chuyển đổi thành chuỗi UTF-8 ở dạng thập phân) 
 xác định vị trí của dấu thập phân trong số dư mã thông báo cho mục đích hiển thị.

            thông số Contract_metadata: https://gitlab.com/tzip/tzip/-/blob/master/proposals/tzip-16/tzip-16.md 
 """ 
 CommonInterface.__init__(self)
            self.data.metadata = siêu dữ liệu 
 self.data.token_metadata = sp.big_map(
                {0: sp.record(token_id=0, token_info=token_metadata)}
            ) 

 cho chủ sở hữu trong ledger.items():
                self.data.balances[owner.key] = owner.value 
 self.data.total_supply += owner.value.balance 

 # TODO: Kích hoạt khi tính năng này được triển khai.
            # self.init_metadata("siêu dữ liệu", siêu dữ liệu) 

 @sp.entrypoint 
 chuyển def(self, param): 
 sp.cast( 
 param, 
 sp.record(from_=sp.address, to_=sp.địa chỉ, value=sp.nat).layout(
                    ("from_ as from", ("to_ as to", "value")) 
 ), 
 ) 
 Balance_from = self.data.balances.get(
                thông số.from_, mặc định=sp.record(cân bằng=0, phê duyệt={}) 
 ) 
 Balance_to = self.data.balances.get(
                thông số.to_, mặc định=sp.record(cân bằng=0, phê duyệt={}) 
 ) 
 Balance_from.balance = sp.as_nat(
                Balance_from.balance - param.value, error="FA1.2_In EnoughBalance" 
 ) 
 Balance_to.balance += param.value 
 nếu không self.is_administrator_(sp.sender):
                khẳng định không phải self.is_paused_(), "FA1.2_Tạm dừng"
                nếu tham số.from_ != sp.sender: 
 số dư_from.approvals[sp.sender] = sp.as_nat(
                        Balance_from.approvals[sp.sender] - param.value,
                        error="FA1.2_NotAllowed", 
 ) 
 self.data.balances[param.from_] = Balance_from 
 self.data.balances[param.to_] = Balance_to 

 @sp.entrypoint 
 xác nhận phê duyệt(self, param): 
 sp.cast( 
 param, 
 sp.record(spender=sp.address, value=sp.nat).layout(
                    ("người chi tiêu", "giá trị") 
 ), 
 ) 
 khẳng định không phải self.is_paused_(), "FA1.2_Tạm dừng"
            chi tiêu_balance = self.data.balances.get(
                sp.sender, mặc định=sp.record(balance=0, phê duyệt={}) 
 ) 
 đã được phê duyệt = chi tiêu_balance.approvals.get(param.spender, default=0) 
 xác nhận ( 
 đã được phê duyệt == 0 hoặc param.value == 0 
 ), "FA1.2_UnsafeAllowanceChange"
            chi tiêu_balance.approvals[param.spender] = param.value 
 self.data.balances[sp.sender] = chi tiêu_balance 

 @sp.entrypoint 
 def getBalance(self, param): 
 (địa chỉ, gọi lại) = param 
 result = self.data.balances.get(
                địa chỉ, mặc định=sp.record(balance=0, phê duyệt={}) 
 ).balance 
 sp.transfer(result, sp.tez(0), callback) 

 @sp.entrypoint 
 def getAllowance(self, param): 
 (args, callback) = param 
 kết quả = self.data.balances.get(
                args.owner, mặc định=sp.record(cân bằng=0, phê duyệt={}) 
 ).approvals.get(args.spender, default=0) 
 sp.transfer(result, sp.tez(0), callback) 

 @sp.entrypoint 
 def getTotalSupply(self, param): 
 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): 
 """Trả về URI siêu dữ liệu mã thông báo cho mã thông báo đã cho. (token_id phải là 0)."""
            sp.cast(token_id, sp.nat) 
 trả về self.data.token_metadata[token_id]

    ########## 
 # Mixins # 
 ##########

    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

    ########## 
 # Kiểm tra # 
 ########## 

 lớp Fa1_2TestFull(Quản trị viên, Tạm dừng, Fa1_2, Mint, Ghi, ChangeMetadata): 
 def __init__(self, quản trị viên, siêu dữ liệu, sổ cái, token_metadata): 
 ChangeMetadata.__init__(bản thân)
            Đốt cháy.__init__(bản thân)
            Cây bạc hà.__init__(bản thân)
            Pháp1_2.__init__(bản thân, siêu dữ liệu, sổ cái, token_metadata) 
 Tạm dừng.__init__(bản thân)
            Quản trị viên.__init__(bản thân, quản trị viên) 

 lớp Viewer_nat(sp.Contract): 
 def __init__(self): 
 self.data.last = sp.cast(Không có, sp.option[sp.nat])

        @sp.entrypoint 
 def target(self, params): 
 self.data.last = sp.Some(params) 

 class Viewer_address(sp.Contract): 
 def __init__(self): 
 self.data.last = sp.cast(Không có, sp.option[sp.address])

        @sp.entrypoint 
 def target(self, params): 
 self.data.last = sp.Some(params) 


 nếu "templates" không có trong __name__: 

 @sp.add_test(name="FA12") 
 kiểm tra def(): 
 sc = sp.test_scenario(m)
        sc.h1("Mẫu FA1.2 - Nội dung có thể thay thế") 

 # sp.test_account tạo cặp khóa ED25519 một cách xác định: 
 quản trị viên = sp.test_account("Quản trị viên")
        alice = sp.test_account("Alice")
        bob = sp.test_account("Robert")

        # Hãy hiển thị các tài khoản: 
 sc.h1("Tài khoản") 
 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. siêu dữ liệu_of_url(
            "ipfs://QmaiAUj1FFNGYTu8rLBjc3eeN9cSKwaF8EGMBNDmhzPNFd" 
 ) 

 c1 = m.Fa1_2TestFull( 
 quản trị viên=admin.address, 
 siêu dữ liệu=contract_metadata, 
 token_metadata=token_metadata, 
 sổ cái={}, 
 ) 
 sc += c1 

 sc .h1("Chế độ xem ngoại tuyến - 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("Cố gắng cập nhật siêu dữ liệu") 
 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("Entrypoints") 
 sc.h2("Quản trị viên đúc một vài xu") 
 c1.mint(address=alice.address, value=12).run (người gửi=quản trị viên)
        c1.mint(address=alice.address, value=3).run(sender=admin)
        c1.mint(address=alice.address, value=3).run(sender=admin)
        sc.h2("Alice chuyển cho Bob") 
 c1.transfer(from_=alice.address, to_=bob.địa chỉ, value=4).run(sender=alice)
        sc.verify(c1.data.balances[alice.address].balance == 14) 
 sc.h2("Bob cố gắng chuyển từ Alice nhưng anh ấy không được cô ấy chấp thuận") 
 c1.transfer(from_=alice.address, to_=bob.địa chỉ, value=4).run(
            sender=bob, valid=False 
 ) 
 sc.h2("Alice chấp thuận việc chuyển Bob và Bob") 
 c1.approve(spender=bob.address, value=5).run(sender=alice)
        c1.transfer(from_=alice.address, to_=bob.địa chỉ, value=4).run(sender=bob)
        sc.h2("Bob cố gắng chuyển quá mức từ Alice") 
 c1.transfer(from_=alice.address, to_=bob.địa chỉ, value=4).run(
            người gửi=bob, hợp lệ=False 
 ) 
 sc.h2("Quản trị viên đốt mã thông báo Bob") 
 c1.burn(address=bob.address, value=1).run(sender=admin)
        sc.verify(c1.data.balances[alice.address].balance == 10) 
 sc.h2("Alice cố đốt mã thông báo Bob") 
 c1.burn(address=bob.address, value=1).run(người gửi=alice, valid=False) 
 sc.h2("Quản trị viên tạm dừng hợp đồng và Alice không thể chuyển tiếp nữa") 
 c1.setPause(True).run(sender=admin)
        c1.transfer(from_=alice.address, to_=bob.địa chỉ, value=4).run(
            người gửi=alice, valid=False 
 ) 
 sc.verify(c1.data.balances[alice.address].balance == 10) 
 sc.h2("Truyền quản trị viên trong khi tạm dừng") 
 c1.transfer(from_=alice.address, to_=bob.địa chỉ, value=1).run(sender=admin)
        sc.h2("Quản trị viên hủy tạm dừng hợp đồng và cho phép chuyển khoản") 
 c1.setPause(False).run(sender=admin)
        sc.verify(c1.data.balances[alice.address].balance == 9) 
 c1.transfer(from_=alice.address, to_=bob.địa chỉ, value=1).run(sender=alice)

        sc.verify(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("Lượt xem") 
 sc.h2("Số dư") 
 view_balance = m.Viewer_nat() 
 sc += view_balance 
 target = sp.contract(sp.TNat, view_balance.address, "mục tiêu").open_some() 
 c1.getBalance((alice.address, mục tiêu)) 
 sc.verify_equal(view_balance.data.last, sp.some(8)) 

 sc.h2("Quản trị viên") 
 view_administrator = m.Viewer_address() 
 sc += view_administrator 
 target = sp.contract(
            sp.TAĐịa chỉ, view_administrator.address, "đích" 
 ).open_some() 
 c1.getAdministrator((sp.unit, target)) 
 sc.verify_equal(view_administrator.data.last, sp.some(admin.address))

        sc.h2("Tổng cung") 
 view_totalSupply = m.Viewer_nat() 
 sc += view_totalSupply 
 target = sp.contract(sp.TNat, view_totalSupply.address, "target").open_some() 
 c1.getTotalSupply((sp.unit, target)) 
 sc.verify_equal(view_totalSupply.data.last, sp.some(17)) 

 sc.h2("Allowance") 
 view_allowance = m.Viewer_nat() 
 sc += view_allowance 
 target = sp.contract(sp.TNat, view_allowance.address, "target").open_some() 
 c1.getAllowance((sp.record(owner=alice.address, chi tiêu=bob.address), target)) 
 sc.verify_equal(view_allowance.data.last, sp.some(1))
  1. Chạy Hợp đồng. Bạn sẽ thấy một cái gì đó như thế này

Hợp đồng FA1.2 ban đầu có chức năng cơ bản như chuyển mã thông báo, phê duyệt chuyển khoản, kiểm tra số dư và xem tổng nguồn cung cấp mã thông báo. Bây giờ chúng tôi sẽ nâng cao chức năng này.

  • Quản trị viên: Chúng tôi sẽ giới thiệu hợp đồng cho phép quản trị viên thực hiện các hành động cụ thể, như tạm dừng hợp đồng và ngăn các tài khoản khác sử dụng các chức năng này.
  • Tạm dừng: Tính năng này cho phép chúng tôi tạm dừng và hủy tạm dừng hợp đồng. Khi hợp đồng bị tạm dừng, không ai có thể sử dụng nó ngoại trừ quản trị viên.
  • Mint: Tính năng hợp đồng này cho phép quản trị viên tạo mã thông báo mới.
  • Ghi: Tính năng hợp đồng này cho phép quản trị viên hủy mã thông báo.
  • ChangeMetadata: Tính năng này cho phép quản trị viên cập nhật siêu dữ liệu của hợp đồng.
    Mỗi chức năng này được định nghĩa trong các lớp riêng biệt.

Chúc mừng! Bạn đã tạo mã thông báo có thể thay thế đầu tiên của mình trên Tezos bằng tiêu chuẩn FA1.2!

Trong bài học tiếp theo, chúng ta sẽ học cách tương tác với hợp đồng token mà chúng ta vừa tạo. Điều này sẽ bao gồm chuyển mã thông báo, phê duyệt chuyển mã thông báo và kiểm tra số dư mã thông báo cũng như tổng nguồn cung. Giữ nguyên!

免責聲明
* 投資有風險,入市須謹慎。本課程不作為投資理財建議。
* 本課程由入駐Gate Learn的作者創作,觀點僅代表作者本人,絕不代表Gate Learn讚同其觀點或證實其描述。
目錄
第2課

Tạo mã thông báo đầu tiên của bạn với tiêu chuẩn FA1.2

Trong bài học này, chúng ta sẽ thực hiện quy trình tạo mã thông báo có thể thay thế bằng tiêu chuẩn FA1.2 trên Tezos. Chúng tôi sẽ sử dụng IDE trực tuyến SmartPy để viết và triển khai hợp đồng thông minh của mình. Hãy nhớ rằng tiêu chuẩn FA1.2 chủ yếu được sử dụng cho các mã thông báo có thể thay thế, nghĩa là các mã thông báo có thuộc tính giống hệt nhau và có thể được trao đổi trên cơ sở một đổi một.

Hướng dẫn từng bước một

  1. Truy cập IDE SmartPy

  2. Đầu tiên, hãy mở IDE trực tuyến SmartPy tại https://smartpy.io/ide/. Đây là nền tảng mà chúng tôi sẽ sử dụng để viết, kiểm tra và triển khai các hợp đồng thông minh của mình.

  3. Khởi tạo mẫu FA1.2

  4. Nhấp vào “Mẫu theo loại” trên thanh bên trái và chọn “FA1.2”. Một tab mới sẽ mở ra với mẫu hợp đồng FA1.2. Đây là hợp đồng sẵn sàng sử dụng theo tiêu chuẩn FA1.2.

  5. Tìm hiểu mẫu FA1.2

  6. Mẫu này có chức năng cơ bản dành cho mã thông báo có thể thay thế, bao gồm chuyển mã thông báo, phê duyệt chuyển khoản, kiểm tra số dư và xem tổng nguồn cung cấp mã thông báo. Hợp đồng cũng bao gồm chức năng bổ sung để đúc và đốt token cũng như quản lý quản trị.

  7. Nghiên cứu mẫu này và đảm bảo bạn hiểu các chức năng của nó. Sẽ không sao nếu bạn không hiểu mọi thứ vào thời điểm này, nhưng hãy cố gắng hiểu khái quát về các hoạt động mà hợp đồng này có thể thực hiện.
    Ví dụ: bạn có thể sao chép mã từ mẫu trên SmartPy IDE hoặc ở đây bên dưới:

Python 
 # Fungible Assets - FA12 
 # Lấy cảm hứng từ https://gitlab.com/tzip/tzip/blob/master/A/FA1.2.md 

 import smartpy as sp 


 # Siêu dữ liệu bên dưới chỉ là một ví dụ, nó đóng vai trò là cơ sở, 
 # nội dung được sử dụng để xây dựng siêu dữ liệu JSON mà người dùng 
 # có thể sao chép và tải lên 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(): 
 lớp AdminInterface(sp.Contract): 
 @sp.private(with_storage="read-only") 
 def is_administrator_(self, sender): 
 sp .cast(sp.sender, sp.address) 
 """Không chuẩn, có thể được xác định lại thông qua kế thừa."""
            trả về Đúng 

 lớp CommonInterface(AdminInterface): 
 def __init__(self): 
 AdminInterface.__init__(bản thân)
            self.data.balances = sp.cast(
                sp.big_map(), 
 sp.big_map[ 
 sp.address, 
 sp.record(approvals=sp.map[sp.address, sp.nat], số dư=sp.nat),
                ], 
 ) 
 self.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(approvals=sp.map[sp.address, sp.nat], số dư=sp.nat),
                ], 
 ) 
 self.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): 
 """Không chuẩn, có thể được xác định lại thông qua kế thừa."""
            trả về Sai 

 lớp Fa1_2(CommonInterface): 
 def __init__(self, siêu dữ liệu, sổ cái, token_metadata): 
 """ 
 thông số token_metadata: https://gitlab.com/tzip/tzip/-/blob/master/ đề xuất/tzip-12/tzip-12.md#token-metadata
            Siêu dữ liệu dành riêng cho mã thông báo được lưu trữ/trình bày dưới dạng loại giá trị Michelson (byte chuỗi bản đồ).
            Một số khóa được đặt trước và xác định trước: 

 - "" : Phải tương ứng với URI TZIP-016 trỏ đến biểu diễn JSON của siêu dữ liệu mã thông báo.
            - "name" : Phải là chuỗi UTF-8 cung cấp "tên hiển thị" cho mã thông báo.
            - "biểu tượng" : Phải là chuỗi UTF-8 cho mã định danh ngắn của mã thông báo (ví dụ: XTZ, EUR,…). 
 - "số thập phân" : Phải là số nguyên (được chuyển đổi thành chuỗi UTF-8 ở dạng thập phân) 
 xác định vị trí của dấu thập phân trong số dư mã thông báo cho mục đích hiển thị.

            thông số Contract_metadata: https://gitlab.com/tzip/tzip/-/blob/master/proposals/tzip-16/tzip-16.md 
 """ 
 CommonInterface.__init__(self)
            self.data.metadata = siêu dữ liệu 
 self.data.token_metadata = sp.big_map(
                {0: sp.record(token_id=0, token_info=token_metadata)}
            ) 

 cho chủ sở hữu trong ledger.items():
                self.data.balances[owner.key] = owner.value 
 self.data.total_supply += owner.value.balance 

 # TODO: Kích hoạt khi tính năng này được triển khai.
            # self.init_metadata("siêu dữ liệu", siêu dữ liệu) 

 @sp.entrypoint 
 chuyển def(self, param): 
 sp.cast( 
 param, 
 sp.record(from_=sp.address, to_=sp.địa chỉ, value=sp.nat).layout(
                    ("from_ as from", ("to_ as to", "value")) 
 ), 
 ) 
 Balance_from = self.data.balances.get(
                thông số.from_, mặc định=sp.record(cân bằng=0, phê duyệt={}) 
 ) 
 Balance_to = self.data.balances.get(
                thông số.to_, mặc định=sp.record(cân bằng=0, phê duyệt={}) 
 ) 
 Balance_from.balance = sp.as_nat(
                Balance_from.balance - param.value, error="FA1.2_In EnoughBalance" 
 ) 
 Balance_to.balance += param.value 
 nếu không self.is_administrator_(sp.sender):
                khẳng định không phải self.is_paused_(), "FA1.2_Tạm dừng"
                nếu tham số.from_ != sp.sender: 
 số dư_from.approvals[sp.sender] = sp.as_nat(
                        Balance_from.approvals[sp.sender] - param.value,
                        error="FA1.2_NotAllowed", 
 ) 
 self.data.balances[param.from_] = Balance_from 
 self.data.balances[param.to_] = Balance_to 

 @sp.entrypoint 
 xác nhận phê duyệt(self, param): 
 sp.cast( 
 param, 
 sp.record(spender=sp.address, value=sp.nat).layout(
                    ("người chi tiêu", "giá trị") 
 ), 
 ) 
 khẳng định không phải self.is_paused_(), "FA1.2_Tạm dừng"
            chi tiêu_balance = self.data.balances.get(
                sp.sender, mặc định=sp.record(balance=0, phê duyệt={}) 
 ) 
 đã được phê duyệt = chi tiêu_balance.approvals.get(param.spender, default=0) 
 xác nhận ( 
 đã được phê duyệt == 0 hoặc param.value == 0 
 ), "FA1.2_UnsafeAllowanceChange"
            chi tiêu_balance.approvals[param.spender] = param.value 
 self.data.balances[sp.sender] = chi tiêu_balance 

 @sp.entrypoint 
 def getBalance(self, param): 
 (địa chỉ, gọi lại) = param 
 result = self.data.balances.get(
                địa chỉ, mặc định=sp.record(balance=0, phê duyệt={}) 
 ).balance 
 sp.transfer(result, sp.tez(0), callback) 

 @sp.entrypoint 
 def getAllowance(self, param): 
 (args, callback) = param 
 kết quả = self.data.balances.get(
                args.owner, mặc định=sp.record(cân bằng=0, phê duyệt={}) 
 ).approvals.get(args.spender, default=0) 
 sp.transfer(result, sp.tez(0), callback) 

 @sp.entrypoint 
 def getTotalSupply(self, param): 
 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): 
 """Trả về URI siêu dữ liệu mã thông báo cho mã thông báo đã cho. (token_id phải là 0)."""
            sp.cast(token_id, sp.nat) 
 trả về self.data.token_metadata[token_id]

    ########## 
 # Mixins # 
 ##########

    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

    ########## 
 # Kiểm tra # 
 ########## 

 lớp Fa1_2TestFull(Quản trị viên, Tạm dừng, Fa1_2, Mint, Ghi, ChangeMetadata): 
 def __init__(self, quản trị viên, siêu dữ liệu, sổ cái, token_metadata): 
 ChangeMetadata.__init__(bản thân)
            Đốt cháy.__init__(bản thân)
            Cây bạc hà.__init__(bản thân)
            Pháp1_2.__init__(bản thân, siêu dữ liệu, sổ cái, token_metadata) 
 Tạm dừng.__init__(bản thân)
            Quản trị viên.__init__(bản thân, quản trị viên) 

 lớp Viewer_nat(sp.Contract): 
 def __init__(self): 
 self.data.last = sp.cast(Không có, sp.option[sp.nat])

        @sp.entrypoint 
 def target(self, params): 
 self.data.last = sp.Some(params) 

 class Viewer_address(sp.Contract): 
 def __init__(self): 
 self.data.last = sp.cast(Không có, sp.option[sp.address])

        @sp.entrypoint 
 def target(self, params): 
 self.data.last = sp.Some(params) 


 nếu "templates" không có trong __name__: 

 @sp.add_test(name="FA12") 
 kiểm tra def(): 
 sc = sp.test_scenario(m)
        sc.h1("Mẫu FA1.2 - Nội dung có thể thay thế") 

 # sp.test_account tạo cặp khóa ED25519 một cách xác định: 
 quản trị viên = sp.test_account("Quản trị viên")
        alice = sp.test_account("Alice")
        bob = sp.test_account("Robert")

        # Hãy hiển thị các tài khoản: 
 sc.h1("Tài khoản") 
 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. siêu dữ liệu_of_url(
            "ipfs://QmaiAUj1FFNGYTu8rLBjc3eeN9cSKwaF8EGMBNDmhzPNFd" 
 ) 

 c1 = m.Fa1_2TestFull( 
 quản trị viên=admin.address, 
 siêu dữ liệu=contract_metadata, 
 token_metadata=token_metadata, 
 sổ cái={}, 
 ) 
 sc += c1 

 sc .h1("Chế độ xem ngoại tuyến - 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("Cố gắng cập nhật siêu dữ liệu") 
 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("Entrypoints") 
 sc.h2("Quản trị viên đúc một vài xu") 
 c1.mint(address=alice.address, value=12).run (người gửi=quản trị viên)
        c1.mint(address=alice.address, value=3).run(sender=admin)
        c1.mint(address=alice.address, value=3).run(sender=admin)
        sc.h2("Alice chuyển cho Bob") 
 c1.transfer(from_=alice.address, to_=bob.địa chỉ, value=4).run(sender=alice)
        sc.verify(c1.data.balances[alice.address].balance == 14) 
 sc.h2("Bob cố gắng chuyển từ Alice nhưng anh ấy không được cô ấy chấp thuận") 
 c1.transfer(from_=alice.address, to_=bob.địa chỉ, value=4).run(
            sender=bob, valid=False 
 ) 
 sc.h2("Alice chấp thuận việc chuyển Bob và Bob") 
 c1.approve(spender=bob.address, value=5).run(sender=alice)
        c1.transfer(from_=alice.address, to_=bob.địa chỉ, value=4).run(sender=bob)
        sc.h2("Bob cố gắng chuyển quá mức từ Alice") 
 c1.transfer(from_=alice.address, to_=bob.địa chỉ, value=4).run(
            người gửi=bob, hợp lệ=False 
 ) 
 sc.h2("Quản trị viên đốt mã thông báo Bob") 
 c1.burn(address=bob.address, value=1).run(sender=admin)
        sc.verify(c1.data.balances[alice.address].balance == 10) 
 sc.h2("Alice cố đốt mã thông báo Bob") 
 c1.burn(address=bob.address, value=1).run(người gửi=alice, valid=False) 
 sc.h2("Quản trị viên tạm dừng hợp đồng và Alice không thể chuyển tiếp nữa") 
 c1.setPause(True).run(sender=admin)
        c1.transfer(from_=alice.address, to_=bob.địa chỉ, value=4).run(
            người gửi=alice, valid=False 
 ) 
 sc.verify(c1.data.balances[alice.address].balance == 10) 
 sc.h2("Truyền quản trị viên trong khi tạm dừng") 
 c1.transfer(from_=alice.address, to_=bob.địa chỉ, value=1).run(sender=admin)
        sc.h2("Quản trị viên hủy tạm dừng hợp đồng và cho phép chuyển khoản") 
 c1.setPause(False).run(sender=admin)
        sc.verify(c1.data.balances[alice.address].balance == 9) 
 c1.transfer(from_=alice.address, to_=bob.địa chỉ, value=1).run(sender=alice)

        sc.verify(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("Lượt xem") 
 sc.h2("Số dư") 
 view_balance = m.Viewer_nat() 
 sc += view_balance 
 target = sp.contract(sp.TNat, view_balance.address, "mục tiêu").open_some() 
 c1.getBalance((alice.address, mục tiêu)) 
 sc.verify_equal(view_balance.data.last, sp.some(8)) 

 sc.h2("Quản trị viên") 
 view_administrator = m.Viewer_address() 
 sc += view_administrator 
 target = sp.contract(
            sp.TAĐịa chỉ, view_administrator.address, "đích" 
 ).open_some() 
 c1.getAdministrator((sp.unit, target)) 
 sc.verify_equal(view_administrator.data.last, sp.some(admin.address))

        sc.h2("Tổng cung") 
 view_totalSupply = m.Viewer_nat() 
 sc += view_totalSupply 
 target = sp.contract(sp.TNat, view_totalSupply.address, "target").open_some() 
 c1.getTotalSupply((sp.unit, target)) 
 sc.verify_equal(view_totalSupply.data.last, sp.some(17)) 

 sc.h2("Allowance") 
 view_allowance = m.Viewer_nat() 
 sc += view_allowance 
 target = sp.contract(sp.TNat, view_allowance.address, "target").open_some() 
 c1.getAllowance((sp.record(owner=alice.address, chi tiêu=bob.address), target)) 
 sc.verify_equal(view_allowance.data.last, sp.some(1))
  1. Chạy Hợp đồng. Bạn sẽ thấy một cái gì đó như thế này

Hợp đồng FA1.2 ban đầu có chức năng cơ bản như chuyển mã thông báo, phê duyệt chuyển khoản, kiểm tra số dư và xem tổng nguồn cung cấp mã thông báo. Bây giờ chúng tôi sẽ nâng cao chức năng này.

  • Quản trị viên: Chúng tôi sẽ giới thiệu hợp đồng cho phép quản trị viên thực hiện các hành động cụ thể, như tạm dừng hợp đồng và ngăn các tài khoản khác sử dụng các chức năng này.
  • Tạm dừng: Tính năng này cho phép chúng tôi tạm dừng và hủy tạm dừng hợp đồng. Khi hợp đồng bị tạm dừng, không ai có thể sử dụng nó ngoại trừ quản trị viên.
  • Mint: Tính năng hợp đồng này cho phép quản trị viên tạo mã thông báo mới.
  • Ghi: Tính năng hợp đồng này cho phép quản trị viên hủy mã thông báo.
  • ChangeMetadata: Tính năng này cho phép quản trị viên cập nhật siêu dữ liệu của hợp đồng.
    Mỗi chức năng này được định nghĩa trong các lớp riêng biệt.

Chúc mừng! Bạn đã tạo mã thông báo có thể thay thế đầu tiên của mình trên Tezos bằng tiêu chuẩn FA1.2!

Trong bài học tiếp theo, chúng ta sẽ học cách tương tác với hợp đồng token mà chúng ta vừa tạo. Điều này sẽ bao gồm chuyển mã thông báo, phê duyệt chuyển mã thông báo và kiểm tra số dư mã thông báo cũng như tổng nguồn cung. Giữ nguyên!

免責聲明
* 投資有風險,入市須謹慎。本課程不作為投資理財建議。
* 本課程由入駐Gate Learn的作者創作,觀點僅代表作者本人,絕不代表Gate Learn讚同其觀點或證實其描述。