Os contratos Multisig fornecem um meio de criar um controlo partilhado sobre os ativos. Os casos de uso típicos envolvem serviços de garantia, gestão de contas corporativas, co-assinatura de acordos financeiros e muito mais. Estes contratos são excepcionalmente benéficos para organizações ou grupos onde a tomada de decisão coletiva é necessária.
Por design, os contratos multisig são resistentes a adulterações e evitam pontos únicos de falha. Mesmo que as chaves de uma parte estejam comprometidas, o invasor não pode executar transações sem a aprovação das outras partes. Isto adiciona uma camada extra de segurança.
Os contratos Multisig podem ser pensados como um equivalente digital de um cofre que requer várias chaves para abrir. 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 quando o contrato é criado.
Os contratos Multisig podem ter muitas configurações diferentes dependendo dos valores de M e N:
No contexto da blockchain, os contratos multisig são amplamente utilizados para melhorar a segurança das transações, suportar mecanismos de governação complexos ou manter um controlo flexível sobre os ativos da blockchain. Eis alguns exemplos:
Quanto aos nossos exemplos de código, estaremos a analisar três implementações diferentes de contratos multiassinatura:
É bastante versátil e permite uma ampla gama de utilizações. Requer múltiplas assinaturas para executar funções lambda arbitrárias.
Python
importar smartpy como sp
@sp .módulo
def principal ():
operation_lambda: tipo = sp.lambda_ (sp.unit, sp.unit, with_operations=Verdadeiro)
classe Multisiglambda (SP.Contract):
"""Vários membros votam pela execução de lambdas.
Este contrato pode ser originado com uma lista de endereços e uma série de
votos necessários. Qualquer membro pode submeter tantos lambdas quanto quiser e votar
para propostas ativas. Quando um lambda atinge os votos necessários, o seu código é
chamado e as operações de saída são executadas. Isto permite que este contrato
fazer qualquer coisa que um contrato possa fazer: transferir tokens, gerir ativos,
administrar outro contrato...
Quando um lambda é aplicado, todos os lambdas apresentados até agora estão inativados.
Os membros ainda podem submeter novos lambdas.
"""
def __init__(eu, membros, obrigatórios_votos):
"""Construtor
Args:
membros (sp.set of sp.address): pessoas que podem submeter e votar
para lambda.
required_vote (sp.nat): número de votos necessários
"""
afirmar < requred_vote = sp.len (
membros
), " requred_vote deve ser < = len (membros) "
self.data.lambdas = sp.cast (
sp.big_map (), sp.big_map [sp.nat, operação_lambda]
)
self.data.vote = sp.cast (
sp.big_map (), sp.big_map [sp.nat, sp.set [sp.address]]
)
auto.data.NextId = 0
Self. Dados.inactivo Antes = 0
self.data.membros = sp.cast (membros, sp.set [endereço sp.endereço])
self.data.required_vote = sp.cast (obrigatórios_votos, sp.nat)
@sp .ponto de entrada
def submit_lambda (self, lambda_):
"""Submeta uma nova lambda à votação.
Submeter uma proposta não implica votar a favor dela.
Args:
lambda_ (sp.lambda com operações): lambda propôs votar.
Levanta:
`Não é membro`
"""
afirmar self.data.members.contém (sp.sender), "Não é membro "
self.data.lambdas [self.data.nextID] = lambda_
self.data.vote [self.data.nextID] = sp.set ()
auto.Data.NextId += 1
@sp .ponto de entrada
def vote_lambda (self, id):
"""Vote num lambda.
Args:
id (sp.nat): ID do lambda em que votar.
Levanta:
`Não é membro`, `O lambda está inativo`, `Lambda não encontrado`
Não há voto contra ou passe. Se alguém discordar de um lambda
podem evitar votar.
"""
afirmar self.data.members.contém (sp.sender), "Não é membro "
assert id > = self.data.inactiveAntes, "O lambda está inactivo "
afirmar self.data.lambdas.contém (id), "Lambda não encontrado "
self.data.vote [id] .add (sp.sender)
se sp.len (self.data.vote [id]) >= self.data.required_vote:
self.data.lambdas [id] ()
Self. Data.inactivo Antes = Self.data.NextID
@sp .onchain_view ()
def get_lambda (self, id):
"""Devolver o lambda correspondente.
Args:
id (sp.nat): ID do lambda a obter.
Retorno:
par do lambda e um booleano a mostrar se o lambda está activo.
"""
retorno (self.data.lambdas [id], id > = self.data.inactiveAntes)
# se " os modelos " não estiverem em __name__:
@sp .módulo
teste def ():
classe Administrada (SP.Contrato):
def __init__(próprio, administrador):
self.data.admin = administrador
self.data.value = sp.int (0)
@sp .ponto de entrada
def set_value (eu, valor):
assert sp.sender == self.data.admin
self.data.value = valor
@sp .add_test (nome= 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
- Voto Lambda
"""
sc = sp.test_scenario ([principal, 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 ([member1.address, membro2.endereço, membro 3.address])
sc.h2 ("Multisiglambda: origem) "
c1 = main.multisiglambda (membros, 2)
sc += c1
sc.h2 ("Administrado: originação) "
c2 = Test.Administrado (c1.address)
sc += c2
sc.h2 ("MultiSiglambda: submit_lambda) "
def set_42 (parâmetros):
administrado = sp.contract (sp.tint, c2.address, entrypoint = set_value) " "
sp.transfer (sp.int (42), sp.tez (0), administrado.open_some ())
lambda_ = sp.build_lambda (set_42, with_operations=Verdadeiro)
c1.submit_lambda (lambda_) .run (remetente = membro1)
sc.h2 ("Multisiglambda: vote_lambda) "
c1.vote_lambda (0) .run (remetente = membro1)
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 em certas ações a serem tomadas e, se for atingido um quórum, as ações propostas são executadas.
Python
importar smartpy como sp
@sp .módulo
def principal ():
# Especificação do tipo de ação da administração interna
InternalAdminAction: tipo = sp.variante (
addSigners=sp.list [sp.address],
ChangeQuorum=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 uma interface que o torna
possível explicar o processo de administração a utilizadores não especialistas.
Os signatários votam nas propostas. Uma proposta é uma lista de um alvo com uma lista de
ação. Uma ação é um byte simples mas destina-se a ser um valor de pacote de
uma variante. Este padrão simples torna possível construir uma interface UX
que mostra o conteúdo de uma proposta ou constrói uma.
"""
def __init__(self, quórum, signatários):
Auto-dadosInactivo Antes = 0
Self.Data.NextId = 0
self.data.propostas = sp.cast (
sp.big_map (),
sp.big_map [
sp.nat,
sp.list [sp.record (target=sp.address, actions=sp.list [sp.bytes])],
],
)
self.data.quorum = sp.cast (quórum, sp.nat)
self.data.signers = sp.cast (signatários, sp.set [endereço sp.endereço])
self.data.vote = sp.cast (
sp.big_map (), sp.big_map [sp.nat, sp.set [sp.address]]
)
@sp .ponto de entrada
def send_proposta (auto, proposta):
"""Apenas assinantes. Submeter uma proposta à votação.
Args:
proposta (sp.list of sp.record do endereço de destino e ação): Lista\
de ações de administração alvo e associadas.
"""
afirmar self.data.signers.contém (sp.sender), "Só os signatários podem propor "
self.data.propostas [self.data.nextID] = proposta
self.data.vote [self.data.nextID] = sp.set ()
auto.Data.NextId += 1
@sp .ponto de entrada
voto def (self, PId):
"""Vote numa ou mais propostas
Args:
PId (sp.nat): ID da proposta.
"""
afirmar self.data.signers.contém (sp.sender), "Só os signatários podem votar "
afirmar self.data.votes.contém (PId), "Proposta desconhecida "
assert PId > = self.data.inactiveAntes, "A proposta está inactiva "
self.data.vote [PId] .add (sp.sender)
se sp.len (self.data.votes.get (PId, padrão = sp.set ())) >= self.data.quorum:
eu mesmo. _No Aprovado (PID)
@sp .private (with_storage= leitura-gravação, with_operations=true") "
def _OnAprovado (próprio, PID):
"""Função inlinhada. Lógica aplicada quando uma proposta foi aprovada. """
proposta = self.data.proposals.get (PId, predefinição= [])
para p_item na proposta:
contrato = sp.contract (sp.list [sp.bytes], p_item.target)
sp.transferência (
p_item.acções,
sp.tez (0),
contract.unwrap_some (erro = Alvo inválido), " "
)
# Desative todas as propostas que já foram submetidas.
Self. Data.inactivo Antes = Self.data.NextID
@sp .ponto de entrada
def administrar (auto, ações):
"""Apenas auto-chamada. Administra este contrato.
Este ponto de entrada deve ser chamado através do sistema de propostas.
Args:
acções (sp.list of sp.bytes): Lista de variantes embaladas de\
`InternalAdminAction` (`Adicionar signatários`, `Alterar quorum`, `Remover assinantes`).
"""
afirmar (
sp.sender == sp.self_address ()
), " Este ponto de entrada deve ser chamado através do sistema de propostas. "
para packed_actions em ações:
ação = sp.unpack (packed_actions, InternalAdminAction) .unwrap_some (
error= Formato de ações " incorrectas "
)
com sp.match (ação):
com SP.Case.ChangeQuorum como quórum:
self.data.quorum = quórum
com Sp.Case.AddSigners conforme adicionado:
para signatário adicionado:
self.data.signers.add (signer)
com Sp.Case.removeSigners foi removido:
para o endereço é removido:
self.data.signers.remove (endereço)
# Garanta que o contrato nunca exija mais quórum do que o total de signatários.
afirmar self.data.quorum = sp.len < (
self.data.signers
), " Mais quórum do que signatários. "
se " os modelos " não estiverem em __name__:
@sp .add_test (nome= Cenário básico, " is_default=True) "
teste def ():
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 (Originação) " "
c1 = Main.Multisigação (
quórum=2,
signers=sp.set ([signer1.address, signer2.address]),
)
s += c1
s.h2 ("Proposta para adicionar um novo signatário) "
alvo = 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]), Principal. InternalAdminAction
)
)
c1.send_proposta ([sp.record (alvo = alvo, ações= [ação])]) .executar (
remetente = signer1
)
s.h2 ("Signatário 1 votos a favor da proposta) "
c1.vote (0) .run (remetente = signatário1)
s.h2 ("Signatário 2 votos a favor da proposta) "
c1.vote (0) .run (remetente = signatário2)
s.verify (c1.data.signers.contém (signer3.address))
Também utiliza um mecanismo de votação. Este contrato permite que os membros apresentem e votem em bytes arbitrários. Uma vez que uma proposta atinge o número necessário de votos, o seu estatuto pode ser confirmado através de uma vista.
Python
importar smartpy como sp
@sp .módulo
def principal ():
classe MultiSigView (SP.Contract):
"""Vários membros votam em bytes arbitrários.
Este contrato pode ser originado com uma lista de endereços e uma série de
votos necessários. Qualquer membro pode enviar quantos bytes quiser e votar
para propostas activas.
Quaisquer bytes que atingiram os votos exigidos podem ser confirmados através de uma vista.
"""
def __init__(eu, membros, obrigatórios_votos):
"""Construtor
Args:
membros (sp.set of sp.address): pessoas que podem submeter e votar
lambda.
required_vote (sp.nat): número de votos necessários
"""
afirmar < requred_vote = sp.len (
membros
), " requred_vote deve ser < = len (membros) "
self.data.propostas = sp.cast (sp.big_map (), sp.big_map [sp.bytes, sp.bool])
self.data.vote = sp.cast (
sp.big_map (), sp.big_map [sp.bytes, sp.set [sp.address]]
)
self.data.membro = sp.cast (membros, sp.set [endereço sp.endereço])
self.data.required_vote = sp.cast (obrigatórios_votos, sp.nat)
@sp .ponto de entrada
def submit_proposta (self, bytes):
"""Submeter uma nova proposta à votação.
Submeter uma proposta não implica votar a favor dela.
Args:
bytes (sp.bytes): bytes propostos para votar.
Levanta:
`Não é membro`
"""
afirmar self.data.members.contém (sp.sender), "Não é membro "
self.data.propostas [bytes] = Falso
self.data.vote [bytes] = sp.set ()
@sp .ponto de entrada
def vote_proposta (self, bytes):
"""Vote numa proposta.
Não há voto contra ou passe. Se alguém discordar de uma proposta eles
pode evitar votar. Atenção: velhas propostas não votadas nunca se tornam
obsoleto.
Args:
id (sp.bytes): bytes da proposta.
Levanta:
`Não é membro`, `Proposta não encontrada`
"""
afirmar self.data.members.contém (sp.sender), "Não é membro "
afirmar self.data.proposals.contém (bytes), "Proposta não encontrada "
self.data.vote [bytes] .add (sp.sender)
se sp.len (self.data.vote [bytes]) >= self.data.required_vote:
self.data.propostas [bytes] = Verdadeiro
@sp .onchain_view ()
def é_votado (self, id):
"""Retorna um booleano indicando se a proposta foi votada.
Args:
id (sp.bytes): bytes da proposta
Retorno:
(sp.bool): Verdade se a proposta foi votada, Falso caso contrário.
"""
retornar self.data.proposals.get (id, erro = " Proposta não encontrada) "
se " os modelos " não estiverem em __name__:
@sp .add_test (nome= cenário básico " MultiSigView, is_default=True) "
def basic_scenario ():
"""Um cenário com uma votação no contrato MultiSigView.
Testes:
- Originação
- Submissão de propostas
- Votação da proposta
"""
sc = sp.test_scenario (principal)
sc.h1 ("Cenário básico. ")
membro1 = sp.test_account (membro1) " "
membro2 = sp.test_account (membro2) " "
membro3 = sp.test_account (membro3) " "
membros = sp.set ([member1.address, membro2.endereço, membro 3.address])
sc.h2 (Origem) " "
c1 = main.multisigView (membros, 2)
sc += c1
sc.h2 (submeter_proposta) " "
c1.submit_proposta (sp.bytes (0x42)) .run (remetente = membro1) " "
sc.h2 (vote_proposta) " "
c1.vote_proposta (sp.bytes (0x42)) .run (remetente = membro1) " "
c1.vote_proposta (sp.bytes (0x42)) .run (remetente = membro2) " "
# Podemos verificar se a proposta foi validada.
sc.verify (c1.is_votado (sp.bytes (0x42))) " "
Cada contrato fornece um mecanismo diferente para alcançar o controlo multi-assinatura, oferecendo flexibilidade dependendo das necessidades específicas do seu caso de uso de blockchain.
Para experimentar os contratos multisig que escrevemos no SmartPy, pode seguir estes passos:
Vá para o IDE SmartPy em https://smartpy.io/ide.
Cole o código do contrato no editor. Pode substituir o código existente.
Para executar o contrato, clique no botão “Executar” localizado no painel superior.
Depois de executar o contrato, pode ver a execução do cenário no painel “Saída” à direita. Aqui, pode ver detalhes de cada ação, incluindo propostas, votos e aprovações.
Para implantar o seu contrato na rede Tezos, primeiro precisa compilá-lo. Clique no botão “Compilar” no painel superior.
Depois de compilar, pode implementar o contrato na rede de teste clicando em “Implantar Contrato Michelson”. Terá de 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 implementado, ser-lhe-á fornecido o endereço do contrato na cadeia de blocos. Pode usar este endereço para interagir com o contrato através de transações.
Para submeter propostas ou votar nos contratos, pode usar os pontos de entrada definidos no código do contrato, como submit_proposta
ou vote_proposta. Estes podem ser chamados diretamente das transações que cria.
Lembre-se, enquanto o IDE SmartPy permite testar o seu contrato numa cadeia de blocos simulada, implantar o 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 um controlo partilhado sobre os ativos. Os casos de uso típicos envolvem serviços de garantia, gestão de contas corporativas, co-assinatura de acordos financeiros e muito mais. Estes contratos são excepcionalmente benéficos para organizações ou grupos onde a tomada de decisão coletiva é necessária.
Por design, os contratos multisig são resistentes a adulterações e evitam pontos únicos de falha. Mesmo que as chaves de uma parte estejam comprometidas, o invasor não pode executar transações sem a aprovação das outras partes. Isto adiciona uma camada extra de segurança.
Os contratos Multisig podem ser pensados como um equivalente digital de um cofre que requer várias chaves para abrir. 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 quando o contrato é criado.
Os contratos Multisig podem ter muitas configurações diferentes dependendo dos valores de M e N:
No contexto da blockchain, os contratos multisig são amplamente utilizados para melhorar a segurança das transações, suportar mecanismos de governação complexos ou manter um controlo flexível sobre os ativos da blockchain. Eis alguns exemplos:
Quanto aos nossos exemplos de código, estaremos a analisar três implementações diferentes de contratos multiassinatura:
É bastante versátil e permite uma ampla gama de utilizações. Requer múltiplas assinaturas para executar funções lambda arbitrárias.
Python
importar smartpy como sp
@sp .módulo
def principal ():
operation_lambda: tipo = sp.lambda_ (sp.unit, sp.unit, with_operations=Verdadeiro)
classe Multisiglambda (SP.Contract):
"""Vários membros votam pela execução de lambdas.
Este contrato pode ser originado com uma lista de endereços e uma série de
votos necessários. Qualquer membro pode submeter tantos lambdas quanto quiser e votar
para propostas ativas. Quando um lambda atinge os votos necessários, o seu código é
chamado e as operações de saída são executadas. Isto permite que este contrato
fazer qualquer coisa que um contrato possa fazer: transferir tokens, gerir ativos,
administrar outro contrato...
Quando um lambda é aplicado, todos os lambdas apresentados até agora estão inativados.
Os membros ainda podem submeter novos lambdas.
"""
def __init__(eu, membros, obrigatórios_votos):
"""Construtor
Args:
membros (sp.set of sp.address): pessoas que podem submeter e votar
para lambda.
required_vote (sp.nat): número de votos necessários
"""
afirmar < requred_vote = sp.len (
membros
), " requred_vote deve ser < = len (membros) "
self.data.lambdas = sp.cast (
sp.big_map (), sp.big_map [sp.nat, operação_lambda]
)
self.data.vote = sp.cast (
sp.big_map (), sp.big_map [sp.nat, sp.set [sp.address]]
)
auto.data.NextId = 0
Self. Dados.inactivo Antes = 0
self.data.membros = sp.cast (membros, sp.set [endereço sp.endereço])
self.data.required_vote = sp.cast (obrigatórios_votos, sp.nat)
@sp .ponto de entrada
def submit_lambda (self, lambda_):
"""Submeta uma nova lambda à votação.
Submeter uma proposta não implica votar a favor dela.
Args:
lambda_ (sp.lambda com operações): lambda propôs votar.
Levanta:
`Não é membro`
"""
afirmar self.data.members.contém (sp.sender), "Não é membro "
self.data.lambdas [self.data.nextID] = lambda_
self.data.vote [self.data.nextID] = sp.set ()
auto.Data.NextId += 1
@sp .ponto de entrada
def vote_lambda (self, id):
"""Vote num lambda.
Args:
id (sp.nat): ID do lambda em que votar.
Levanta:
`Não é membro`, `O lambda está inativo`, `Lambda não encontrado`
Não há voto contra ou passe. Se alguém discordar de um lambda
podem evitar votar.
"""
afirmar self.data.members.contém (sp.sender), "Não é membro "
assert id > = self.data.inactiveAntes, "O lambda está inactivo "
afirmar self.data.lambdas.contém (id), "Lambda não encontrado "
self.data.vote [id] .add (sp.sender)
se sp.len (self.data.vote [id]) >= self.data.required_vote:
self.data.lambdas [id] ()
Self. Data.inactivo Antes = Self.data.NextID
@sp .onchain_view ()
def get_lambda (self, id):
"""Devolver o lambda correspondente.
Args:
id (sp.nat): ID do lambda a obter.
Retorno:
par do lambda e um booleano a mostrar se o lambda está activo.
"""
retorno (self.data.lambdas [id], id > = self.data.inactiveAntes)
# se " os modelos " não estiverem em __name__:
@sp .módulo
teste def ():
classe Administrada (SP.Contrato):
def __init__(próprio, administrador):
self.data.admin = administrador
self.data.value = sp.int (0)
@sp .ponto de entrada
def set_value (eu, valor):
assert sp.sender == self.data.admin
self.data.value = valor
@sp .add_test (nome= 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
- Voto Lambda
"""
sc = sp.test_scenario ([principal, 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 ([member1.address, membro2.endereço, membro 3.address])
sc.h2 ("Multisiglambda: origem) "
c1 = main.multisiglambda (membros, 2)
sc += c1
sc.h2 ("Administrado: originação) "
c2 = Test.Administrado (c1.address)
sc += c2
sc.h2 ("MultiSiglambda: submit_lambda) "
def set_42 (parâmetros):
administrado = sp.contract (sp.tint, c2.address, entrypoint = set_value) " "
sp.transfer (sp.int (42), sp.tez (0), administrado.open_some ())
lambda_ = sp.build_lambda (set_42, with_operations=Verdadeiro)
c1.submit_lambda (lambda_) .run (remetente = membro1)
sc.h2 ("Multisiglambda: vote_lambda) "
c1.vote_lambda (0) .run (remetente = membro1)
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 em certas ações a serem tomadas e, se for atingido um quórum, as ações propostas são executadas.
Python
importar smartpy como sp
@sp .módulo
def principal ():
# Especificação do tipo de ação da administração interna
InternalAdminAction: tipo = sp.variante (
addSigners=sp.list [sp.address],
ChangeQuorum=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 uma interface que o torna
possível explicar o processo de administração a utilizadores não especialistas.
Os signatários votam nas propostas. Uma proposta é uma lista de um alvo com uma lista de
ação. Uma ação é um byte simples mas destina-se a ser um valor de pacote de
uma variante. Este padrão simples torna possível construir uma interface UX
que mostra o conteúdo de uma proposta ou constrói uma.
"""
def __init__(self, quórum, signatários):
Auto-dadosInactivo Antes = 0
Self.Data.NextId = 0
self.data.propostas = sp.cast (
sp.big_map (),
sp.big_map [
sp.nat,
sp.list [sp.record (target=sp.address, actions=sp.list [sp.bytes])],
],
)
self.data.quorum = sp.cast (quórum, sp.nat)
self.data.signers = sp.cast (signatários, sp.set [endereço sp.endereço])
self.data.vote = sp.cast (
sp.big_map (), sp.big_map [sp.nat, sp.set [sp.address]]
)
@sp .ponto de entrada
def send_proposta (auto, proposta):
"""Apenas assinantes. Submeter uma proposta à votação.
Args:
proposta (sp.list of sp.record do endereço de destino e ação): Lista\
de ações de administração alvo e associadas.
"""
afirmar self.data.signers.contém (sp.sender), "Só os signatários podem propor "
self.data.propostas [self.data.nextID] = proposta
self.data.vote [self.data.nextID] = sp.set ()
auto.Data.NextId += 1
@sp .ponto de entrada
voto def (self, PId):
"""Vote numa ou mais propostas
Args:
PId (sp.nat): ID da proposta.
"""
afirmar self.data.signers.contém (sp.sender), "Só os signatários podem votar "
afirmar self.data.votes.contém (PId), "Proposta desconhecida "
assert PId > = self.data.inactiveAntes, "A proposta está inactiva "
self.data.vote [PId] .add (sp.sender)
se sp.len (self.data.votes.get (PId, padrão = sp.set ())) >= self.data.quorum:
eu mesmo. _No Aprovado (PID)
@sp .private (with_storage= leitura-gravação, with_operations=true") "
def _OnAprovado (próprio, PID):
"""Função inlinhada. Lógica aplicada quando uma proposta foi aprovada. """
proposta = self.data.proposals.get (PId, predefinição= [])
para p_item na proposta:
contrato = sp.contract (sp.list [sp.bytes], p_item.target)
sp.transferência (
p_item.acções,
sp.tez (0),
contract.unwrap_some (erro = Alvo inválido), " "
)
# Desative todas as propostas que já foram submetidas.
Self. Data.inactivo Antes = Self.data.NextID
@sp .ponto de entrada
def administrar (auto, ações):
"""Apenas auto-chamada. Administra este contrato.
Este ponto de entrada deve ser chamado através do sistema de propostas.
Args:
acções (sp.list of sp.bytes): Lista de variantes embaladas de\
`InternalAdminAction` (`Adicionar signatários`, `Alterar quorum`, `Remover assinantes`).
"""
afirmar (
sp.sender == sp.self_address ()
), " Este ponto de entrada deve ser chamado através do sistema de propostas. "
para packed_actions em ações:
ação = sp.unpack (packed_actions, InternalAdminAction) .unwrap_some (
error= Formato de ações " incorrectas "
)
com sp.match (ação):
com SP.Case.ChangeQuorum como quórum:
self.data.quorum = quórum
com Sp.Case.AddSigners conforme adicionado:
para signatário adicionado:
self.data.signers.add (signer)
com Sp.Case.removeSigners foi removido:
para o endereço é removido:
self.data.signers.remove (endereço)
# Garanta que o contrato nunca exija mais quórum do que o total de signatários.
afirmar self.data.quorum = sp.len < (
self.data.signers
), " Mais quórum do que signatários. "
se " os modelos " não estiverem em __name__:
@sp .add_test (nome= Cenário básico, " is_default=True) "
teste def ():
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 (Originação) " "
c1 = Main.Multisigação (
quórum=2,
signers=sp.set ([signer1.address, signer2.address]),
)
s += c1
s.h2 ("Proposta para adicionar um novo signatário) "
alvo = 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]), Principal. InternalAdminAction
)
)
c1.send_proposta ([sp.record (alvo = alvo, ações= [ação])]) .executar (
remetente = signer1
)
s.h2 ("Signatário 1 votos a favor da proposta) "
c1.vote (0) .run (remetente = signatário1)
s.h2 ("Signatário 2 votos a favor da proposta) "
c1.vote (0) .run (remetente = signatário2)
s.verify (c1.data.signers.contém (signer3.address))
Também utiliza um mecanismo de votação. Este contrato permite que os membros apresentem e votem em bytes arbitrários. Uma vez que uma proposta atinge o número necessário de votos, o seu estatuto pode ser confirmado através de uma vista.
Python
importar smartpy como sp
@sp .módulo
def principal ():
classe MultiSigView (SP.Contract):
"""Vários membros votam em bytes arbitrários.
Este contrato pode ser originado com uma lista de endereços e uma série de
votos necessários. Qualquer membro pode enviar quantos bytes quiser e votar
para propostas activas.
Quaisquer bytes que atingiram os votos exigidos podem ser confirmados através de uma vista.
"""
def __init__(eu, membros, obrigatórios_votos):
"""Construtor
Args:
membros (sp.set of sp.address): pessoas que podem submeter e votar
lambda.
required_vote (sp.nat): número de votos necessários
"""
afirmar < requred_vote = sp.len (
membros
), " requred_vote deve ser < = len (membros) "
self.data.propostas = sp.cast (sp.big_map (), sp.big_map [sp.bytes, sp.bool])
self.data.vote = sp.cast (
sp.big_map (), sp.big_map [sp.bytes, sp.set [sp.address]]
)
self.data.membro = sp.cast (membros, sp.set [endereço sp.endereço])
self.data.required_vote = sp.cast (obrigatórios_votos, sp.nat)
@sp .ponto de entrada
def submit_proposta (self, bytes):
"""Submeter uma nova proposta à votação.
Submeter uma proposta não implica votar a favor dela.
Args:
bytes (sp.bytes): bytes propostos para votar.
Levanta:
`Não é membro`
"""
afirmar self.data.members.contém (sp.sender), "Não é membro "
self.data.propostas [bytes] = Falso
self.data.vote [bytes] = sp.set ()
@sp .ponto de entrada
def vote_proposta (self, bytes):
"""Vote numa proposta.
Não há voto contra ou passe. Se alguém discordar de uma proposta eles
pode evitar votar. Atenção: velhas propostas não votadas nunca se tornam
obsoleto.
Args:
id (sp.bytes): bytes da proposta.
Levanta:
`Não é membro`, `Proposta não encontrada`
"""
afirmar self.data.members.contém (sp.sender), "Não é membro "
afirmar self.data.proposals.contém (bytes), "Proposta não encontrada "
self.data.vote [bytes] .add (sp.sender)
se sp.len (self.data.vote [bytes]) >= self.data.required_vote:
self.data.propostas [bytes] = Verdadeiro
@sp .onchain_view ()
def é_votado (self, id):
"""Retorna um booleano indicando se a proposta foi votada.
Args:
id (sp.bytes): bytes da proposta
Retorno:
(sp.bool): Verdade se a proposta foi votada, Falso caso contrário.
"""
retornar self.data.proposals.get (id, erro = " Proposta não encontrada) "
se " os modelos " não estiverem em __name__:
@sp .add_test (nome= cenário básico " MultiSigView, is_default=True) "
def basic_scenario ():
"""Um cenário com uma votação no contrato MultiSigView.
Testes:
- Originação
- Submissão de propostas
- Votação da proposta
"""
sc = sp.test_scenario (principal)
sc.h1 ("Cenário básico. ")
membro1 = sp.test_account (membro1) " "
membro2 = sp.test_account (membro2) " "
membro3 = sp.test_account (membro3) " "
membros = sp.set ([member1.address, membro2.endereço, membro 3.address])
sc.h2 (Origem) " "
c1 = main.multisigView (membros, 2)
sc += c1
sc.h2 (submeter_proposta) " "
c1.submit_proposta (sp.bytes (0x42)) .run (remetente = membro1) " "
sc.h2 (vote_proposta) " "
c1.vote_proposta (sp.bytes (0x42)) .run (remetente = membro1) " "
c1.vote_proposta (sp.bytes (0x42)) .run (remetente = membro2) " "
# Podemos verificar se a proposta foi validada.
sc.verify (c1.is_votado (sp.bytes (0x42))) " "
Cada contrato fornece um mecanismo diferente para alcançar o controlo multi-assinatura, oferecendo flexibilidade dependendo das necessidades específicas do seu caso de uso de blockchain.
Para experimentar os contratos multisig que escrevemos no SmartPy, pode seguir estes passos:
Vá para o IDE SmartPy em https://smartpy.io/ide.
Cole o código do contrato no editor. Pode substituir o código existente.
Para executar o contrato, clique no botão “Executar” localizado no painel superior.
Depois de executar o contrato, pode ver a execução do cenário no painel “Saída” à direita. Aqui, pode ver detalhes de cada ação, incluindo propostas, votos e aprovações.
Para implantar o seu contrato na rede Tezos, primeiro precisa compilá-lo. Clique no botão “Compilar” no painel superior.
Depois de compilar, pode implementar o contrato na rede de teste clicando em “Implantar Contrato Michelson”. Terá de 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 implementado, ser-lhe-á fornecido o endereço do contrato na cadeia de blocos. Pode usar este endereço para interagir com o contrato através de transações.
Para submeter propostas ou votar nos contratos, pode usar os pontos de entrada definidos no código do contrato, como submit_proposta
ou vote_proposta. Estes podem ser chamados diretamente das transações que cria.
Lembre-se, enquanto o IDE SmartPy permite testar o seu contrato numa cadeia de blocos simulada, implantar o contrato na rede Tezos real incorrerá em custos de gás, que devem ser pagos em XTZ, a criptomoeda nativa da rede Tezos.