Acessando o IDE SmartPy
Primeiro, abra o IDE online SmartPy em https://smartpy.io/ide/
. Esta é a plataforma que usaremos para escrever, testar e implantar nossos contratos inteligentes.
Iniciando o modelo FA1.2
Clique em “Modelos por tipo” na barra lateral esquerda e selecione “FA1.2”. Uma nova aba será aberta com o modelo de contrato FA1.2. Este é um contrato pronto para uso que segue o padrão FA1.2.
Compreendendo o modelo FA1.2
Este modelo possui a funcionalidade básica para um token fungível, que inclui transferência de tokens, aprovação de transferências, verificação de saldos e visualização do fornecimento total de tokens. O contrato também inclui funcionalidades adicionais para cunhagem e queima de tokens, bem como para gestão de governança.
Estude este modelo e certifique-se de compreender suas funcionalidades. Tudo bem se você não entendeu tudo neste momento, mas tente ter uma noção geral das operações que esse contrato pode realizar.
Por exemplo, você 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 smartpy como sp
# Os metadados abaixo são apenas um exemplo, são serve como base,
# o conteúdo é usado para construir o JSON de metadados que os usuários
# podem copiar e enviar para 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():
class AdminInterface(sp.Contract):
@sp.private(with_storage="read-only")
def is_administrator_(self, sender):
sp .cast(sp.remetente, sp.address)
"""Não é padrão, pode ser redefinido por herança."""
retornar Verdadeiro
classe CommonInterface(AdminInterface):
def __init__(self):
AdminInterface.__init__(próprio)
self.data.balances=sp.cast(
sp.big_map(),
sp.big_map[
sp.address,
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.address,
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="read-only")
def is_paused_(self):
"""Não é padrão, pode ser redefinido por meio de herança."""
retornar Falso
classe Fa1_2 (CommonInterface):
def __init__(self, metadata, ledger, token_metadata):
"""
token_metadata spec: https://gitlab.com/tzip/tzip/-/blob/master/ propostas/tzip-12/tzip-12.md#token-metadata
Os metadados específicos do token são armazenados/apresentados como um valor Michelson do tipo (map string bytes).
Algumas das chaves são reservadas e predefinidas:
- "" : Deve corresponder a um URI TZIP-016 que aponta para uma representação JSON dos metadados do token.
- "name": deve ser uma string UTF-8 dando um "nome de exibição" ao token.
- "symbol" : Deve ser uma string UTF-8 para o identificador curto do token (por exemplo XTZ, EUR, …).
- "decimais": Deve ser um número inteiro (convertido para uma string UTF-8 em decimal)
que define a posição da vírgula decimal nos saldos de tokens para fins de exibição.
Especificação de contract_metadata: https://gitlab.com/tzip/tzip/-/blob/master/proposals/tzip-16/tzip-16.md
"""
CommonInterface.__init__(auto)
self.data.metadata = metadados
self.data.token_metadata = sp.big_map(
{0: sp.record(token_id=0, token_info=token_metadata)}
)
para proprietário em ledger.items():
self.data.balances[proprietário.chave] = proprietário.valor
self.data.total_supply += proprietário.value.balance
# TODO: Ativar quando este recurso for implementado.
# self.init_metadata("metadados", metadados)
@sp.entrypoint
def transfer(self, param):
sp.cast(
param,
sp.record(from_=sp.address, para_=sp.endereço, valor=sp.nat).layout(
("from_ as from", ("to_ as to", "value"))
),
)
balance_from = self.data.balances.get(
parâmetro.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={})
)
balance_from.balance = sp.as_nat(
balance_from.balance - param.value, erro = "FA1.2_InsufficientBalance"
)
balance_to.balance += param.value
se não for self.is_administrator_(sp.sender):
afirmar não self.is_paused_(), "FA1.2_Pausado"
se parâmetro.from_ != sp.sender:
saldo_from.approvals[sp.sender] = sp.as_nat(
balance_from.approvals[sp.sender] - param.value,
erro = "FA1.2_NotAllowed",
)
self.data.balances[param.from_] = balance_from
self.data.balances[param.to_] = balance_to
@sp.entrypoint
def aprovar(self, param):
sp.cast(
param,
sp.record(spender=sp.address, valor=sp.nat).layout(
("gastador", "valor")
),
)
afirma não self.is_paused_(), "FA1.2_Pausado"
gastador_balance=self.data.balances.get(
sp.sender, padrão = sp.record (saldo = 0, aprovações={})
)
já aprovado = gastador_balance.approvals.get(param.spender, padrão = 0)
afirmação (
já aprovado == 0 ou param.value == 0
), "FA1.2_UnsafeAllowanceChange"
gastador_balance.approvals[param.spender] = param.valor
self.data.balances[sp.sender] = gastador_balance
@sp.entrypoint
def getBalance(self, param):
(endereço, retorno de chamada) = param
resultado = self.data.balances.get(
endereço, padrão = sp.record (saldo = 0, aprovações={})
).balance
sp.transfer(resultado, sp.tez(0), retorno de chamada)
@sp.entrypoint
def getAllowance(self, param):
(args, retorno de chamada) = parâmetro
resultado = self.data.balances.get(
args.proprietário, padrão = sp.record (saldo = 0, aprovações={})
).approvals.get(args.spender, padrão=0)
sp.transfer(resultado, sp.tez(0), retorno de chamada)
@sp.entrypoint
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.snd(param))
@sp.offchain_view()
def token_metadata(self, token_id):
"""Retorna o URI de metadados do token para o token fornecido. (token_id deve ser 0)."""
sp.cast(token_id, sp.nat)
retorna 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
##########
# Testes #
###########
classe Fa1_2TestFull (Admin, Pause, Fa1_2, Mint, Burn, ChangeMetadata):
def __init__(self, administrador, metadados, razão, token_metadata):
ChangeMetadata.__init__(próprio)
Queimar.__init__(próprio)
Hortelã.__init__(próprio)
Fa1_2.__init__(próprio, metadados, razão, token_metadata)
Pausa.__init__(próprio)
Administrador.__init__(próprio, administrador)
classe Viewer_nat(sp.Contract):
def __init__(self):
self.data.last = sp.cast(None, sp.opção[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(Nenhum, sp.opção[sp.endereço])
@sp.entrypoint
def target(self, params):
self.data.last = sp.Some(params)
se "modelos" não estiverem em __name__:
@sp.add_test(name="FA12")
def teste():
sc = sp.test_scenario(m)
sc.h1("Modelo FA1.2 - Ativos fungíveis")
# sp.test_account gera pares de chaves ED25519 deterministicamente:
admin = sp.test_account("Administrator")
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_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. metadados_de_url(
"ipfs://QmaiAUj1FFNGYTu8rLBjc3eeN9cSKwaF8EGMBNDmhzPNFd"
)
c1 = m.Fa1_2TestFull(
administrador=admin.address,
metadata=contract_metadata,
token_metadata=token_metadata,
ledger={},
)
sc += c1
sc .h1("Visualização offchain - token_metadata")
sc.verify_equal(
sp.View(c1, "token_metadata")(0),
sp.record(
token_id=0,
token_info=sp.map(
{
"decimals": sp.utils.bytes_of_string("18"),
"name": sp.utils.bytes_of_string("My Great Token"),
"symbol": sp.utils.bytes_of_string("MGT"),
"icon": sp.utils.bytes_of_string(
"https://smartpy.io/static/img/logo-only.svg"
),
}
),
),
)
sc.h1("Tentativa de atualizar metadados")
sc.verify(
c1.data.metadata[""]
== sp.utils.bytes_of_string(
"ipfs://QmaiAUj1FFNGYTu8rLBjc3eeN9cSKwaF8EGMBNDmhzPNFd"
)
)
c1.update_metadata(key="", valor=sp.bytes("0x00")).run(sender=admin)
sc.verify(c1.data.metadata[ ""] == sp.bytes("0x00"))
sc.h1("Pontos de entrada")
sc.h2("Admin cunha algumas moedas")
c1.mint(address=alice.address, value=12).run (remetente = administrador)
c1.mint(endereço=alice.endereço, valor=3).run(remetente=admin)
c1.mint(endereço=alice.endereço, valor=3).run(remetente=admin)
sc.h2("Alice transfere para Bob")
c1.transfer(from_=alice.address, to_=bob.endereço, valor=4).run(remetente=alice)
sc.verify(c1.data.balances[alice.address].balance == 14)
sc.h2("Bob tenta transferir de Alice, mas não tem a aprovação dela")
c1.transfer(from_=alice.address, to_=bob.endereço, valor=4).run(
sender=bob, valid=False
)
sc.h2("Alice aprova transferências de Bob e Bob")
c1.approve(spender=bob.address, value=5).run(sender=alice)
c1.transfer(from_=alice.endereço, to_=bob.endereço, valor=4).run(remetente=bob)
sc.h2("Bob tenta transferir demais de Alice")
c1.transfer(from_=alice.address, to_=bob.endereço, valor=4).run(
sender=bob, valid=False
)
sc.h2("Admin queima token de Bob")
c1.burn(address=bob.address, valor=1).run(remetente=admin)
sc.verify(c1.data.balances[alice.address].balance == 10)
sc.h2("Alice tenta queimar token de Bob")
c1.burn(address=bob.address, valor=1).run(remetente=alice, valid=False)
sc.h2("Admin pausa o contrato e Alice não pode mais transferir")
c1.setPause(True).run(sender=admin)
c1.transfer(from_=alice.endereço, to_=bob.endereço, valor=4).run(
remetente=alice, válido=Falso
)
sc.verify(c1.data.balances[alice.address].balance == 10)
sc.h2("Transferências de administrador durante a pausa")
c1.transfer(from_=alice.address, to_=bob.endereço, valor=1).run(remetente=admin)
sc.h2("Admin retoma o contrato e transferências são permitidas")
c1.setPause(False).run(sender=admin)
sc.verify(c1.data.balances[alice.address].balance == 9)
c1.transfer(from_=alice.address, to_=bob.endereço, 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("Balance")
view_balance = m.Viewer_nat()
sc += view_balance
target = sp.contract(sp.TNat, view_balance.address, "destino").open_some()
c1.getBalance((alice.address, alvo))
sc.verify_equal(view_balance.data.last, sp.some(8))
sc.h2("Administrador")
view_administrator = m.Viewer_address()
sc += view_administrator
target = sp.contract(
sp.TAddress, view_administrator.address, "destino"
).open_some()
c1.getAdministrator((sp.unit, alvo))
sc.verify_equal(view_administrator.data.last, sp.algum(endereço.admin))
sc.h2("Fornecimento total")
view_totalSupply = m.Viewer_nat()
sc += view_totalSupply
target = sp.contract(sp.TNat, view_totalSupply.address, "alvo").open_some()
c1.getTotalSupply((sp.unit, alvo))
sc.verify_equal(view_totalSupply.data.last, sp.some(17))
sc.h2("Permissão")
view_allowance = m.Viewer_nat()
sc += view_allowance
alvo = sp.contract(sp.TNat, view_allowance.address, "alvo").open_some()
c1.getAllowance((sp.record(proprietário=alice.address, gastador=bob.address), alvo))
sc.verify_equal(view_allowance.data.last, sp.algum(1))
O contrato FA1.2 original possui funcionalidades básicas, como transferência de tokens, aprovação de transferências, verificação de saldos e visualização do fornecimento total de tokens. Agora vamos aprimorar essa funcionalidade.
Parabéns! Você criou seu primeiro token fungível no Tezos usando o padrão FA1.2!
Na próxima lição, aprenderemos como interagir com o contrato de token que acabamos de criar. Isso incluirá a transferência de tokens, a aprovação de transferências de tokens e a verificação do saldo de tokens e do fornecimento total. Fique atento!
Acessando o IDE SmartPy
Primeiro, abra o IDE online SmartPy em https://smartpy.io/ide/
. Esta é a plataforma que usaremos para escrever, testar e implantar nossos contratos inteligentes.
Iniciando o modelo FA1.2
Clique em “Modelos por tipo” na barra lateral esquerda e selecione “FA1.2”. Uma nova aba será aberta com o modelo de contrato FA1.2. Este é um contrato pronto para uso que segue o padrão FA1.2.
Compreendendo o modelo FA1.2
Este modelo possui a funcionalidade básica para um token fungível, que inclui transferência de tokens, aprovação de transferências, verificação de saldos e visualização do fornecimento total de tokens. O contrato também inclui funcionalidades adicionais para cunhagem e queima de tokens, bem como para gestão de governança.
Estude este modelo e certifique-se de compreender suas funcionalidades. Tudo bem se você não entendeu tudo neste momento, mas tente ter uma noção geral das operações que esse contrato pode realizar.
Por exemplo, você 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 smartpy como sp
# Os metadados abaixo são apenas um exemplo, são serve como base,
# o conteúdo é usado para construir o JSON de metadados que os usuários
# podem copiar e enviar para 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():
class AdminInterface(sp.Contract):
@sp.private(with_storage="read-only")
def is_administrator_(self, sender):
sp .cast(sp.remetente, sp.address)
"""Não é padrão, pode ser redefinido por herança."""
retornar Verdadeiro
classe CommonInterface(AdminInterface):
def __init__(self):
AdminInterface.__init__(próprio)
self.data.balances=sp.cast(
sp.big_map(),
sp.big_map[
sp.address,
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.address,
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="read-only")
def is_paused_(self):
"""Não é padrão, pode ser redefinido por meio de herança."""
retornar Falso
classe Fa1_2 (CommonInterface):
def __init__(self, metadata, ledger, token_metadata):
"""
token_metadata spec: https://gitlab.com/tzip/tzip/-/blob/master/ propostas/tzip-12/tzip-12.md#token-metadata
Os metadados específicos do token são armazenados/apresentados como um valor Michelson do tipo (map string bytes).
Algumas das chaves são reservadas e predefinidas:
- "" : Deve corresponder a um URI TZIP-016 que aponta para uma representação JSON dos metadados do token.
- "name": deve ser uma string UTF-8 dando um "nome de exibição" ao token.
- "symbol" : Deve ser uma string UTF-8 para o identificador curto do token (por exemplo XTZ, EUR, …).
- "decimais": Deve ser um número inteiro (convertido para uma string UTF-8 em decimal)
que define a posição da vírgula decimal nos saldos de tokens para fins de exibição.
Especificação de contract_metadata: https://gitlab.com/tzip/tzip/-/blob/master/proposals/tzip-16/tzip-16.md
"""
CommonInterface.__init__(auto)
self.data.metadata = metadados
self.data.token_metadata = sp.big_map(
{0: sp.record(token_id=0, token_info=token_metadata)}
)
para proprietário em ledger.items():
self.data.balances[proprietário.chave] = proprietário.valor
self.data.total_supply += proprietário.value.balance
# TODO: Ativar quando este recurso for implementado.
# self.init_metadata("metadados", metadados)
@sp.entrypoint
def transfer(self, param):
sp.cast(
param,
sp.record(from_=sp.address, para_=sp.endereço, valor=sp.nat).layout(
("from_ as from", ("to_ as to", "value"))
),
)
balance_from = self.data.balances.get(
parâmetro.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={})
)
balance_from.balance = sp.as_nat(
balance_from.balance - param.value, erro = "FA1.2_InsufficientBalance"
)
balance_to.balance += param.value
se não for self.is_administrator_(sp.sender):
afirmar não self.is_paused_(), "FA1.2_Pausado"
se parâmetro.from_ != sp.sender:
saldo_from.approvals[sp.sender] = sp.as_nat(
balance_from.approvals[sp.sender] - param.value,
erro = "FA1.2_NotAllowed",
)
self.data.balances[param.from_] = balance_from
self.data.balances[param.to_] = balance_to
@sp.entrypoint
def aprovar(self, param):
sp.cast(
param,
sp.record(spender=sp.address, valor=sp.nat).layout(
("gastador", "valor")
),
)
afirma não self.is_paused_(), "FA1.2_Pausado"
gastador_balance=self.data.balances.get(
sp.sender, padrão = sp.record (saldo = 0, aprovações={})
)
já aprovado = gastador_balance.approvals.get(param.spender, padrão = 0)
afirmação (
já aprovado == 0 ou param.value == 0
), "FA1.2_UnsafeAllowanceChange"
gastador_balance.approvals[param.spender] = param.valor
self.data.balances[sp.sender] = gastador_balance
@sp.entrypoint
def getBalance(self, param):
(endereço, retorno de chamada) = param
resultado = self.data.balances.get(
endereço, padrão = sp.record (saldo = 0, aprovações={})
).balance
sp.transfer(resultado, sp.tez(0), retorno de chamada)
@sp.entrypoint
def getAllowance(self, param):
(args, retorno de chamada) = parâmetro
resultado = self.data.balances.get(
args.proprietário, padrão = sp.record (saldo = 0, aprovações={})
).approvals.get(args.spender, padrão=0)
sp.transfer(resultado, sp.tez(0), retorno de chamada)
@sp.entrypoint
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.snd(param))
@sp.offchain_view()
def token_metadata(self, token_id):
"""Retorna o URI de metadados do token para o token fornecido. (token_id deve ser 0)."""
sp.cast(token_id, sp.nat)
retorna 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
##########
# Testes #
###########
classe Fa1_2TestFull (Admin, Pause, Fa1_2, Mint, Burn, ChangeMetadata):
def __init__(self, administrador, metadados, razão, token_metadata):
ChangeMetadata.__init__(próprio)
Queimar.__init__(próprio)
Hortelã.__init__(próprio)
Fa1_2.__init__(próprio, metadados, razão, token_metadata)
Pausa.__init__(próprio)
Administrador.__init__(próprio, administrador)
classe Viewer_nat(sp.Contract):
def __init__(self):
self.data.last = sp.cast(None, sp.opção[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(Nenhum, sp.opção[sp.endereço])
@sp.entrypoint
def target(self, params):
self.data.last = sp.Some(params)
se "modelos" não estiverem em __name__:
@sp.add_test(name="FA12")
def teste():
sc = sp.test_scenario(m)
sc.h1("Modelo FA1.2 - Ativos fungíveis")
# sp.test_account gera pares de chaves ED25519 deterministicamente:
admin = sp.test_account("Administrator")
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_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. metadados_de_url(
"ipfs://QmaiAUj1FFNGYTu8rLBjc3eeN9cSKwaF8EGMBNDmhzPNFd"
)
c1 = m.Fa1_2TestFull(
administrador=admin.address,
metadata=contract_metadata,
token_metadata=token_metadata,
ledger={},
)
sc += c1
sc .h1("Visualização offchain - token_metadata")
sc.verify_equal(
sp.View(c1, "token_metadata")(0),
sp.record(
token_id=0,
token_info=sp.map(
{
"decimals": sp.utils.bytes_of_string("18"),
"name": sp.utils.bytes_of_string("My Great Token"),
"symbol": sp.utils.bytes_of_string("MGT"),
"icon": sp.utils.bytes_of_string(
"https://smartpy.io/static/img/logo-only.svg"
),
}
),
),
)
sc.h1("Tentativa de atualizar metadados")
sc.verify(
c1.data.metadata[""]
== sp.utils.bytes_of_string(
"ipfs://QmaiAUj1FFNGYTu8rLBjc3eeN9cSKwaF8EGMBNDmhzPNFd"
)
)
c1.update_metadata(key="", valor=sp.bytes("0x00")).run(sender=admin)
sc.verify(c1.data.metadata[ ""] == sp.bytes("0x00"))
sc.h1("Pontos de entrada")
sc.h2("Admin cunha algumas moedas")
c1.mint(address=alice.address, value=12).run (remetente = administrador)
c1.mint(endereço=alice.endereço, valor=3).run(remetente=admin)
c1.mint(endereço=alice.endereço, valor=3).run(remetente=admin)
sc.h2("Alice transfere para Bob")
c1.transfer(from_=alice.address, to_=bob.endereço, valor=4).run(remetente=alice)
sc.verify(c1.data.balances[alice.address].balance == 14)
sc.h2("Bob tenta transferir de Alice, mas não tem a aprovação dela")
c1.transfer(from_=alice.address, to_=bob.endereço, valor=4).run(
sender=bob, valid=False
)
sc.h2("Alice aprova transferências de Bob e Bob")
c1.approve(spender=bob.address, value=5).run(sender=alice)
c1.transfer(from_=alice.endereço, to_=bob.endereço, valor=4).run(remetente=bob)
sc.h2("Bob tenta transferir demais de Alice")
c1.transfer(from_=alice.address, to_=bob.endereço, valor=4).run(
sender=bob, valid=False
)
sc.h2("Admin queima token de Bob")
c1.burn(address=bob.address, valor=1).run(remetente=admin)
sc.verify(c1.data.balances[alice.address].balance == 10)
sc.h2("Alice tenta queimar token de Bob")
c1.burn(address=bob.address, valor=1).run(remetente=alice, valid=False)
sc.h2("Admin pausa o contrato e Alice não pode mais transferir")
c1.setPause(True).run(sender=admin)
c1.transfer(from_=alice.endereço, to_=bob.endereço, valor=4).run(
remetente=alice, válido=Falso
)
sc.verify(c1.data.balances[alice.address].balance == 10)
sc.h2("Transferências de administrador durante a pausa")
c1.transfer(from_=alice.address, to_=bob.endereço, valor=1).run(remetente=admin)
sc.h2("Admin retoma o contrato e transferências são permitidas")
c1.setPause(False).run(sender=admin)
sc.verify(c1.data.balances[alice.address].balance == 9)
c1.transfer(from_=alice.address, to_=bob.endereço, 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("Balance")
view_balance = m.Viewer_nat()
sc += view_balance
target = sp.contract(sp.TNat, view_balance.address, "destino").open_some()
c1.getBalance((alice.address, alvo))
sc.verify_equal(view_balance.data.last, sp.some(8))
sc.h2("Administrador")
view_administrator = m.Viewer_address()
sc += view_administrator
target = sp.contract(
sp.TAddress, view_administrator.address, "destino"
).open_some()
c1.getAdministrator((sp.unit, alvo))
sc.verify_equal(view_administrator.data.last, sp.algum(endereço.admin))
sc.h2("Fornecimento total")
view_totalSupply = m.Viewer_nat()
sc += view_totalSupply
target = sp.contract(sp.TNat, view_totalSupply.address, "alvo").open_some()
c1.getTotalSupply((sp.unit, alvo))
sc.verify_equal(view_totalSupply.data.last, sp.some(17))
sc.h2("Permissão")
view_allowance = m.Viewer_nat()
sc += view_allowance
alvo = sp.contract(sp.TNat, view_allowance.address, "alvo").open_some()
c1.getAllowance((sp.record(proprietário=alice.address, gastador=bob.address), alvo))
sc.verify_equal(view_allowance.data.last, sp.algum(1))
O contrato FA1.2 original possui funcionalidades básicas, como transferência de tokens, aprovação de transferências, verificação de saldos e visualização do fornecimento total de tokens. Agora vamos aprimorar essa funcionalidade.
Parabéns! Você criou seu primeiro token fungível no Tezos usando o padrão FA1.2!
Na próxima lição, aprenderemos como interagir com o contrato de token que acabamos de criar. Isso incluirá a transferência de tokens, a aprovação de transferências de tokens e a verificação do saldo de tokens e do fornecimento total. Fique atento!