Accediendo al IDE de SmartPy
Primero, abra el IDE en línea de SmartPy en https://smartpy.io/ide/
. Esta es la plataforma que usaremos para escribir, probar e implementar nuestros contratos inteligentes.
Iniciando la plantilla FA1.2
Haga clic en "Plantillas por tipo" en la barra lateral izquierda y seleccione "FA1.2". Se abrirá una nueva pestaña con la plantilla de contrato FA1.2. Este es un contrato listo para usar que sigue el estándar FA1.2.
Comprender la plantilla FA1.2
Esta plantilla tiene la funcionalidad básica de un token fungible, que incluye transferir tokens, aprobar transferencias, verificar saldos y ver el suministro total de tokens. El contrato también incluye funciones adicionales para acuñar y quemar tokens, así como para la gestión de la gobernanza.
Estudie esta plantilla y asegúrese de comprender sus funcionalidades. Está bien si no comprende todo en este momento, pero intente tener una idea general de las operaciones que este contrato puede realizar.
Por ejemplo, puede copiar el código de la plantilla en el IDE de SmartPy o aquí a continuación:
Python
# Activos fungibles - FA12
# Inspirado en https://gitlab.com/tzip/tzip/blob/master/A/FA1.2.md
importar smartpy como sp
# Los metadatos a continuación son solo un ejemplo, sirve como base,
# el contenido se utiliza para construir los metadatos JSON que los usuarios
# pueden copiar y cargar en 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():
clase AdminInterface(sp.Contract):
@sp.private(with_storage="solo lectura")
def is_administrator_(self, remitente):
sp .cast(sp.remitente, sp.address)
"""No es estándar, puede redefinirse mediante herencia."""
devuelve Verdadero
clase CommonInterface(AdminInterface):
def __init__(self):
AdminInterface.__init__(yo)
auto.datos.equilibrios = sp.cast(
sp.big_map(),
sp.big_map[
sp.dirección,
sp.record(aprobaciones=sp.map[sp.dirección, sp.nat], equilibrio=sp.nat),
],
)
self.data.total_supply = 0
self.data.token_metadatos = 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.metadatos = sp.cast(
sp.big_map(),
sp.big_map[sp.cadena, sp.bytes],
)
self.data.balances = sp.cast(
sp.big_map(),
sp.big_map[
sp.dirección,
sp.record(aprobaciones=sp.map[sp.dirección, sp.nat], equilibrio=sp.nat),
],
)
self.data.total_supply = 0
self.data.token_metadatos = 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.metadatos = sp.cast(
sp.big_map(),
sp.big_map[sp.cadena, sp.bytes],
)
@sp.private(with_storage="read-only")
def is_paused_(self):
"""No estándar, se puede redefinir mediante herencia."""
return False
clase Fa1_2(CommonInterface):
def __init__(self, metadata, ledger, token_metadata):
"""
token_metadata spec: https://gitlab.com/tzip/tzip/-/blob/master/ propuestas/tzip-12/tzip-12.md#token-metadatos
Los metadatos específicos del token se almacenan/presentan como un valor de tipo Michelson (bytes de cadena de mapa).
Algunas de las claves están reservadas y predefinidas:
- "": debe corresponder a un URI TZIP-016 que apunta a una representación JSON de los metadatos del token.
- "nombre": debe ser una cadena UTF-8 que proporcione un "nombre para mostrar" al token.
- "símbolo": debe ser una cadena UTF-8 para el identificador corto del token (p. ej. XTZ, EUR, …).
- "decimales": debe ser un número entero (convertido a una cadena UTF-8 en decimal)
que define la posición del punto decimal en los saldos de tokens para fines de visualización.
especificación de metadatos de contrato: https://gitlab.com/tzip/tzip/-/blob/master/proposals/tzip-16/tzip-16.md
"""
CommonInterface.__init__(self)
self.data.metadata = metadatos
self.data.token_metadata = sp.big_map(
{0: sp.record(token_id=0, token_info=token_metadata)}
)
para el propietario en ledger.items():
auto.datos.saldos[propietario.clave] = propietario.valor
self.data.total_supply += propietario.valor.equilibrio
# TODO: Activar cuando se implemente esta función.
# self.init_metadata("metadatos", metadatos)
@sp.entrypoint
def transfer(self, param):
sp.cast(
param,
sp.record(from_=sp.address, to_=sp.dirección, valor=sp.nat).diseño(
("desde_ desde", ("hasta_ desde", "valor"))
),
)
balance_from = self.data.balances.get(
parámetro.de_, predeterminado=sp.record(saldo=0, aprobaciones={})
)
balance_to = self.data.balances.get(
parámetro.to_, predeterminado=sp.record(saldo=0, aprobaciones={})
)
balance_from.balance = sp.as_nat(
balance_from.balance - valor.param, error="FA1.2_InsufficientBalance"
)
balance_to.balance += param.value
si no es self.is_administrator_(sp.sender):
afirmar no self.is_paused_(), "FA1.2_Pausado"
si param.from_ != sp.remitente:
balance_from.approvals[sp.remitente] = sp.as_nat(
balance_from.approvals[sp.sender] - valor.param,
error="FA1.2_NotAllowed",
)
self.data.balances[param.from_] = balance_de
self.data.balances[param.to_] = balance_to
@sp.entrypoint
def aprobar(self, param):
sp.cast(
param,
sp.record(spender=sp.address, valor=sp.nat).diseño(
("gastador", "valor")
),
)
afirmar no self.is_paused_(), "FA1.2_Pausado"
balance_gastador = self.data.balances.get(
sp.remitente, predeterminado=sp.record(saldo=0, aprobaciones={})
)
yaAprobadas = gastador_equilibrio.aprobaciones.get(param.spender, default=0)
afirmar (
yaAprobado == 0 o param.value == 0
), "FA1.2_UnsafeAllowanceChange"
gastador_equilibrio.aprobaciones[param.spender] =
auto.datos.saldos[sp.remitente] = gastador_equilibrio
@sp.entrypoint
def getBalance(self, parámetro):
(dirección, devolución de llamada) = parámetro
resultado = self.data.balances.get(
dirección, predeterminado=sp.record(saldo=0, aprobaciones={})
).balance
sp.transfer(resultado, sp.tez(0), devolución de llamada)
@sp.entrypoint
def getAllowance(self, parámetro):
(args, devolución de llamada) = parámetro
resultado = self.data.balances.get(
args.propietario, predeterminado=sp.record(saldo=0, aprobaciones={})
).aprobaciones.get(args.spender, predeterminado=0)
sp.transfer(resultado, sp.tez(0), devolución de llamada)
@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):
"""Devuelve el URI de metadatos del token para el token dado. (token_id debe ser 0)."""
sp.cast(token_id, sp.nat)
devolver 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
##########
# Pruebas #
##########
clase Fa1_2TestFull(Admin, Pause, Fa1_2, Mint, Burn, ChangeMetadata):
def __init__(auto, administrador, metadatos, libro mayor, token_metadata):
ChangeMetadata.__init__(yo)
Quemar.__init__(yo)
Menta.__init__(yo)
Fa1_2.__init__(yo, metadatos, libro mayor, token_metadata)
Pausa.__init__(yo)
Administración.__init__(yo, administrador)
clase Viewer_nat(sp.Contract):
def __init__(self):
self.data.last = sp.cast(Ninguno, sp.opción[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(Ninguno, opción.sp[dirección.sp])
@sp.entrypoint
def target(self, params):
self.data.last = sp.Some(params)
si las "plantillas" no están en __name__:
@sp.add_test(name="FA12")
def prueba():
sc = sp.test_scenario(m)
sc.h1("Plantilla FA1.2 - Activos fungibles")
# sp.test_account genera pares de claves ED25519 de forma determinista:
admin = sp.test_account("Administrador")
Alicia = sp.test_account ("Alicia")
bob = sp.test_account("Roberto")
# Mostremos las cuentas:
sc.h1("Cuentas")
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. metadatos_de_url(
"ipfs://QmaiAUj1FFNGYTu8rLBjc3eeN9cSKwaF8EGMBNDmhzPNFd"
)
c1 = m.Fa1_2TestFull(
administrador=admin.address,
metadata=metadata_contrato,
token_metadata=token_metadata,
libro mayor={},
)
sc += c1
sc .h1("Vista fuera de cadena - 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("Intento de actualizar metadatos")
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("Entrypoints")
sc.h2("El administrador acuña algunas monedas")
c1.mint(address=alice.address, value=12).run (remitente=administrador)
c1.mint(dirección=alice.dirección, valor=3).run(remitente=admin)
c1.mint(dirección=alice.dirección, valor=3).run(remitente=admin)
sc.h2("Alice se transfiere a Bob")
c1.transfer(from_=alice.address, to_=bob.dirección, valor=4).run(remitente=alicia)
sc.verify(c1.datos.saldos[alice.dirección].saldo == 14)
sc.h2("Bob intenta transferirse de Alice pero no tiene su aprobación")
c1.transfer(from_=alice.address, to_=bob.dirección, valor=4).ejecutar(
remitente=bob, válido=Falso
)
sc.h2("Alice aprueba las transferencias de Bob y Bob")
c1.approve(spender=bob.address, value=5).run(remitente=alice)
c1.transfer(desde_=alicia.dirección, to_=bob.dirección, valor=4).run(remitente=bob)
sc.h2("Bob intenta realizar una transferencia excesiva desde Alice")
c1.transfer(from_=alice.address, to_=bob.dirección, valor=4).ejecutar(
remitente=bob, válido=Falso
)
sc.h2("El administrador quema el token de Bob")
c1.burn(dirección=bob.address, valor=1).ejecutar(remitente=admin)
sc.verify(c1.datos.saldos[alice.dirección].saldo == 10)
sc.h2("Alice intenta quemar la ficha de Bob")
c1.burn(dirección=bob.dirección, valor=1).run(remitente=alicia, valid=False)
sc.h2("El administrador pausa el contrato y Alice ya no puede transferir")
c1.setPause(True).run(sender=admin)
c1.transfer(desde_=alicia.dirección, to_=bob.dirección, valor=4).ejecutar(
remitente=alicia, válido=Falso
)
sc.verify(c1.data.balances[alice.address].balance == 10)
sc.h2("El administrador transfiere mientras está en pausa")
c1.transfer(from_=alice.address, to_=bob.dirección, valor=1).ejecutar(remitente=admin)
sc.h2("El administrador reanuda el contrato y se permiten transferencias")
c1.setPause(False).run(sender=admin)
sc.verify(c1.datos.saldos[alice.dirección].saldo == 9)
c1.transfer(desde_=alicia.dirección, to_=bob.dirección, valor=1).run(remitente=alicia)
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("Vistas")
sc.h2("Saldo")
view_balance = m.Viewer_nat()
sc += view_balance
target = sp.contract(sp.TNat, view_balance.address, "destino").open_some()
c1.getBalance((alicia.dirección, objetivo))
sc.verify_equal(view_balance.data.last, sp.some(8))
sc.h2("Administrador")
view_administrator = m.Viewer_address()
sc += view_administrator
destino = sp.contract(
sp.TAddress, view_administrator.address, "destino"
).open_some()
c1.getAdministrator((sp.unidad, objetivo))
sc.verify_equal(view_administrator.data.last, sp.some(dirección.admin))
sc.h2("Suministro total")
view_totalSupply = m.Viewer_nat()
sc += view_totalSupply
target = sp.contract(sp.TNat, view_totalSupply.address, "destino").open_some()
c1.getTotalSupply((sp.unit, destino))
sc.verify_equal(view_totalSupply.data.last, sp.some(17))
sc.h2("Permiso")
view_allowance = m.Viewer_nat()
sc += view_allowance
target = sp.contract(sp.TNat, view_allowance.address, "destino").open_some()
c1.getAllowance((sp.record(propietario=alice.dirección, gastador=bob.dirección), objetivo))
sc.verify_equal(view_allowance.data.last, sp.algunos(1))
El contrato FA1.2 original tiene funciones básicas como transferir tokens, aprobar transferencias, verificar saldos y ver el suministro total de tokens. Ahora mejoraremos esta funcionalidad.
¡Felicidades! ¡Has creado tu primer token fungible en Tezos utilizando el estándar FA1.2!
En la próxima lección, aprenderemos cómo interactuar con el contrato de token que acabamos de crear. Esto incluirá la transferencia de tokens, la aprobación de transferencias de tokens y la verificación del saldo de tokens y el suministro total. ¡Manténganse al tanto!
Accediendo al IDE de SmartPy
Primero, abra el IDE en línea de SmartPy en https://smartpy.io/ide/
. Esta es la plataforma que usaremos para escribir, probar e implementar nuestros contratos inteligentes.
Iniciando la plantilla FA1.2
Haga clic en "Plantillas por tipo" en la barra lateral izquierda y seleccione "FA1.2". Se abrirá una nueva pestaña con la plantilla de contrato FA1.2. Este es un contrato listo para usar que sigue el estándar FA1.2.
Comprender la plantilla FA1.2
Esta plantilla tiene la funcionalidad básica de un token fungible, que incluye transferir tokens, aprobar transferencias, verificar saldos y ver el suministro total de tokens. El contrato también incluye funciones adicionales para acuñar y quemar tokens, así como para la gestión de la gobernanza.
Estudie esta plantilla y asegúrese de comprender sus funcionalidades. Está bien si no comprende todo en este momento, pero intente tener una idea general de las operaciones que este contrato puede realizar.
Por ejemplo, puede copiar el código de la plantilla en el IDE de SmartPy o aquí a continuación:
Python
# Activos fungibles - FA12
# Inspirado en https://gitlab.com/tzip/tzip/blob/master/A/FA1.2.md
importar smartpy como sp
# Los metadatos a continuación son solo un ejemplo, sirve como base,
# el contenido se utiliza para construir los metadatos JSON que los usuarios
# pueden copiar y cargar en 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():
clase AdminInterface(sp.Contract):
@sp.private(with_storage="solo lectura")
def is_administrator_(self, remitente):
sp .cast(sp.remitente, sp.address)
"""No es estándar, puede redefinirse mediante herencia."""
devuelve Verdadero
clase CommonInterface(AdminInterface):
def __init__(self):
AdminInterface.__init__(yo)
auto.datos.equilibrios = sp.cast(
sp.big_map(),
sp.big_map[
sp.dirección,
sp.record(aprobaciones=sp.map[sp.dirección, sp.nat], equilibrio=sp.nat),
],
)
self.data.total_supply = 0
self.data.token_metadatos = 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.metadatos = sp.cast(
sp.big_map(),
sp.big_map[sp.cadena, sp.bytes],
)
self.data.balances = sp.cast(
sp.big_map(),
sp.big_map[
sp.dirección,
sp.record(aprobaciones=sp.map[sp.dirección, sp.nat], equilibrio=sp.nat),
],
)
self.data.total_supply = 0
self.data.token_metadatos = 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.metadatos = sp.cast(
sp.big_map(),
sp.big_map[sp.cadena, sp.bytes],
)
@sp.private(with_storage="read-only")
def is_paused_(self):
"""No estándar, se puede redefinir mediante herencia."""
return False
clase Fa1_2(CommonInterface):
def __init__(self, metadata, ledger, token_metadata):
"""
token_metadata spec: https://gitlab.com/tzip/tzip/-/blob/master/ propuestas/tzip-12/tzip-12.md#token-metadatos
Los metadatos específicos del token se almacenan/presentan como un valor de tipo Michelson (bytes de cadena de mapa).
Algunas de las claves están reservadas y predefinidas:
- "": debe corresponder a un URI TZIP-016 que apunta a una representación JSON de los metadatos del token.
- "nombre": debe ser una cadena UTF-8 que proporcione un "nombre para mostrar" al token.
- "símbolo": debe ser una cadena UTF-8 para el identificador corto del token (p. ej. XTZ, EUR, …).
- "decimales": debe ser un número entero (convertido a una cadena UTF-8 en decimal)
que define la posición del punto decimal en los saldos de tokens para fines de visualización.
especificación de metadatos de contrato: https://gitlab.com/tzip/tzip/-/blob/master/proposals/tzip-16/tzip-16.md
"""
CommonInterface.__init__(self)
self.data.metadata = metadatos
self.data.token_metadata = sp.big_map(
{0: sp.record(token_id=0, token_info=token_metadata)}
)
para el propietario en ledger.items():
auto.datos.saldos[propietario.clave] = propietario.valor
self.data.total_supply += propietario.valor.equilibrio
# TODO: Activar cuando se implemente esta función.
# self.init_metadata("metadatos", metadatos)
@sp.entrypoint
def transfer(self, param):
sp.cast(
param,
sp.record(from_=sp.address, to_=sp.dirección, valor=sp.nat).diseño(
("desde_ desde", ("hasta_ desde", "valor"))
),
)
balance_from = self.data.balances.get(
parámetro.de_, predeterminado=sp.record(saldo=0, aprobaciones={})
)
balance_to = self.data.balances.get(
parámetro.to_, predeterminado=sp.record(saldo=0, aprobaciones={})
)
balance_from.balance = sp.as_nat(
balance_from.balance - valor.param, error="FA1.2_InsufficientBalance"
)
balance_to.balance += param.value
si no es self.is_administrator_(sp.sender):
afirmar no self.is_paused_(), "FA1.2_Pausado"
si param.from_ != sp.remitente:
balance_from.approvals[sp.remitente] = sp.as_nat(
balance_from.approvals[sp.sender] - valor.param,
error="FA1.2_NotAllowed",
)
self.data.balances[param.from_] = balance_de
self.data.balances[param.to_] = balance_to
@sp.entrypoint
def aprobar(self, param):
sp.cast(
param,
sp.record(spender=sp.address, valor=sp.nat).diseño(
("gastador", "valor")
),
)
afirmar no self.is_paused_(), "FA1.2_Pausado"
balance_gastador = self.data.balances.get(
sp.remitente, predeterminado=sp.record(saldo=0, aprobaciones={})
)
yaAprobadas = gastador_equilibrio.aprobaciones.get(param.spender, default=0)
afirmar (
yaAprobado == 0 o param.value == 0
), "FA1.2_UnsafeAllowanceChange"
gastador_equilibrio.aprobaciones[param.spender] =
auto.datos.saldos[sp.remitente] = gastador_equilibrio
@sp.entrypoint
def getBalance(self, parámetro):
(dirección, devolución de llamada) = parámetro
resultado = self.data.balances.get(
dirección, predeterminado=sp.record(saldo=0, aprobaciones={})
).balance
sp.transfer(resultado, sp.tez(0), devolución de llamada)
@sp.entrypoint
def getAllowance(self, parámetro):
(args, devolución de llamada) = parámetro
resultado = self.data.balances.get(
args.propietario, predeterminado=sp.record(saldo=0, aprobaciones={})
).aprobaciones.get(args.spender, predeterminado=0)
sp.transfer(resultado, sp.tez(0), devolución de llamada)
@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):
"""Devuelve el URI de metadatos del token para el token dado. (token_id debe ser 0)."""
sp.cast(token_id, sp.nat)
devolver 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
##########
# Pruebas #
##########
clase Fa1_2TestFull(Admin, Pause, Fa1_2, Mint, Burn, ChangeMetadata):
def __init__(auto, administrador, metadatos, libro mayor, token_metadata):
ChangeMetadata.__init__(yo)
Quemar.__init__(yo)
Menta.__init__(yo)
Fa1_2.__init__(yo, metadatos, libro mayor, token_metadata)
Pausa.__init__(yo)
Administración.__init__(yo, administrador)
clase Viewer_nat(sp.Contract):
def __init__(self):
self.data.last = sp.cast(Ninguno, sp.opción[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(Ninguno, opción.sp[dirección.sp])
@sp.entrypoint
def target(self, params):
self.data.last = sp.Some(params)
si las "plantillas" no están en __name__:
@sp.add_test(name="FA12")
def prueba():
sc = sp.test_scenario(m)
sc.h1("Plantilla FA1.2 - Activos fungibles")
# sp.test_account genera pares de claves ED25519 de forma determinista:
admin = sp.test_account("Administrador")
Alicia = sp.test_account ("Alicia")
bob = sp.test_account("Roberto")
# Mostremos las cuentas:
sc.h1("Cuentas")
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. metadatos_de_url(
"ipfs://QmaiAUj1FFNGYTu8rLBjc3eeN9cSKwaF8EGMBNDmhzPNFd"
)
c1 = m.Fa1_2TestFull(
administrador=admin.address,
metadata=metadata_contrato,
token_metadata=token_metadata,
libro mayor={},
)
sc += c1
sc .h1("Vista fuera de cadena - 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("Intento de actualizar metadatos")
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("Entrypoints")
sc.h2("El administrador acuña algunas monedas")
c1.mint(address=alice.address, value=12).run (remitente=administrador)
c1.mint(dirección=alice.dirección, valor=3).run(remitente=admin)
c1.mint(dirección=alice.dirección, valor=3).run(remitente=admin)
sc.h2("Alice se transfiere a Bob")
c1.transfer(from_=alice.address, to_=bob.dirección, valor=4).run(remitente=alicia)
sc.verify(c1.datos.saldos[alice.dirección].saldo == 14)
sc.h2("Bob intenta transferirse de Alice pero no tiene su aprobación")
c1.transfer(from_=alice.address, to_=bob.dirección, valor=4).ejecutar(
remitente=bob, válido=Falso
)
sc.h2("Alice aprueba las transferencias de Bob y Bob")
c1.approve(spender=bob.address, value=5).run(remitente=alice)
c1.transfer(desde_=alicia.dirección, to_=bob.dirección, valor=4).run(remitente=bob)
sc.h2("Bob intenta realizar una transferencia excesiva desde Alice")
c1.transfer(from_=alice.address, to_=bob.dirección, valor=4).ejecutar(
remitente=bob, válido=Falso
)
sc.h2("El administrador quema el token de Bob")
c1.burn(dirección=bob.address, valor=1).ejecutar(remitente=admin)
sc.verify(c1.datos.saldos[alice.dirección].saldo == 10)
sc.h2("Alice intenta quemar la ficha de Bob")
c1.burn(dirección=bob.dirección, valor=1).run(remitente=alicia, valid=False)
sc.h2("El administrador pausa el contrato y Alice ya no puede transferir")
c1.setPause(True).run(sender=admin)
c1.transfer(desde_=alicia.dirección, to_=bob.dirección, valor=4).ejecutar(
remitente=alicia, válido=Falso
)
sc.verify(c1.data.balances[alice.address].balance == 10)
sc.h2("El administrador transfiere mientras está en pausa")
c1.transfer(from_=alice.address, to_=bob.dirección, valor=1).ejecutar(remitente=admin)
sc.h2("El administrador reanuda el contrato y se permiten transferencias")
c1.setPause(False).run(sender=admin)
sc.verify(c1.datos.saldos[alice.dirección].saldo == 9)
c1.transfer(desde_=alicia.dirección, to_=bob.dirección, valor=1).run(remitente=alicia)
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("Vistas")
sc.h2("Saldo")
view_balance = m.Viewer_nat()
sc += view_balance
target = sp.contract(sp.TNat, view_balance.address, "destino").open_some()
c1.getBalance((alicia.dirección, objetivo))
sc.verify_equal(view_balance.data.last, sp.some(8))
sc.h2("Administrador")
view_administrator = m.Viewer_address()
sc += view_administrator
destino = sp.contract(
sp.TAddress, view_administrator.address, "destino"
).open_some()
c1.getAdministrator((sp.unidad, objetivo))
sc.verify_equal(view_administrator.data.last, sp.some(dirección.admin))
sc.h2("Suministro total")
view_totalSupply = m.Viewer_nat()
sc += view_totalSupply
target = sp.contract(sp.TNat, view_totalSupply.address, "destino").open_some()
c1.getTotalSupply((sp.unit, destino))
sc.verify_equal(view_totalSupply.data.last, sp.some(17))
sc.h2("Permiso")
view_allowance = m.Viewer_nat()
sc += view_allowance
target = sp.contract(sp.TNat, view_allowance.address, "destino").open_some()
c1.getAllowance((sp.record(propietario=alice.dirección, gastador=bob.dirección), objetivo))
sc.verify_equal(view_allowance.data.last, sp.algunos(1))
El contrato FA1.2 original tiene funciones básicas como transferir tokens, aprobar transferencias, verificar saldos y ver el suministro total de tokens. Ahora mejoraremos esta funcionalidad.
¡Felicidades! ¡Has creado tu primer token fungible en Tezos utilizando el estándar FA1.2!
En la próxima lección, aprenderemos cómo interactuar con el contrato de token que acabamos de crear. Esto incluirá la transferencia de tokens, la aprobación de transferencias de tokens y la verificación del saldo de tokens y el suministro total. ¡Manténganse al tanto!