Zugriff auf die SmartPy-IDE
Öffnen Sie zunächst die SmartPy-Online-IDE unter https://smartpy.io/ide/
. Dies ist die Plattform, die wir zum Schreiben, Testen und Bereitstellen unserer Smart Contracts verwenden werden.
Initiieren der FA1.2-Vorlage
Klicken Sie in der linken Seitenleiste auf „Vorlagen nach Typ“ und wählen Sie „FA1.2“. Es öffnet sich ein neuer Tab mit der FA1.2-Vertragsvorlage. Hierbei handelt es sich um einen gebrauchsfertigen Vertrag, der dem FA1.2-Standard folgt.
Die FA1.2-Vorlage verstehen
Diese Vorlage verfügt über die Grundfunktionen für einen fungiblen Token, einschließlich der Übertragung von Token, der Genehmigung von Übertragungen, der Überprüfung von Guthaben und der Anzeige des gesamten Token-Vorrats. Der Vertrag umfasst außerdem zusätzliche Funktionen zum Prägen und Brennen von Token sowie zum Governance-Management.
Studieren Sie diese Vorlage und stellen Sie sicher, dass Sie ihre Funktionen verstehen. Es ist in Ordnung, wenn Sie an dieser Stelle nicht alles verstehen, aber versuchen Sie, einen allgemeinen Eindruck von den Vorgängen zu bekommen, die dieser Vertrag ausführen kann.
Sie können den Code beispielsweise aus der Vorlage in der SmartPy-IDE oder hier unten kopieren:
Python
# Fungible Assets – FA12
# Inspiriert von https://gitlab.com/tzip/tzip/blob/master/A/FA1.2.md
Smartpy als SP importieren
# Die folgenden Metadaten sind nur ein Beispiel dient als Basis,
# der Inhalt wird zum Erstellen der Metadaten-JSON verwendet, die Benutzer
# kopieren und auf IPFS hochladen können.
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():
class AdminInterface(sp.Contract):
@sp.private(with_storage="read-only")
def is_administrator_(self, sender):
sp .cast(sp.sender, sp.address)
"""Nicht Standard, kann durch Vererbung neu definiert werden."""
return True
class CommonInterface(AdminInterface):
def __init__(self):
AdminInterface.__init__(selbst)
self.data.balances = sp.cast(
sp.big_map(),
sp.big_map[
sp.address,
sp.record(approvals=sp.map[sp.address, sp.nat], balance=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], balance=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):
"""Nicht Standard, kann durch Vererbung neu definiert werden."""
return False
class Fa1_2(CommonInterface):
def __init__(self, metadata, ledger, token_metadata):
"""
token_metadata spec: https://gitlab.com/tzip/tzip/-/blob/master/ Proposals/tzip-12/tzip-12.md#token-metadata
Tokenspezifische Metadaten werden als Michelson-Wert vom Typ (Mapping-String-Bytes) gespeichert/dargestellt.
Einige der Schlüssel sind reserviert und vordefiniert:
– „“: Sollte einem TZIP-016-URI entsprechen, der auf eine JSON-Darstellung der Token-Metadaten verweist.
- „Name“: Sollte eine UTF-8-Zeichenfolge sein, die dem Token einen „Anzeigenamen“ gibt.
- „Symbol“: Sollte eine UTF-8-Zeichenfolge für die Kurzkennung des Tokens sein (z. B XTZ, EUR, …).
– „Dezimalzahlen“: Sollte eine Ganzzahl sein (konvertiert in eine UTF-8-Zeichenfolge im Dezimalformat)
, die die Position des Dezimalpunkts in Token-Salden für Anzeigezwecke definiert.
Contract_Metadata-Spezifikation: https://gitlab.com/tzip/tzip/-/blob/master/proposals/tzip-16/tzip-16.md
„““
CommonInterface.__init__(selbst)
self.data.metadata = Metadaten
self.data.token_metadata = sp.big_map(
{0: sp.record(token_id=0, token_info=token_metadata)}
)
für Besitzer in ledger.items():
self.data.balances[owner.key] = Eigentümer.Wert
self.data.total_supply +=owner.value.balance
# TODO: Aktivieren, wenn diese Funktion implementiert ist.
# self.init_metadata("metadata", Metadaten)
@sp.entrypoint
def transfer(self, param):
sp.cast(
param,
sp.record(from_=sp.address, to_=sp.address, value=sp.nat).layout(
(„from_ as from“, („to_ as to“, „value“))
),
)
balance_from = self.data.balances.get(
param.from_, default=sp.record(balance=0, Approvals={})
)
balance_to = self.data.balances.get(
param.to_, default=sp.record(balance=0, genehmigt={})
)
balance_from.balance = sp.as_nat(
balance_from.balance – param.value, error="FA1.2_InsufficientBalance"
)
balance_to.balance += param.value
wenn nicht self.is_administrator_(sp.sender):
behaupten nicht self.is_paused_(), „FA1.2_Paused“
wenn param.from_ != sp.sender:
balance_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
def genehmigt(self, param):
sp.cast(
param,
sp.record(spender=sp.address, value=sp.nat).layout(
("spender", "value")
),
)
activate not self.is_paused_(), „FA1.2_Paused“
spender_balance = self.data.balances.get(
sp.sender, default=sp.record(balance=0, Approvals={})
)
bereitsApproved = spender_balance.approvals.get(param.spender, Standard = 0)
Assert (
bereits Approved == 0 oder param.value == 0
), "FA1.2_UnsafeAllowanceChange"
spender_balance.approvals[param.spender] = param.value
self.data.balances[sp.sender] = spender_balance
@sp.entrypoint
def getBalance(self, param):
(address, callback) = param
result = self.data.balances.get(
Adresse, default=sp.record(balance=0, Approvals={})
).balance
sp.transfer(result, sp.tez(0), callback)
@sp.entrypoint
def getAllowance(self, param):
(args, callback) = param
result = self.data.balances.get(
args.owner, default=sp.record(balance=0, Approvals={})
).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):
"""Gibt den Token-Metadaten-URI für das angegebene Token zurück. (token_id muss 0 sein)."""
sp.cast(token_id, sp.nat)
return 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
##########
# Tests #
##########
Klasse Fa1_2TestFull(Admin, Pause, Fa1_2, Mint, Burn, ChangeMetadata):
def __init__(selbst, Administrator, Metadaten, Ledger, token_metadata):
ChangeMetadata.__init__(selbst)
Brennen.__init__(selbst)
Minze.__init__(selbst)
Fa1_2.__init__(selbst, metadata, ledger, token_metadata)
Pause.__init__(selbst)
Administrator.__init__(selbst, Administrator)
Klasse Viewer_nat(sp.Contract):
def __init__(self):
self.data.last = sp.cast(None, 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(None, sp.option[sp.address])
@sp.entrypoint
def target(self, params):
self.data.last = sp.Some(params)
if „templates“ not in __name__:
@sp.add_test(name="FA12")
def test():
sc = sp.test_scenario(m)
sc.h1("FA1.2-Vorlage - Fungible Assets")
# sp.test_account generiert deterministisch ED25519-Schlüsselpaare:
admin = sp.test_account("Administrator")
alice = sp.test_account("Alice")
bob = sp.test_account("Robert")
# Lassen Sie uns die Konten anzeigen:
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"
),
}
5tract_metadata = sp.utils. metadata_of_url(
„ipfs://QmaiAUj1FFNGYTu8rLBjc3eeN9cSKwaF8EGMBNDmhzPNFd“
)
c1 = m.Fa1_2TestFull(
Administrator=admin.address,
metadata=contract_metadata,
token_metadata=token_metadata,
ledger={},
)
sc += c1
sc .h1("Offchain-Ansicht - 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("Versuch, Metadaten zu aktualisieren")
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("Admin prägt ein paar Münzen")
c1.mint(address=alice.address, value=12).run (Absender=Administrator)
c1.mint(address=alice.address, value=3).run(sender=admin)
c1.mint(address=alice.address, value=3).run(sender=admin)
sc.h2("Alice wechselt zu Bob")
c1.transfer(from_=alice.address, to_=bob.address, value=4).run(sender=alice)
sc.verify(c1.data.balances[alice.address].balance == 14)
sc.h2("Bob versucht, von Alice zu übertragen, aber er hat nicht ihre Zustimmung")
c1.transfer(from_=alice.address, to_=bob.address, value=4).run(
sender=bob, valid=False
)
sc.h2("Alice genehmigt Bob- und Bob-Transfers")
c1.approve(spender=bob.address, value=5).run(sender=alice)
c1.transfer(from_=alice.address, to_=bob.address, value=4).run(sender=bob)
sc.h2("Bob versucht, von Alice zu viel zu übertragen")
c1.transfer(from_=alice.address, to_=bob.address, value=4).run(
sender=bob, valid=False
)
sc.h2("Admin verbrennt Bob-Token")
c1.burn(address=bob.address, value=1).run(sender=admin)
sc.verify(c1.data.balances[alice.address].balance == 10)
sc.h2("Alice versucht, Bob-Token zu verbrennen")
c1.burn(address=bob.address, value=1).run(sender=alice, valid=False)
sc.h2("Admin pausiert den Vertrag und Alice kann nicht mehr übertragen")
c1.setPause(True).run(sender=admin)
c1.transfer(from_=alice.address, to_=bob.address, value=4).run(
sender=alice, valid=False
)
sc.verify(c1.data.balances[alice.address].balance == 10)
sc.h2("Admin transfers while on pause")
c1.transfer(from_=alice.address, to_=bob.address, value=1).run(sender=admin)
sc.h2("Der Administrator hebt die Pause des Vertrags auf und Übertragungen sind zulässig")
c1.setPause(False).run(sender=admin)
sc.verify(c1.data.balances[alice.address].balance == 9)
c1.transfer(from_=alice.address, to_=bob.address, 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("Views")
sc.h2("Balance")
view_balance = m.Viewer_nat()
sc += view_balance
target = sp.contract(sp.TNat, view_balance.address, "target").open_some()
c1.getBalance((alice.address, Ziel))
sc.verify_equal(view_balance.data.last, sp.some(8))
sc.h2("Administrator")
view_administrator = m.Viewer_address()
sc += view_administrator
target = sp.contract(
sp.TAddress, view_administrator.address, "target"
).open_some()
c1.getAdministrator((sp.unit, target))
sc.verify_equal(view_administrator.data.last, sp.some(admin.address))
sc.h2("Total Supply")
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, spender=bob.address), target))
sc.verify_equal(view_allowance.data.last, sp.some(1))
Der ursprüngliche FA1.2-Vertrag verfügt über grundlegende Funktionen wie die Übertragung von Token, die Genehmigung von Übertragungen, die Überprüfung von Guthaben und die Anzeige des Gesamtbestands an Token. Jetzt werden wir diese Funktionalität erweitern.
Glückwunsch! Sie haben Ihren ersten fungiblen Token auf Tezos mit dem FA1.2-Standard erstellt!
In der nächsten Lektion lernen wir, wie wir mit dem soeben erstellten Token-Vertrag interagieren. Dazu gehören die Übertragung von Token, die Genehmigung von Token-Übertragungen sowie die Überprüfung des Token-Guthabens und des Gesamtvorrats. Bleiben Sie dran!
Zugriff auf die SmartPy-IDE
Öffnen Sie zunächst die SmartPy-Online-IDE unter https://smartpy.io/ide/
. Dies ist die Plattform, die wir zum Schreiben, Testen und Bereitstellen unserer Smart Contracts verwenden werden.
Initiieren der FA1.2-Vorlage
Klicken Sie in der linken Seitenleiste auf „Vorlagen nach Typ“ und wählen Sie „FA1.2“. Es öffnet sich ein neuer Tab mit der FA1.2-Vertragsvorlage. Hierbei handelt es sich um einen gebrauchsfertigen Vertrag, der dem FA1.2-Standard folgt.
Die FA1.2-Vorlage verstehen
Diese Vorlage verfügt über die Grundfunktionen für einen fungiblen Token, einschließlich der Übertragung von Token, der Genehmigung von Übertragungen, der Überprüfung von Guthaben und der Anzeige des gesamten Token-Vorrats. Der Vertrag umfasst außerdem zusätzliche Funktionen zum Prägen und Brennen von Token sowie zum Governance-Management.
Studieren Sie diese Vorlage und stellen Sie sicher, dass Sie ihre Funktionen verstehen. Es ist in Ordnung, wenn Sie an dieser Stelle nicht alles verstehen, aber versuchen Sie, einen allgemeinen Eindruck von den Vorgängen zu bekommen, die dieser Vertrag ausführen kann.
Sie können den Code beispielsweise aus der Vorlage in der SmartPy-IDE oder hier unten kopieren:
Python
# Fungible Assets – FA12
# Inspiriert von https://gitlab.com/tzip/tzip/blob/master/A/FA1.2.md
Smartpy als SP importieren
# Die folgenden Metadaten sind nur ein Beispiel dient als Basis,
# der Inhalt wird zum Erstellen der Metadaten-JSON verwendet, die Benutzer
# kopieren und auf IPFS hochladen können.
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():
class AdminInterface(sp.Contract):
@sp.private(with_storage="read-only")
def is_administrator_(self, sender):
sp .cast(sp.sender, sp.address)
"""Nicht Standard, kann durch Vererbung neu definiert werden."""
return True
class CommonInterface(AdminInterface):
def __init__(self):
AdminInterface.__init__(selbst)
self.data.balances = sp.cast(
sp.big_map(),
sp.big_map[
sp.address,
sp.record(approvals=sp.map[sp.address, sp.nat], balance=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], balance=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):
"""Nicht Standard, kann durch Vererbung neu definiert werden."""
return False
class Fa1_2(CommonInterface):
def __init__(self, metadata, ledger, token_metadata):
"""
token_metadata spec: https://gitlab.com/tzip/tzip/-/blob/master/ Proposals/tzip-12/tzip-12.md#token-metadata
Tokenspezifische Metadaten werden als Michelson-Wert vom Typ (Mapping-String-Bytes) gespeichert/dargestellt.
Einige der Schlüssel sind reserviert und vordefiniert:
– „“: Sollte einem TZIP-016-URI entsprechen, der auf eine JSON-Darstellung der Token-Metadaten verweist.
- „Name“: Sollte eine UTF-8-Zeichenfolge sein, die dem Token einen „Anzeigenamen“ gibt.
- „Symbol“: Sollte eine UTF-8-Zeichenfolge für die Kurzkennung des Tokens sein (z. B XTZ, EUR, …).
– „Dezimalzahlen“: Sollte eine Ganzzahl sein (konvertiert in eine UTF-8-Zeichenfolge im Dezimalformat)
, die die Position des Dezimalpunkts in Token-Salden für Anzeigezwecke definiert.
Contract_Metadata-Spezifikation: https://gitlab.com/tzip/tzip/-/blob/master/proposals/tzip-16/tzip-16.md
„““
CommonInterface.__init__(selbst)
self.data.metadata = Metadaten
self.data.token_metadata = sp.big_map(
{0: sp.record(token_id=0, token_info=token_metadata)}
)
für Besitzer in ledger.items():
self.data.balances[owner.key] = Eigentümer.Wert
self.data.total_supply +=owner.value.balance
# TODO: Aktivieren, wenn diese Funktion implementiert ist.
# self.init_metadata("metadata", Metadaten)
@sp.entrypoint
def transfer(self, param):
sp.cast(
param,
sp.record(from_=sp.address, to_=sp.address, value=sp.nat).layout(
(„from_ as from“, („to_ as to“, „value“))
),
)
balance_from = self.data.balances.get(
param.from_, default=sp.record(balance=0, Approvals={})
)
balance_to = self.data.balances.get(
param.to_, default=sp.record(balance=0, genehmigt={})
)
balance_from.balance = sp.as_nat(
balance_from.balance – param.value, error="FA1.2_InsufficientBalance"
)
balance_to.balance += param.value
wenn nicht self.is_administrator_(sp.sender):
behaupten nicht self.is_paused_(), „FA1.2_Paused“
wenn param.from_ != sp.sender:
balance_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
def genehmigt(self, param):
sp.cast(
param,
sp.record(spender=sp.address, value=sp.nat).layout(
("spender", "value")
),
)
activate not self.is_paused_(), „FA1.2_Paused“
spender_balance = self.data.balances.get(
sp.sender, default=sp.record(balance=0, Approvals={})
)
bereitsApproved = spender_balance.approvals.get(param.spender, Standard = 0)
Assert (
bereits Approved == 0 oder param.value == 0
), "FA1.2_UnsafeAllowanceChange"
spender_balance.approvals[param.spender] = param.value
self.data.balances[sp.sender] = spender_balance
@sp.entrypoint
def getBalance(self, param):
(address, callback) = param
result = self.data.balances.get(
Adresse, default=sp.record(balance=0, Approvals={})
).balance
sp.transfer(result, sp.tez(0), callback)
@sp.entrypoint
def getAllowance(self, param):
(args, callback) = param
result = self.data.balances.get(
args.owner, default=sp.record(balance=0, Approvals={})
).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):
"""Gibt den Token-Metadaten-URI für das angegebene Token zurück. (token_id muss 0 sein)."""
sp.cast(token_id, sp.nat)
return 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
##########
# Tests #
##########
Klasse Fa1_2TestFull(Admin, Pause, Fa1_2, Mint, Burn, ChangeMetadata):
def __init__(selbst, Administrator, Metadaten, Ledger, token_metadata):
ChangeMetadata.__init__(selbst)
Brennen.__init__(selbst)
Minze.__init__(selbst)
Fa1_2.__init__(selbst, metadata, ledger, token_metadata)
Pause.__init__(selbst)
Administrator.__init__(selbst, Administrator)
Klasse Viewer_nat(sp.Contract):
def __init__(self):
self.data.last = sp.cast(None, 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(None, sp.option[sp.address])
@sp.entrypoint
def target(self, params):
self.data.last = sp.Some(params)
if „templates“ not in __name__:
@sp.add_test(name="FA12")
def test():
sc = sp.test_scenario(m)
sc.h1("FA1.2-Vorlage - Fungible Assets")
# sp.test_account generiert deterministisch ED25519-Schlüsselpaare:
admin = sp.test_account("Administrator")
alice = sp.test_account("Alice")
bob = sp.test_account("Robert")
# Lassen Sie uns die Konten anzeigen:
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"
),
}
5tract_metadata = sp.utils. metadata_of_url(
„ipfs://QmaiAUj1FFNGYTu8rLBjc3eeN9cSKwaF8EGMBNDmhzPNFd“
)
c1 = m.Fa1_2TestFull(
Administrator=admin.address,
metadata=contract_metadata,
token_metadata=token_metadata,
ledger={},
)
sc += c1
sc .h1("Offchain-Ansicht - 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("Versuch, Metadaten zu aktualisieren")
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("Admin prägt ein paar Münzen")
c1.mint(address=alice.address, value=12).run (Absender=Administrator)
c1.mint(address=alice.address, value=3).run(sender=admin)
c1.mint(address=alice.address, value=3).run(sender=admin)
sc.h2("Alice wechselt zu Bob")
c1.transfer(from_=alice.address, to_=bob.address, value=4).run(sender=alice)
sc.verify(c1.data.balances[alice.address].balance == 14)
sc.h2("Bob versucht, von Alice zu übertragen, aber er hat nicht ihre Zustimmung")
c1.transfer(from_=alice.address, to_=bob.address, value=4).run(
sender=bob, valid=False
)
sc.h2("Alice genehmigt Bob- und Bob-Transfers")
c1.approve(spender=bob.address, value=5).run(sender=alice)
c1.transfer(from_=alice.address, to_=bob.address, value=4).run(sender=bob)
sc.h2("Bob versucht, von Alice zu viel zu übertragen")
c1.transfer(from_=alice.address, to_=bob.address, value=4).run(
sender=bob, valid=False
)
sc.h2("Admin verbrennt Bob-Token")
c1.burn(address=bob.address, value=1).run(sender=admin)
sc.verify(c1.data.balances[alice.address].balance == 10)
sc.h2("Alice versucht, Bob-Token zu verbrennen")
c1.burn(address=bob.address, value=1).run(sender=alice, valid=False)
sc.h2("Admin pausiert den Vertrag und Alice kann nicht mehr übertragen")
c1.setPause(True).run(sender=admin)
c1.transfer(from_=alice.address, to_=bob.address, value=4).run(
sender=alice, valid=False
)
sc.verify(c1.data.balances[alice.address].balance == 10)
sc.h2("Admin transfers while on pause")
c1.transfer(from_=alice.address, to_=bob.address, value=1).run(sender=admin)
sc.h2("Der Administrator hebt die Pause des Vertrags auf und Übertragungen sind zulässig")
c1.setPause(False).run(sender=admin)
sc.verify(c1.data.balances[alice.address].balance == 9)
c1.transfer(from_=alice.address, to_=bob.address, 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("Views")
sc.h2("Balance")
view_balance = m.Viewer_nat()
sc += view_balance
target = sp.contract(sp.TNat, view_balance.address, "target").open_some()
c1.getBalance((alice.address, Ziel))
sc.verify_equal(view_balance.data.last, sp.some(8))
sc.h2("Administrator")
view_administrator = m.Viewer_address()
sc += view_administrator
target = sp.contract(
sp.TAddress, view_administrator.address, "target"
).open_some()
c1.getAdministrator((sp.unit, target))
sc.verify_equal(view_administrator.data.last, sp.some(admin.address))
sc.h2("Total Supply")
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, spender=bob.address), target))
sc.verify_equal(view_allowance.data.last, sp.some(1))
Der ursprüngliche FA1.2-Vertrag verfügt über grundlegende Funktionen wie die Übertragung von Token, die Genehmigung von Übertragungen, die Überprüfung von Guthaben und die Anzeige des Gesamtbestands an Token. Jetzt werden wir diese Funktionalität erweitern.
Glückwunsch! Sie haben Ihren ersten fungiblen Token auf Tezos mit dem FA1.2-Standard erstellt!
In der nächsten Lektion lernen wir, wie wir mit dem soeben erstellten Token-Vertrag interagieren. Dazu gehören die Übertragung von Token, die Genehmigung von Token-Übertragungen sowie die Überprüfung des Token-Guthabens und des Gesamtvorrats. Bleiben Sie dran!