Truy cập IDE SmartPy
Đầ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.
Khởi tạo mẫu FA1.2
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.
Tìm hiểu mẫu FA1.2
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ị.
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))
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.
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!
Truy cập IDE SmartPy
Đầ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.
Khởi tạo mẫu FA1.2
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.
Tìm hiểu mẫu FA1.2
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ị.
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))
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.
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!