Os contratos Multisig fornecem um meio de criar controle compartilhado sobre os ativos. Os casos de uso típicos envolvem serviços de garantia, gerenciamento de contas corporativas, assinatura conjunta de acordos financeiros e muito mais. Estes contratos são excepcionalmente benéficos para organizações ou grupos onde é necessária uma tomada de decisão colectiva.
Por definição, os contratos multisig são invioláveis e evitam pontos únicos de falha. Mesmo que as chaves de uma das partes sejam comprometidas, o invasor não poderá executar transações sem a aprovação das outras partes. Isso adiciona uma camada extra de segurança.
Os contratos Multisig podem ser considerados um equivalente digital de um cofre que requer múltiplas chaves para ser aberto. O número total de chaves (N) e o número mínimo de chaves necessárias para abrir a caixa (M) são acordados no momento da criação do contrato.
Os contratos Multisig podem ter muitas configurações diferentes dependendo dos valores de M e N:
No contexto do blockchain, os contratos multisig são amplamente utilizados para aumentar a segurança das transações, apoiar mecanismos complexos de governança ou manter o controle flexível sobre os ativos do blockchain. aqui estão alguns exemplos:
Quanto aos nossos exemplos de código, veremos três implementações diferentes de contratos com múltiplas assinaturas:
É bastante versátil e permite uma ampla gama de utilizações. Requer múltiplas assinaturas para executar funções lambda arbitrárias.
Python
importa smartpy como sp
@sp.module
def main():
operation_lambda: type = sp.lambda_(sp.unit, sp.unit, with_operations=True)
class MultisigLambda(sp.Contract):
"""Vários membros votam na execução de lambdas.
Este contrato pode ser originado com uma lista de endereços e um número de
votos necessários. Qualquer membro pode enviar quantos lambdas quiser e votar
para propostas ativas. Quando um lambda atinge os votos necessários, seu código é chamado
e as operações de saída são executadas. Isso permite que este contrato
faça tudo o que um contrato pode fazer: transferir tokens, gerenciar ativos,
administrar outro contrato...
Quando um lambda é aplicado, todos os lambdas enviados até agora são inativados.
Os membros ainda podem enviar novos lambdas.
"""
def __init__(self, member, require_votes):
"""Construtor
Args:
membros (sp.set de sp.address): pessoas que podem enviar e votar
para lambda.
requerimentos_votos (sp.nat): número de votos necessários
"""
assert requerimentos_votos <= sp.len(
membros
), "required_votes devem ser <= len(membros)"
self.data.lambdas = sp.cast (
sp.big_map(), sp.big_map[sp.nat, operação_lambda]
)
self.data.votes = sp.cast(
sp.big_map(), sp.big_map[sp.nat, sp.set[sp.address]]
)
self.data.nextId = 0
self.data.inactiveBefore = 0
self.data.members = sp.cast(membros, sp.set[sp.endereço])
self.data.required_votes = sp.cast(votos_obrigatórios, sp.nat)
@sp.entrypoint
def submit_lambda(self, lambda_):
"""Envie um novo lambda para votação.
Apresentar uma proposta não implica votar a favor da mesma.
Args:
lambda_(sp.lambda com operações): lambda propôs votar.
Aumenta:
`Você não é um membro`
"""
assert self.data.members.contains(sp.sender), "Você não é membro"
self.data.lambdas[self.data.nextId] = lambda_
self.data.votes[self.data.nextId] = sp.set()
self.data.nextId += 1
@sp.entrypoint
def vote_lambda(self, id):
"""Vote em um lambda.
Args:
id(sp.nat): id do lambda para votar.
Aumenta:
`Você não é membro`, `O lambda está inativo`, `Lambda não encontrado`
Não há voto contra ou aprovação. Se alguém discordar de um lambda
poderá evitar votar.
"""
afirmação self.data.members.contains(sp.sender), "Você não é membro"
assert id >= self.data.inactiveBefore, "O lambda está inativo"
afirmação self.data.lambdas.contains(id), "Lambda não encontrado"
self.data.votes[id].add(sp.sender)
se sp.len(self.data.votes[id]) >= self.data.required_votes:
self.data.lambdas[id]()
self.data.inactiveBefore = self.data.nextId
@sp.onchain_view()
def get_lambda(self, id):
"""Retorna o lambda correspondente.
Args:
id (sp.nat): id do lambda a ser obtido.
Retorno:
par de lambda e um booleano mostrando se o lambda está ativo.
"""
retorno (self.data.lambdas[id], id >= self.data.inactiveBefore)
# se "templates" não estiverem em __name__:
@sp.module
def test():
class Administrated(sp.Contract):
def __init__(self, admin):
self.data.admin = admin
self.data.value = sp.int(0)
@sp.entrypoint
def set_value(self, value):
assert sp.sender == self.data.admin
self.data.value = value
@sp.add_test(name="Cenário básico MultisigLambda", is_default=True )
def basic_scenario():
"""Use o multisigLambda como administrador de um contrato de exemplo.
Testes:
- Originação
- Submissão Lambda
- Votação Lambda
"""
sc = sp.test_scenario([main, teste])
sc.h1("Cenário básico.")
membro1 = sp.test_account("membro1")
membro2 = sp.test_account("membro2")
membro3 = sp.test_account("membro3")
membros = sp.set([membro1.endereço, membro2.endereço, membro3.endereço])
sc.h2("MultisigLambda: origem")
c1 = main.MultisigLambda(membros, 2)
sc += c1
sc.h2("Administrado: origem")
c2 = test.Administrado(c1.address)
sc += c2
sc.h2("MultisigLambda: submit_lambda")
def set_42(params):
administrado = sp.contract(sp.TInt, c2.address, entrypoint="set_value")
sp.transfer(sp. interno(42), sp.tez(0), administrado.open_some())
lambda_ = sp.build_lambda(conjunto_42, with_operations=True)
c1.submit_lambda(lambda_).run(sender=member1)
sc.h2("MultisigLambda: vote_lambda")
c1.vote_lambda(0).run(sender=member1)
c1.vote_lambda(0).run(remetente=membro2)
# Podemos verificar se o contrato administrado recebeu a transferência.
sc.verify(c2.data.value == 42)
Introduz o conceito de votação de propostas. Neste contrato, os signatários podem votar para que determinadas ações sejam tomadas e, caso seja atingido o quórum, as ações propostas são executadas.
Python
importar smartpy como sp
@sp.module
def main():
# Especificação do tipo de ação de administração interna
InternalAdminAction: type = sp.variant(
addSigners=sp.lista[sp.endereço],
alterarQuorum=sp.nat,
removeSigners=sp.list[sp.address],
)
classe MultisigAction(sp.Contract):
"""Um contrato que pode ser usado por vários signatários para administrar outros
contratos. Os contratos administrados implementam
interface que permite explicitar o processo de administração para usuários não especialistas.
Os signatários votam nas propostas. Uma proposta é uma lista de uma meta com uma lista de
ação. Uma ação é um byte simples, mas pretende ser um valor de pacote de
por variante. Este padrão simples permite construir uma interface UX
que mostra o conteúdo de uma proposta ou construir uma.
"""
def __init__(self, quorum, signatários):
self.data.inactiveBefore = 0
self.data.nextId = 0
self.data.proposals = sp.cast(
sp.big_map(),
sp.big_map[
sp.nat,
sp.list[sp.record(target=sp.address, ações=sp.lista[sp.bytes])],
],
)
self.data.quorum = sp.cast(quorum, sp.nat)
self.data.signers = sp.cast(signatários, sp.set[sp.endereço])
self.data.votes = sp.cast(
sp.big_map(), sp.big_map[sp.nat, sp.set[sp.address]]
)
@sp.entrypoint
def send_proposal(self, proposta):
"""Somente signatário. Envie uma proposta para votação.
Args:
proposta (sp.list de sp.record de endereço alvo e ação): Lista\
de alvo e ações de administração associadas.
"""
afirmação self.data.signers.contains(sp.sender), "Somente signatários podem propor"
self.data.proposals[self.data.nextId] = proposta
self.data.votes[self.data.nextId] = sp.set()
self.data.nextId += 1
@sp.entrypoint
def vote(self, pId):
"""Vote em uma ou mais propostas
Args:
pId (sp.nat): ID da proposta.
"""
afirmação self.data.signers.contains(sp.sender), "Somente signatários podem votar"
afirmação self.data.votes.contains(pId), "Proposta desconhecida"
assert pId >= self.data.inactiveBefore, "A proposta está inativa"
self.data.votes[pId].add(sp.sender)
se sp.len(self.data.votes.get(pId, padrão=sp.set())) >= self.data.quorum:
self._onAprovado(pId)
@sp.private(with_storage="read-write", with_operations=True)
def _onApproved(self, pId):
"""Função embutida. Lógica aplicada quando uma proposta é aprovada."""
proposta = self.data.proposals.get(pId, padrão=[])
para p_item na proposta:
contrato = sp.contract(sp.list[sp.bytes], p_item.target)
sp.transfer(
p_item.actions,
sp.tez(0),
contrato.unwrap_some(error="InvalidTarget"),
)
# Desative todas as propostas que já foram enviadas.
self.data.inactiveBefore = self.data.nextId
@sp.entrypoint
def administrate(self, actions):
"""Somente autochamada. Administrar este contrato.
Este ponto de entrada deve ser chamado através do sistema de propostas.
Args:
ações (sp.list de sp.bytes): Lista de variante compactada de \
`InternalAdminAction` (`addSigners`, `changeQuorum`, `removeSigners`).
"""
assert (
sp.sender == sp.self_address()
), "Este ponto de entrada deve ser chamado através do sistema de proposta."
para pack_actions em ações:
action = sp.unpack(packed_actions, InternalAdminAction).unwrap_some(
error="Formato de ações incorretas"
)
com sp.match(action):
com sp.case.changeQuorum como quorum:
self.data.quorum = quorum
com sp.case.addSigners como adicionado:
para signatário adicionado:
self.data.signers.add(signer)
com sp.case.removeSigners como removido:
para endereço removido:
self.data.signers.remove(address)
# Certifique-se de que o contrato nunca exija mais quórum do que o total de signatários.
assert self.data.quorum <= sp.len(
self.data.signers
), "Mais quórum do que signatários."
se "modelos" não estiverem em __name__:
@sp.add_test(name="Cenário básico", is_default=True)
def test():
signer1 = sp.test_account("signer1")
signer2 = sp.test_account("signer2")
signer3 = sp.test_account("signer3")
s = sp.test_scenario(principal)
s.h1("Cenário básico")
s.h2("Origem")
c1 = main.MultisigAction(
quorum=2,
signers=sp.set([signer1.address, signer2.address]),
)
s += c1
s.h2("Proposta para adicionar um novo signatário")
target = sp.to_address(
sp.contract(sp.TList(sp.TBytes), c1.address, "administrar").open_some()
)
ação = sp.pack(
sp.set_type_expr(
sp.variant("addSigners", [signer3.address]), main.InternalAdminAction
)
)
c1.send_proposal([sp.record(target=target, actions=[action])]).run(
sender=signer1
)
s.h2("Signatário 1 vota na proposta")
c1.vote(0).run(sender=signer1)
s.h2("Signatário 2 votos para a proposta")
c1.vote(0).run(sender=signer2)
s.verify(c1.data.signers.contains(signer3.address))
Ele também utiliza um mecanismo de votação. Este contrato permite que os membros enviem e votem em bytes arbitrários. Depois que uma proposta atinge o número necessário de votos, seu status pode ser confirmado por meio de uma visualização.
Python
import smartpy as sp
@sp.module
def main():
class MultisigView(sp.Contract):
"""Vários membros votam em bytes arbitrários.
Este contrato pode ser originado com uma lista de endereços e um número de
votos necessários. Qualquer membro pode enviar quantos bytes quiser e votar
nas propostas ativas.
Quaisquer bytes que atingiram os votos necessários podem ser confirmados por meio de uma visualização.
"""
def __init__(self, member, require_votes):
"""Construtor
Args:
membros (sp.set de sp.address): pessoas que podem enviar e votar em
lambda.
requerimentos_votos (sp.nat): número de votos necessários
"""
assert requerimentos_votos <= sp.len(
membros
), "required_votes devem ser <= len(membros)"
self.data.proposals = sp.cast (sp.big_map(), sp.big_map[sp.bytes, sp.bool])
self.data.votes = sp.cast(
sp.big_map(), sp.big_map[sp.bytes, sp.set[sp.address]]
)
self.data.members = sp.cast(membros, sp.set[sp.endereço])
self.data.required_votes = sp.cast(votos_obrigatórios, sp.nat)
@sp.entrypoint
def submit_proposal(self, bytes):
"""Envie uma nova proposta para votação.
Apresentar uma proposta não implica votar a favor da mesma.
Args:
byte (sp.bytes): bytes propostos para votação.
Aumenta:
`Você não é um membro`
"""
assert self.data.members.contains(sp.sender), "Você não é membro"
self.data.proposals[bytes] = Falso
self.data.votes[bytes] = sp.set()
@sp.entrypoint
def vote_proposal(self, bytes):
"""Vote em uma proposta.
Não há voto contra ou aprovação. Se alguém discordar de uma proposta
poderá evitar votar. Atenção: propostas antigas não votadas nunca se tornam
.
Args:
id (sp.bytes): bytes da proposta.
Aumenta:
`Você não é membro`, `Proposta não encontrada`
"""
assert self.data.members.contains(sp.sender), "Você não é membro"
afirmação self.data.proposals.contains(bytes), "Proposta não encontrada"
self.data.votes[bytes].add(sp.sender)
se sp.len(self.dados.votos[bytes]) >= self.data.required_votes:
self.data.proposals[bytes] = True
@sp.onchain_view()
def is_voted(self, id):
"""Retorna um booleano indicando se a proposta foi votada.
Args:
id (sp.bytes): bytes da proposta
Retorno:
(sp.bool): Verdadeiro se a proposta foi votada; caso contrário, falso.
"""
retorna self.data.proposals.get(id, error="Proposta não encontrada")
se "modelos" não estiverem em __name__:
@sp.add_test(name="Cenário básico MultisigView", is_default=True)
def basic_scenario():
"""Um cenário com uma votação sobre o contrato
Testes:
- Originação
- Envio da proposta
- Votação da proposta
"""
sc = sp.test_scenario(main)
sc.h1("Cenário básico.")
membro1 = sp.test_account("membro1")
membro2 = sp.test_account("membro2")
membro3 = sp.test_account("membro3")
membros = sp.set([membro1.endereço, membro2.endereço, membro3.endereço])
sc.h2("Origem")
c1 = main.MultisigView(membros, 2)
sc += c1
sc.h2("submit_proposal")
c1.submit_proposal(sp.bytes("0x42")).run( remetente=membro1)
sc.h2("vote_proposal")
c1.vote_proposal(sp.bytes("0x42")).run(sender=member1)
c1.vote_proposal(sp.bytes("0x42")).run (sender=member2)
# Podemos verificar se a proposta foi validada.
sc.verify(c1.is_voted(sp.bytes("0x42")))
Cada contrato fornece um mecanismo diferente para obter controle de múltiplas assinaturas, oferecendo flexibilidade dependendo das necessidades específicas do seu caso de uso de blockchain.
Para experimentar os contratos multisig que escrevemos no SmartPy, você pode seguir estas etapas:
Acesse o IDE SmartPy em https://smartpy.io/ide.
Cole o código do contrato no editor. Você pode substituir o código existente.
Para executar o contrato, clique no botão “Executar” localizado no painel superior.
Após executar o contrato, você pode visualizar a execução do cenário no painel “Output” à direita. Aqui você pode ver detalhes de cada ação, incluindo propostas, votações e aprovações.
Para implantar seu contrato na rede Tezos, primeiro você precisa compilá-lo. Clique no botão “Compile” no painel superior.
Após a compilação, você pode implantar o contrato na testnet clicando em “Implementar Contrato Michelson”. Você precisará fornecer uma chave secreta para uma conta Tezos com fundos suficientes para pagar os custos de implantação do gás.
Assim que o contrato for implantado, você receberá o endereço do contrato no blockchain. Você pode usar esse endereço para interagir com o contrato por meio de transações.
Para enviar propostas ou votar nos contratos, você pode utilizar os pontos de entrada definidos no código do contrato, como submit_proposal
ou vote_proposal
. Eles podem ser chamados diretamente das transações que você cria.
Lembre-se, embora o IDE SmartPy permita testar seu contrato em um blockchain simulado, a implantação do contrato na rede Tezos real incorrerá em custos de gás, que devem ser pagos em XTZ, a criptomoeda nativa da rede Tezos.
Os contratos Multisig fornecem um meio de criar controle compartilhado sobre os ativos. Os casos de uso típicos envolvem serviços de garantia, gerenciamento de contas corporativas, assinatura conjunta de acordos financeiros e muito mais. Estes contratos são excepcionalmente benéficos para organizações ou grupos onde é necessária uma tomada de decisão colectiva.
Por definição, os contratos multisig são invioláveis e evitam pontos únicos de falha. Mesmo que as chaves de uma das partes sejam comprometidas, o invasor não poderá executar transações sem a aprovação das outras partes. Isso adiciona uma camada extra de segurança.
Os contratos Multisig podem ser considerados um equivalente digital de um cofre que requer múltiplas chaves para ser aberto. O número total de chaves (N) e o número mínimo de chaves necessárias para abrir a caixa (M) são acordados no momento da criação do contrato.
Os contratos Multisig podem ter muitas configurações diferentes dependendo dos valores de M e N:
No contexto do blockchain, os contratos multisig são amplamente utilizados para aumentar a segurança das transações, apoiar mecanismos complexos de governança ou manter o controle flexível sobre os ativos do blockchain. aqui estão alguns exemplos:
Quanto aos nossos exemplos de código, veremos três implementações diferentes de contratos com múltiplas assinaturas:
É bastante versátil e permite uma ampla gama de utilizações. Requer múltiplas assinaturas para executar funções lambda arbitrárias.
Python
importa smartpy como sp
@sp.module
def main():
operation_lambda: type = sp.lambda_(sp.unit, sp.unit, with_operations=True)
class MultisigLambda(sp.Contract):
"""Vários membros votam na execução de lambdas.
Este contrato pode ser originado com uma lista de endereços e um número de
votos necessários. Qualquer membro pode enviar quantos lambdas quiser e votar
para propostas ativas. Quando um lambda atinge os votos necessários, seu código é chamado
e as operações de saída são executadas. Isso permite que este contrato
faça tudo o que um contrato pode fazer: transferir tokens, gerenciar ativos,
administrar outro contrato...
Quando um lambda é aplicado, todos os lambdas enviados até agora são inativados.
Os membros ainda podem enviar novos lambdas.
"""
def __init__(self, member, require_votes):
"""Construtor
Args:
membros (sp.set de sp.address): pessoas que podem enviar e votar
para lambda.
requerimentos_votos (sp.nat): número de votos necessários
"""
assert requerimentos_votos <= sp.len(
membros
), "required_votes devem ser <= len(membros)"
self.data.lambdas = sp.cast (
sp.big_map(), sp.big_map[sp.nat, operação_lambda]
)
self.data.votes = sp.cast(
sp.big_map(), sp.big_map[sp.nat, sp.set[sp.address]]
)
self.data.nextId = 0
self.data.inactiveBefore = 0
self.data.members = sp.cast(membros, sp.set[sp.endereço])
self.data.required_votes = sp.cast(votos_obrigatórios, sp.nat)
@sp.entrypoint
def submit_lambda(self, lambda_):
"""Envie um novo lambda para votação.
Apresentar uma proposta não implica votar a favor da mesma.
Args:
lambda_(sp.lambda com operações): lambda propôs votar.
Aumenta:
`Você não é um membro`
"""
assert self.data.members.contains(sp.sender), "Você não é membro"
self.data.lambdas[self.data.nextId] = lambda_
self.data.votes[self.data.nextId] = sp.set()
self.data.nextId += 1
@sp.entrypoint
def vote_lambda(self, id):
"""Vote em um lambda.
Args:
id(sp.nat): id do lambda para votar.
Aumenta:
`Você não é membro`, `O lambda está inativo`, `Lambda não encontrado`
Não há voto contra ou aprovação. Se alguém discordar de um lambda
poderá evitar votar.
"""
afirmação self.data.members.contains(sp.sender), "Você não é membro"
assert id >= self.data.inactiveBefore, "O lambda está inativo"
afirmação self.data.lambdas.contains(id), "Lambda não encontrado"
self.data.votes[id].add(sp.sender)
se sp.len(self.data.votes[id]) >= self.data.required_votes:
self.data.lambdas[id]()
self.data.inactiveBefore = self.data.nextId
@sp.onchain_view()
def get_lambda(self, id):
"""Retorna o lambda correspondente.
Args:
id (sp.nat): id do lambda a ser obtido.
Retorno:
par de lambda e um booleano mostrando se o lambda está ativo.
"""
retorno (self.data.lambdas[id], id >= self.data.inactiveBefore)
# se "templates" não estiverem em __name__:
@sp.module
def test():
class Administrated(sp.Contract):
def __init__(self, admin):
self.data.admin = admin
self.data.value = sp.int(0)
@sp.entrypoint
def set_value(self, value):
assert sp.sender == self.data.admin
self.data.value = value
@sp.add_test(name="Cenário básico MultisigLambda", is_default=True )
def basic_scenario():
"""Use o multisigLambda como administrador de um contrato de exemplo.
Testes:
- Originação
- Submissão Lambda
- Votação Lambda
"""
sc = sp.test_scenario([main, teste])
sc.h1("Cenário básico.")
membro1 = sp.test_account("membro1")
membro2 = sp.test_account("membro2")
membro3 = sp.test_account("membro3")
membros = sp.set([membro1.endereço, membro2.endereço, membro3.endereço])
sc.h2("MultisigLambda: origem")
c1 = main.MultisigLambda(membros, 2)
sc += c1
sc.h2("Administrado: origem")
c2 = test.Administrado(c1.address)
sc += c2
sc.h2("MultisigLambda: submit_lambda")
def set_42(params):
administrado = sp.contract(sp.TInt, c2.address, entrypoint="set_value")
sp.transfer(sp. interno(42), sp.tez(0), administrado.open_some())
lambda_ = sp.build_lambda(conjunto_42, with_operations=True)
c1.submit_lambda(lambda_).run(sender=member1)
sc.h2("MultisigLambda: vote_lambda")
c1.vote_lambda(0).run(sender=member1)
c1.vote_lambda(0).run(remetente=membro2)
# Podemos verificar se o contrato administrado recebeu a transferência.
sc.verify(c2.data.value == 42)
Introduz o conceito de votação de propostas. Neste contrato, os signatários podem votar para que determinadas ações sejam tomadas e, caso seja atingido o quórum, as ações propostas são executadas.
Python
importar smartpy como sp
@sp.module
def main():
# Especificação do tipo de ação de administração interna
InternalAdminAction: type = sp.variant(
addSigners=sp.lista[sp.endereço],
alterarQuorum=sp.nat,
removeSigners=sp.list[sp.address],
)
classe MultisigAction(sp.Contract):
"""Um contrato que pode ser usado por vários signatários para administrar outros
contratos. Os contratos administrados implementam
interface que permite explicitar o processo de administração para usuários não especialistas.
Os signatários votam nas propostas. Uma proposta é uma lista de uma meta com uma lista de
ação. Uma ação é um byte simples, mas pretende ser um valor de pacote de
por variante. Este padrão simples permite construir uma interface UX
que mostra o conteúdo de uma proposta ou construir uma.
"""
def __init__(self, quorum, signatários):
self.data.inactiveBefore = 0
self.data.nextId = 0
self.data.proposals = sp.cast(
sp.big_map(),
sp.big_map[
sp.nat,
sp.list[sp.record(target=sp.address, ações=sp.lista[sp.bytes])],
],
)
self.data.quorum = sp.cast(quorum, sp.nat)
self.data.signers = sp.cast(signatários, sp.set[sp.endereço])
self.data.votes = sp.cast(
sp.big_map(), sp.big_map[sp.nat, sp.set[sp.address]]
)
@sp.entrypoint
def send_proposal(self, proposta):
"""Somente signatário. Envie uma proposta para votação.
Args:
proposta (sp.list de sp.record de endereço alvo e ação): Lista\
de alvo e ações de administração associadas.
"""
afirmação self.data.signers.contains(sp.sender), "Somente signatários podem propor"
self.data.proposals[self.data.nextId] = proposta
self.data.votes[self.data.nextId] = sp.set()
self.data.nextId += 1
@sp.entrypoint
def vote(self, pId):
"""Vote em uma ou mais propostas
Args:
pId (sp.nat): ID da proposta.
"""
afirmação self.data.signers.contains(sp.sender), "Somente signatários podem votar"
afirmação self.data.votes.contains(pId), "Proposta desconhecida"
assert pId >= self.data.inactiveBefore, "A proposta está inativa"
self.data.votes[pId].add(sp.sender)
se sp.len(self.data.votes.get(pId, padrão=sp.set())) >= self.data.quorum:
self._onAprovado(pId)
@sp.private(with_storage="read-write", with_operations=True)
def _onApproved(self, pId):
"""Função embutida. Lógica aplicada quando uma proposta é aprovada."""
proposta = self.data.proposals.get(pId, padrão=[])
para p_item na proposta:
contrato = sp.contract(sp.list[sp.bytes], p_item.target)
sp.transfer(
p_item.actions,
sp.tez(0),
contrato.unwrap_some(error="InvalidTarget"),
)
# Desative todas as propostas que já foram enviadas.
self.data.inactiveBefore = self.data.nextId
@sp.entrypoint
def administrate(self, actions):
"""Somente autochamada. Administrar este contrato.
Este ponto de entrada deve ser chamado através do sistema de propostas.
Args:
ações (sp.list de sp.bytes): Lista de variante compactada de \
`InternalAdminAction` (`addSigners`, `changeQuorum`, `removeSigners`).
"""
assert (
sp.sender == sp.self_address()
), "Este ponto de entrada deve ser chamado através do sistema de proposta."
para pack_actions em ações:
action = sp.unpack(packed_actions, InternalAdminAction).unwrap_some(
error="Formato de ações incorretas"
)
com sp.match(action):
com sp.case.changeQuorum como quorum:
self.data.quorum = quorum
com sp.case.addSigners como adicionado:
para signatário adicionado:
self.data.signers.add(signer)
com sp.case.removeSigners como removido:
para endereço removido:
self.data.signers.remove(address)
# Certifique-se de que o contrato nunca exija mais quórum do que o total de signatários.
assert self.data.quorum <= sp.len(
self.data.signers
), "Mais quórum do que signatários."
se "modelos" não estiverem em __name__:
@sp.add_test(name="Cenário básico", is_default=True)
def test():
signer1 = sp.test_account("signer1")
signer2 = sp.test_account("signer2")
signer3 = sp.test_account("signer3")
s = sp.test_scenario(principal)
s.h1("Cenário básico")
s.h2("Origem")
c1 = main.MultisigAction(
quorum=2,
signers=sp.set([signer1.address, signer2.address]),
)
s += c1
s.h2("Proposta para adicionar um novo signatário")
target = sp.to_address(
sp.contract(sp.TList(sp.TBytes), c1.address, "administrar").open_some()
)
ação = sp.pack(
sp.set_type_expr(
sp.variant("addSigners", [signer3.address]), main.InternalAdminAction
)
)
c1.send_proposal([sp.record(target=target, actions=[action])]).run(
sender=signer1
)
s.h2("Signatário 1 vota na proposta")
c1.vote(0).run(sender=signer1)
s.h2("Signatário 2 votos para a proposta")
c1.vote(0).run(sender=signer2)
s.verify(c1.data.signers.contains(signer3.address))
Ele também utiliza um mecanismo de votação. Este contrato permite que os membros enviem e votem em bytes arbitrários. Depois que uma proposta atinge o número necessário de votos, seu status pode ser confirmado por meio de uma visualização.
Python
import smartpy as sp
@sp.module
def main():
class MultisigView(sp.Contract):
"""Vários membros votam em bytes arbitrários.
Este contrato pode ser originado com uma lista de endereços e um número de
votos necessários. Qualquer membro pode enviar quantos bytes quiser e votar
nas propostas ativas.
Quaisquer bytes que atingiram os votos necessários podem ser confirmados por meio de uma visualização.
"""
def __init__(self, member, require_votes):
"""Construtor
Args:
membros (sp.set de sp.address): pessoas que podem enviar e votar em
lambda.
requerimentos_votos (sp.nat): número de votos necessários
"""
assert requerimentos_votos <= sp.len(
membros
), "required_votes devem ser <= len(membros)"
self.data.proposals = sp.cast (sp.big_map(), sp.big_map[sp.bytes, sp.bool])
self.data.votes = sp.cast(
sp.big_map(), sp.big_map[sp.bytes, sp.set[sp.address]]
)
self.data.members = sp.cast(membros, sp.set[sp.endereço])
self.data.required_votes = sp.cast(votos_obrigatórios, sp.nat)
@sp.entrypoint
def submit_proposal(self, bytes):
"""Envie uma nova proposta para votação.
Apresentar uma proposta não implica votar a favor da mesma.
Args:
byte (sp.bytes): bytes propostos para votação.
Aumenta:
`Você não é um membro`
"""
assert self.data.members.contains(sp.sender), "Você não é membro"
self.data.proposals[bytes] = Falso
self.data.votes[bytes] = sp.set()
@sp.entrypoint
def vote_proposal(self, bytes):
"""Vote em uma proposta.
Não há voto contra ou aprovação. Se alguém discordar de uma proposta
poderá evitar votar. Atenção: propostas antigas não votadas nunca se tornam
.
Args:
id (sp.bytes): bytes da proposta.
Aumenta:
`Você não é membro`, `Proposta não encontrada`
"""
assert self.data.members.contains(sp.sender), "Você não é membro"
afirmação self.data.proposals.contains(bytes), "Proposta não encontrada"
self.data.votes[bytes].add(sp.sender)
se sp.len(self.dados.votos[bytes]) >= self.data.required_votes:
self.data.proposals[bytes] = True
@sp.onchain_view()
def is_voted(self, id):
"""Retorna um booleano indicando se a proposta foi votada.
Args:
id (sp.bytes): bytes da proposta
Retorno:
(sp.bool): Verdadeiro se a proposta foi votada; caso contrário, falso.
"""
retorna self.data.proposals.get(id, error="Proposta não encontrada")
se "modelos" não estiverem em __name__:
@sp.add_test(name="Cenário básico MultisigView", is_default=True)
def basic_scenario():
"""Um cenário com uma votação sobre o contrato
Testes:
- Originação
- Envio da proposta
- Votação da proposta
"""
sc = sp.test_scenario(main)
sc.h1("Cenário básico.")
membro1 = sp.test_account("membro1")
membro2 = sp.test_account("membro2")
membro3 = sp.test_account("membro3")
membros = sp.set([membro1.endereço, membro2.endereço, membro3.endereço])
sc.h2("Origem")
c1 = main.MultisigView(membros, 2)
sc += c1
sc.h2("submit_proposal")
c1.submit_proposal(sp.bytes("0x42")).run( remetente=membro1)
sc.h2("vote_proposal")
c1.vote_proposal(sp.bytes("0x42")).run(sender=member1)
c1.vote_proposal(sp.bytes("0x42")).run (sender=member2)
# Podemos verificar se a proposta foi validada.
sc.verify(c1.is_voted(sp.bytes("0x42")))
Cada contrato fornece um mecanismo diferente para obter controle de múltiplas assinaturas, oferecendo flexibilidade dependendo das necessidades específicas do seu caso de uso de blockchain.
Para experimentar os contratos multisig que escrevemos no SmartPy, você pode seguir estas etapas:
Acesse o IDE SmartPy em https://smartpy.io/ide.
Cole o código do contrato no editor. Você pode substituir o código existente.
Para executar o contrato, clique no botão “Executar” localizado no painel superior.
Após executar o contrato, você pode visualizar a execução do cenário no painel “Output” à direita. Aqui você pode ver detalhes de cada ação, incluindo propostas, votações e aprovações.
Para implantar seu contrato na rede Tezos, primeiro você precisa compilá-lo. Clique no botão “Compile” no painel superior.
Após a compilação, você pode implantar o contrato na testnet clicando em “Implementar Contrato Michelson”. Você precisará fornecer uma chave secreta para uma conta Tezos com fundos suficientes para pagar os custos de implantação do gás.
Assim que o contrato for implantado, você receberá o endereço do contrato no blockchain. Você pode usar esse endereço para interagir com o contrato por meio de transações.
Para enviar propostas ou votar nos contratos, você pode utilizar os pontos de entrada definidos no código do contrato, como submit_proposal
ou vote_proposal
. Eles podem ser chamados diretamente das transações que você cria.
Lembre-se, embora o IDE SmartPy permita testar seu contrato em um blockchain simulado, a implantação do contrato na rede Tezos real incorrerá em custos de gás, que devem ser pagos em XTZ, a criptomoeda nativa da rede Tezos.