Aceder ao SmartPy IDE
Primeiro, abra o IDE online SmartPy em https://smartpy.io/ide/
. Esta é a plataforma que usaremos para escrever, testar e implementar os nossos contratos inteligentes.
Iniciar o modelo FA1.2
Clique em “Modelos por Tipo” na barra lateral esquerda e seleccione “FA1.2”. Um novo separador será aberto com o modelo de contrato FA1.2. Este é um contrato pronto a usar que segue o padrão FA1.2.
Compreender o modelo FA1.2
Este modelo tem a funcionalidade básica para um token fungível, que inclui a transferência de tokens, a aprovação de transferências, a verificação de saldos e a visualização da oferta total de tokens. O contrato também inclui funcionalidades adicionais para cunhar e queimar tokens, bem como para gestão de governança.
Estude este modelo e certifique-se de que compreende as suas funcionalidades. Está tudo bem se não perceber tudo neste momento, mas tente ter uma noção geral das operações que este contrato pode realizar.
Por exemplo, pode copiar o código do modelo no IDE SmartPy ou aqui abaixo:
Python
# Ativos Fungíveis - FA12
# Inspirado em https://gitlab.com/tzip/tzip/blob/master/A/FA1.2.md
importar de forma inteligente como sp
# Os metadados abaixo são apenas um exemplo, servem de base,
# o conteúdo é usado para construir os metadados JSON que os utilizadores
# pode copiar e fazer upload para o 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 .módulo
def m ():
classe AdminInterface (SP.Contract):
@sp .private ("with_storage= apenas leitura) "
def é_administrador_ (próprio, remetente):
sp.cast (sp.remetente, sp.endereço)
"""Não é normal, pode ser redefinido através de herança. """
retorno Verdadeiro
classe CommonInterface (AdminInterface):
def __init__(self):
Interface de administrador.__init__(auto)
self.data.balances = sp.cast (
sp.big_map (),
sp.big_map [
sp.endereço,
sp.record (aprovações=sp.map [sp.address, sp.nat], saldo = 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.endereço,
sp.record (aprovações=sp.map [sp.address, sp.nat], saldo = 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= apenas leitura) "
def é_pausada_ (self):
"""Não é normal, pode ser redefinido através de herança. """
devolver Falso
classe Fa1_2 (CommonInterface):
def __init__(self, metadados, livro-razão, token_metadata):
"""
especificação de token_metadata: https://gitlab.com/tzip/tzip/-/blob/master/proposals/tzip-12/tzip-12.md#token-metadata
Os metadados específicos do Token são armazenados/apresentados como um valor de Michelson do tipo (bytes da string do mapa).
Algumas das chaves estão reservadas e predefinidas:
- "": Deve corresponder a um URI TZIP-016 que aponta para uma representação JSON dos metadados do token.
- " nome": Deve ser uma string UTF-8 dando um nome de " exibição " ao token.
- " símbolo": Deve ser uma string UTF-8 para o identificador curto do token (ex. XTZ, EUR,...).
- " decimais": Deve ser um número inteiro (convertido numa string UTF-8 em decimal)
que define a posição do ponto decimal nos saldos de token para fins de exibição.
especificação contract_metadata: https://gitlab.com/tzip/tzip/-/blob/master/proposals/tzip-16/tzip-16.md
"""
Interface comum.__init__(auto)
self.data.metadata = metadados
self.data.token_metadata = sp.big_map (
{0: sp.record(token_id=0, token_info=token_metadata)}
)
para o proprietário em ledger.items ():
self.data.balances [owner.key] = proprietário.value
self.data.total_supply += proprietário.value.balance
# TODO: Ativar quando esta funcionalidade for implementada.
# self.init_metadata (metadados, " " metadados)
@sp .ponto de entrada
transferência def (self, param):
sp.cast (
param,
sp.record (de_=sp.address, para_=sp.endereço, value=sp.nat) .layout (
("de_ a partir de", ("a_ quanto a", " valor"))
),
)
balance_from = self.data.balances.get (
param.de_, padrão = sp.record (saldo = 0, aprovações= {})
)
balance_to = self.data.balances.get (
param.to_, padrão = sp.record (saldo = 0, aprovações= {})
)
saldo de.balance = sp.as_nat (
balance_from.balance - param.value, erro = FA1.2_Saldo insuficiente " "
)
saldo para.saldo += param.valor
se não auto.is_administrator_ (sp.sender):
afirme não a si mesmo. is_paused_ (), "FA1.2_Pausado "
se param.de_ ! = sp.remetente:
saldo de.aprovações [sp.sender] = sp.as_nat (
balance_from.aprovações [sp.sender] - param.value,
erro = " FA1.2_Não permitido, "
)
self.data.balances [param.from_] = saldo_de
self.data.balances [param.to_] = saldo_para
@sp .ponto de entrada
aprovar def (self, param):
sp.cast (
param,
sp.record (spender=sp.address, value=sp.nat) .layout (
("gastador", " valor")
),
)
afirme não a si mesmo. is_paused_ (), "FA1.2_Pausado "
spender_balance = self.data.balances.get (
sp.remetente, predefinição=sp.record (saldo = 0, aprovações= {})
)
Já aprovado = spender_balance.aprovados.get (param.spender, padrão = 0)
afirmar (
Já aprovado == 0 ou param.value == 0
), " FA1.2 _Alteração de subsídio não seguro "
spender_balance.aprovações [param.spender] = parâmetro. valor
self.data.balances [sp.sender] = saldo_gastador_saldo
@sp .ponto de entrada
def GetBalance (self, param):
(endereço, chamada de retorno) = param
resultado = self.data.balances.get (
endereço, default=sp.record (saldo = 0, aprovações= {})
) .saldo
sp.transfer (resultado, sp.tez (0), retorno de chamada)
@sp .ponto de entrada
def getAllovenance (self, param):
(args, callback) = param
resultado = self.data.balances.get (
args.proprietário, padrão = sp.record (saldo = 0, aprovações= {})
) .aprovações.get (args.spender, padrão = 0)
sp.transfer (resultado, sp.tez (0), retorno de chamada)
@sp .ponto de entrada
def getTotalSupply (self, param):
sp.cast (param, sp.pair [sp.unit, sp.contrato [sp.nat]])
sp.transfer (self.data.total_supply, sp.tez (0), sp.and (param))
@sp .offchain_view ()
def token_metadata (self, token_id):
"""Devolver o URI dos metadados do token fornecido. (token_id deve ser 0). """
sp.cast (token_id, sp.nat)
devolver self.data.token_metadata [token_id]
##########
# Misturas #
##################
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
### Testes #
##########
classe FA1_2TestFull (Admin, Pausa, Fa1_2, Mint, Burn, ChangeMetadata):
def __init__(auto, administrador, metadados, livro-razão, token_metadata):
Alterar metadados.__init__(auto)
Queimar.__init__(eu)
Hortelã.__init__(auto)
Fa1_2.__init__(eu, metadados, livro-razão, token_metadata)
Pausa.__init__(auto)
Administrador.__init__(eu, administrador)
classe Viewer_NAT (SP.Contract):
def __init__(self):
self.data.last = sp.cast (Nenhum, sp.option [sp.nat])
@sp .ponto de entrada
def alvo (self, parâmetros):
self.data.last = Sp.Alguns (parâmetros)
turma Viewer_address (SP.Contract):
def __init__(self):
self.data.last = sp.cast (Nenhum, sp.option [sp.address])
@sp .ponto de entrada
alvo def (self, parâmetros):
self.data.last = SP.Alguns (parâmetros)
se " os modelos " não estiverem em __name__:
@sp .add_test (nome = FA12) " "
teste def ():
sc = sp.test_scenario (m)
sc.h1 (modelo " FA1.2 - Ativos fungíveis) "
# sp.test_account gera ED25519 pares de chaves deterministicamente:
admin = sp.test_account (Administrador) " "
alice = sp.test_account (Alice) " "
bob = sp.test_account (Robert) " "
# Vamos exibir as contas:
sc.h1 (Contas) " "
sc.show ([admin, alice, bob])
sc.h1 (Contrato) " "
token_metadados = {
"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_metadados = sp.utils.metadata_of_url (
"ipfs: //qmaiaUJ1FFngYTU8RLBJC3een9CSkwaf8EGmbNdmHzpNfd "
)
c1 = m.FA1_2TestCompleto (
administrator=admin.address,
metadata=contract_metadata,
token_metadata=token_metadata,
livro-razão = {},
)
sc += c1
sc.h1 (Vista " fora da cadeia - 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 ("Tentativa de actualizar metadados) "
sc.verificar (
c1.data.metadados [] " "
== sp.utils.bytes_of_string (
"ipfs: //qmaiaUJ1FFngYTU8RLBJC3EEN9CSkwaf8EGmbNdmHzpNfd "
)
)
c1.update_metadata (chave =, valor = sp.bytes (0x00)) .run (remetente = " " administrador) " "
sc.verify (c1.data.metadata [] " " == sp.bytes (0x00)) " "
sc.h1 (Pontos de entrada) " "
sc.h2 (O " administrador cunha algumas moedas) "
c1.mint (endereço=alice.address, value=12) .run (remetente=admin)
c1.mint (endereço=alice.address, value=3) .run (remetente = admin)
c1.mint (endereço=alice.address, value=3) .run (remetente = admin)
sc.h2 ("Alice transfere para Bob) "
c1.transfer (de_=alice.address, para_=bob.address, valor=4) .run (remetente = alice)
sc.verify (c1.data.balances [alice.address] .balance == 14)
sc.h2 ("Bob tenta transferir-se de Alice mas não tem a aprovação dela) "
c1.transfer (de_=alice.address, para_=bob.address, valor = 4) .run (
remetente = bob, válido = falso
)
sc.h2 ("Alice aprova transferências de Bob e Bob) "
c1.approve (spender=bob.address, value=5) .run (remetente = alice)
c1.transfer (de_=alice.address, para_=bob.address, valor=4) .run (remetente = bob)
sc.h2 ("Bob tenta transferir em excesso de Alice) "
c1.transfer (de_=alice.address, para_=bob.address, valor = 4) .run (
remetente = bob, válido = falso
)
sc.h2 (O " administrador queima o token Bob) "
c1.burn (endereço=bob.address, valor=1) .run (remetente = administrador)
sc.verify (c1.data.balances [alice.address] .balance == 10)
sc.h2 ("Alice tenta queimar o token Bob) "
c1.burn (endereço=bob.address, value=1) .run (remetente = alice, Válido = falso)
sc.h2 (O " administrador pausa o contrato e Alice não pode mais transferir) "
c1.setPause (Verdadeiro) .run (remetente = admin)
c1.transfer (de_=alice.address, para_=bob.address, valor = 4) .run (
remetente = Alice, Válido = Falso
)
sc.verify (c1.data.balances [alice.address] .balance == 10)
sc.h2 (O " administrador transfere enquanto está em pausa) "
c1.transfer (de_=alice.address, para_=bob.address, valor=1) .run (remetente = administrador)
sc.h2 (O " administrador anula o contrato e as transferências são permitidas) "
c1.setPause (Falso) .run (remetente = admin)
sc.verify (c1.data.balances [alice.address] .balance == 9)
c1.transfer (de_=alice.address, para_=bob.address, valor = 1) .run (remetente = 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 (Visualizações) " "
sc.h2 (Equilíbrio) " "
view_balance = m.Viewer_NAT ()
sc += visão_equilíbrio
alvo = sp.contract (SP.tnat, view_balance.address, "alvo") .open_some ()
c1.GetBalance ((alice.address, alvo))
sc.verify_equal (ver_balance.data.last, sp.alguns (8))
sc.h2 (Administrador) " "
visualizar_administrador = m.Viewer_address ()
sc += visualizar_administrador
alvo = sp.contrato (
Endereço SP.T, view_administrator.address, "alvo "
) .open_some ()
c1.GetAdministrator ((sp.unit, alvo))
sc.verify_equal (ver_administrator.data.last, sp.some (admin.address))
sc.h2 (Fornecimento Total) " "
View_totalSupply = m.Viewer_NAT ()
sc += Visualizar_TotalSupply
alvo = sp.contract (SP.tnat, view_totalsupply.address, "alvo") .open_some ()
c1.GetTotalSupply ((sp.unidade, destino))
sc.verify_equal (View_totalsupply.data.last, sp.alguns (17))
sc.h2 (Subsídio) " "
view_allowance = m.Viewer_NAT ()
sc += view_allowance
alvo = sp.contract (sp.tnat, view_allowance.address, "alvo") .open_some ()
c1.getAllance ((sp.record (owner=alice.address, spender=bob.address), alvo))
sc.verify_equal (ver_allowance.data.last, sp.alguns (1))
O contrato FA1.2 original tem funcionalidades básicas como transferir tokens, aprovar transferências, verificar saldos e visualizar o fornecimento total de tokens. Agora vamos melhorar esta funcionalidade.
Parabéns! Criou o seu primeiro token fungível no Tezos usando o padrão FA1.2!
Na próxima lição, aprenderemos a interagir com o contrato de token que acabamos de criar. Isso incluirá a transferência de tokens, a aprovação de transferências de token e a verificação do saldo do token e da oferta total. Fiquem atentos!
Aceder ao SmartPy IDE
Primeiro, abra o IDE online SmartPy em https://smartpy.io/ide/
. Esta é a plataforma que usaremos para escrever, testar e implementar os nossos contratos inteligentes.
Iniciar o modelo FA1.2
Clique em “Modelos por Tipo” na barra lateral esquerda e seleccione “FA1.2”. Um novo separador será aberto com o modelo de contrato FA1.2. Este é um contrato pronto a usar que segue o padrão FA1.2.
Compreender o modelo FA1.2
Este modelo tem a funcionalidade básica para um token fungível, que inclui a transferência de tokens, a aprovação de transferências, a verificação de saldos e a visualização da oferta total de tokens. O contrato também inclui funcionalidades adicionais para cunhar e queimar tokens, bem como para gestão de governança.
Estude este modelo e certifique-se de que compreende as suas funcionalidades. Está tudo bem se não perceber tudo neste momento, mas tente ter uma noção geral das operações que este contrato pode realizar.
Por exemplo, pode copiar o código do modelo no IDE SmartPy ou aqui abaixo:
Python
# Ativos Fungíveis - FA12
# Inspirado em https://gitlab.com/tzip/tzip/blob/master/A/FA1.2.md
importar de forma inteligente como sp
# Os metadados abaixo são apenas um exemplo, servem de base,
# o conteúdo é usado para construir os metadados JSON que os utilizadores
# pode copiar e fazer upload para o 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 .módulo
def m ():
classe AdminInterface (SP.Contract):
@sp .private ("with_storage= apenas leitura) "
def é_administrador_ (próprio, remetente):
sp.cast (sp.remetente, sp.endereço)
"""Não é normal, pode ser redefinido através de herança. """
retorno Verdadeiro
classe CommonInterface (AdminInterface):
def __init__(self):
Interface de administrador.__init__(auto)
self.data.balances = sp.cast (
sp.big_map (),
sp.big_map [
sp.endereço,
sp.record (aprovações=sp.map [sp.address, sp.nat], saldo = 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.endereço,
sp.record (aprovações=sp.map [sp.address, sp.nat], saldo = 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= apenas leitura) "
def é_pausada_ (self):
"""Não é normal, pode ser redefinido através de herança. """
devolver Falso
classe Fa1_2 (CommonInterface):
def __init__(self, metadados, livro-razão, token_metadata):
"""
especificação de token_metadata: https://gitlab.com/tzip/tzip/-/blob/master/proposals/tzip-12/tzip-12.md#token-metadata
Os metadados específicos do Token são armazenados/apresentados como um valor de Michelson do tipo (bytes da string do mapa).
Algumas das chaves estão reservadas e predefinidas:
- "": Deve corresponder a um URI TZIP-016 que aponta para uma representação JSON dos metadados do token.
- " nome": Deve ser uma string UTF-8 dando um nome de " exibição " ao token.
- " símbolo": Deve ser uma string UTF-8 para o identificador curto do token (ex. XTZ, EUR,...).
- " decimais": Deve ser um número inteiro (convertido numa string UTF-8 em decimal)
que define a posição do ponto decimal nos saldos de token para fins de exibição.
especificação contract_metadata: https://gitlab.com/tzip/tzip/-/blob/master/proposals/tzip-16/tzip-16.md
"""
Interface comum.__init__(auto)
self.data.metadata = metadados
self.data.token_metadata = sp.big_map (
{0: sp.record(token_id=0, token_info=token_metadata)}
)
para o proprietário em ledger.items ():
self.data.balances [owner.key] = proprietário.value
self.data.total_supply += proprietário.value.balance
# TODO: Ativar quando esta funcionalidade for implementada.
# self.init_metadata (metadados, " " metadados)
@sp .ponto de entrada
transferência def (self, param):
sp.cast (
param,
sp.record (de_=sp.address, para_=sp.endereço, value=sp.nat) .layout (
("de_ a partir de", ("a_ quanto a", " valor"))
),
)
balance_from = self.data.balances.get (
param.de_, padrão = sp.record (saldo = 0, aprovações= {})
)
balance_to = self.data.balances.get (
param.to_, padrão = sp.record (saldo = 0, aprovações= {})
)
saldo de.balance = sp.as_nat (
balance_from.balance - param.value, erro = FA1.2_Saldo insuficiente " "
)
saldo para.saldo += param.valor
se não auto.is_administrator_ (sp.sender):
afirme não a si mesmo. is_paused_ (), "FA1.2_Pausado "
se param.de_ ! = sp.remetente:
saldo de.aprovações [sp.sender] = sp.as_nat (
balance_from.aprovações [sp.sender] - param.value,
erro = " FA1.2_Não permitido, "
)
self.data.balances [param.from_] = saldo_de
self.data.balances [param.to_] = saldo_para
@sp .ponto de entrada
aprovar def (self, param):
sp.cast (
param,
sp.record (spender=sp.address, value=sp.nat) .layout (
("gastador", " valor")
),
)
afirme não a si mesmo. is_paused_ (), "FA1.2_Pausado "
spender_balance = self.data.balances.get (
sp.remetente, predefinição=sp.record (saldo = 0, aprovações= {})
)
Já aprovado = spender_balance.aprovados.get (param.spender, padrão = 0)
afirmar (
Já aprovado == 0 ou param.value == 0
), " FA1.2 _Alteração de subsídio não seguro "
spender_balance.aprovações [param.spender] = parâmetro. valor
self.data.balances [sp.sender] = saldo_gastador_saldo
@sp .ponto de entrada
def GetBalance (self, param):
(endereço, chamada de retorno) = param
resultado = self.data.balances.get (
endereço, default=sp.record (saldo = 0, aprovações= {})
) .saldo
sp.transfer (resultado, sp.tez (0), retorno de chamada)
@sp .ponto de entrada
def getAllovenance (self, param):
(args, callback) = param
resultado = self.data.balances.get (
args.proprietário, padrão = sp.record (saldo = 0, aprovações= {})
) .aprovações.get (args.spender, padrão = 0)
sp.transfer (resultado, sp.tez (0), retorno de chamada)
@sp .ponto de entrada
def getTotalSupply (self, param):
sp.cast (param, sp.pair [sp.unit, sp.contrato [sp.nat]])
sp.transfer (self.data.total_supply, sp.tez (0), sp.and (param))
@sp .offchain_view ()
def token_metadata (self, token_id):
"""Devolver o URI dos metadados do token fornecido. (token_id deve ser 0). """
sp.cast (token_id, sp.nat)
devolver self.data.token_metadata [token_id]
##########
# Misturas #
##################
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
### Testes #
##########
classe FA1_2TestFull (Admin, Pausa, Fa1_2, Mint, Burn, ChangeMetadata):
def __init__(auto, administrador, metadados, livro-razão, token_metadata):
Alterar metadados.__init__(auto)
Queimar.__init__(eu)
Hortelã.__init__(auto)
Fa1_2.__init__(eu, metadados, livro-razão, token_metadata)
Pausa.__init__(auto)
Administrador.__init__(eu, administrador)
classe Viewer_NAT (SP.Contract):
def __init__(self):
self.data.last = sp.cast (Nenhum, sp.option [sp.nat])
@sp .ponto de entrada
def alvo (self, parâmetros):
self.data.last = Sp.Alguns (parâmetros)
turma Viewer_address (SP.Contract):
def __init__(self):
self.data.last = sp.cast (Nenhum, sp.option [sp.address])
@sp .ponto de entrada
alvo def (self, parâmetros):
self.data.last = SP.Alguns (parâmetros)
se " os modelos " não estiverem em __name__:
@sp .add_test (nome = FA12) " "
teste def ():
sc = sp.test_scenario (m)
sc.h1 (modelo " FA1.2 - Ativos fungíveis) "
# sp.test_account gera ED25519 pares de chaves deterministicamente:
admin = sp.test_account (Administrador) " "
alice = sp.test_account (Alice) " "
bob = sp.test_account (Robert) " "
# Vamos exibir as contas:
sc.h1 (Contas) " "
sc.show ([admin, alice, bob])
sc.h1 (Contrato) " "
token_metadados = {
"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_metadados = sp.utils.metadata_of_url (
"ipfs: //qmaiaUJ1FFngYTU8RLBJC3een9CSkwaf8EGmbNdmHzpNfd "
)
c1 = m.FA1_2TestCompleto (
administrator=admin.address,
metadata=contract_metadata,
token_metadata=token_metadata,
livro-razão = {},
)
sc += c1
sc.h1 (Vista " fora da cadeia - 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 ("Tentativa de actualizar metadados) "
sc.verificar (
c1.data.metadados [] " "
== sp.utils.bytes_of_string (
"ipfs: //qmaiaUJ1FFngYTU8RLBJC3EEN9CSkwaf8EGmbNdmHzpNfd "
)
)
c1.update_metadata (chave =, valor = sp.bytes (0x00)) .run (remetente = " " administrador) " "
sc.verify (c1.data.metadata [] " " == sp.bytes (0x00)) " "
sc.h1 (Pontos de entrada) " "
sc.h2 (O " administrador cunha algumas moedas) "
c1.mint (endereço=alice.address, value=12) .run (remetente=admin)
c1.mint (endereço=alice.address, value=3) .run (remetente = admin)
c1.mint (endereço=alice.address, value=3) .run (remetente = admin)
sc.h2 ("Alice transfere para Bob) "
c1.transfer (de_=alice.address, para_=bob.address, valor=4) .run (remetente = alice)
sc.verify (c1.data.balances [alice.address] .balance == 14)
sc.h2 ("Bob tenta transferir-se de Alice mas não tem a aprovação dela) "
c1.transfer (de_=alice.address, para_=bob.address, valor = 4) .run (
remetente = bob, válido = falso
)
sc.h2 ("Alice aprova transferências de Bob e Bob) "
c1.approve (spender=bob.address, value=5) .run (remetente = alice)
c1.transfer (de_=alice.address, para_=bob.address, valor=4) .run (remetente = bob)
sc.h2 ("Bob tenta transferir em excesso de Alice) "
c1.transfer (de_=alice.address, para_=bob.address, valor = 4) .run (
remetente = bob, válido = falso
)
sc.h2 (O " administrador queima o token Bob) "
c1.burn (endereço=bob.address, valor=1) .run (remetente = administrador)
sc.verify (c1.data.balances [alice.address] .balance == 10)
sc.h2 ("Alice tenta queimar o token Bob) "
c1.burn (endereço=bob.address, value=1) .run (remetente = alice, Válido = falso)
sc.h2 (O " administrador pausa o contrato e Alice não pode mais transferir) "
c1.setPause (Verdadeiro) .run (remetente = admin)
c1.transfer (de_=alice.address, para_=bob.address, valor = 4) .run (
remetente = Alice, Válido = Falso
)
sc.verify (c1.data.balances [alice.address] .balance == 10)
sc.h2 (O " administrador transfere enquanto está em pausa) "
c1.transfer (de_=alice.address, para_=bob.address, valor=1) .run (remetente = administrador)
sc.h2 (O " administrador anula o contrato e as transferências são permitidas) "
c1.setPause (Falso) .run (remetente = admin)
sc.verify (c1.data.balances [alice.address] .balance == 9)
c1.transfer (de_=alice.address, para_=bob.address, valor = 1) .run (remetente = 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 (Visualizações) " "
sc.h2 (Equilíbrio) " "
view_balance = m.Viewer_NAT ()
sc += visão_equilíbrio
alvo = sp.contract (SP.tnat, view_balance.address, "alvo") .open_some ()
c1.GetBalance ((alice.address, alvo))
sc.verify_equal (ver_balance.data.last, sp.alguns (8))
sc.h2 (Administrador) " "
visualizar_administrador = m.Viewer_address ()
sc += visualizar_administrador
alvo = sp.contrato (
Endereço SP.T, view_administrator.address, "alvo "
) .open_some ()
c1.GetAdministrator ((sp.unit, alvo))
sc.verify_equal (ver_administrator.data.last, sp.some (admin.address))
sc.h2 (Fornecimento Total) " "
View_totalSupply = m.Viewer_NAT ()
sc += Visualizar_TotalSupply
alvo = sp.contract (SP.tnat, view_totalsupply.address, "alvo") .open_some ()
c1.GetTotalSupply ((sp.unidade, destino))
sc.verify_equal (View_totalsupply.data.last, sp.alguns (17))
sc.h2 (Subsídio) " "
view_allowance = m.Viewer_NAT ()
sc += view_allowance
alvo = sp.contract (sp.tnat, view_allowance.address, "alvo") .open_some ()
c1.getAllance ((sp.record (owner=alice.address, spender=bob.address), alvo))
sc.verify_equal (ver_allowance.data.last, sp.alguns (1))
O contrato FA1.2 original tem funcionalidades básicas como transferir tokens, aprovar transferências, verificar saldos e visualizar o fornecimento total de tokens. Agora vamos melhorar esta funcionalidade.
Parabéns! Criou o seu primeiro token fungível no Tezos usando o padrão FA1.2!
Na próxima lição, aprenderemos a interagir com o contrato de token que acabamos de criar. Isso incluirá a transferência de tokens, a aprovação de transferências de token e a verificação do saldo do token e da oferta total. Fiquem atentos!