Nesse artigo iremos aprender a criar uma carteira que aceita múltiplas assinaturas no envio da transação através da criação de um contrato inteligente.
Carteira com Multi-Assinaturas
Vamos criar uma carteira que aceita múltiplas assinaturas de transações, onde iremos simular um sistema que os donos de carteiras poderão:
- Enviar uma transação
- Aprovar e revogar a aprovação de transações pendentes
- Qualquer carteira poderá executar uma transação depois que um determinado número de proprietários tiver aprovado a transação
// SPDX-License-Identifier: MITpragma solidity ^0.8.13;contract MultiSigWallet {// Declaração dos eventos que serão emitidos através da chamada// das funções pelo contratoevent Deposit(address indexed sender, uint amount, uint balance);event SubmitTransaction(address indexed owner,uint indexed txIndex,address indexed to,uint value,bytes data);event ConfirmTransaction(address indexed owner, uint indexed txIndex);event RevokeConfirmation(address indexed owner, uint indexed txIndex);event ExecuteTransaction(address indexed owner, uint indexed txIndex);// Array onde ficarão armazenados os endereços de carteiras// dos proprietáriosaddress[] public owners;mapping(address => bool) public isOwner;uint public numConfirmationsRequired;// Estrutura de uma transação dentro do contratostruct Transaction {address to;uint value;bytes data;bool executed;uint numConfirmations;}// Mapa que servirá de armazenamento para verificar// se as transações foram confirmadas ou não// mapping from tx index => owner => boolmapping(uint => mapping(address => bool)) public isConfirmed;Transaction[] public transactions;// Verifica se a carteira que está solicitando// uma transação é um proprietáriomodifier onlyOwner() {require(isOwner[msg.sender], "Não é o proprietário");_;}// Verifica se uma hash de transação existemodifier txExists(uint _txIndex) {require(_txIndex < transactions.length, "Transação não existe");_;}// Verifica se a transação já foi executadamodifier notExecuted(uint _txIndex) {require(!transactions[_txIndex].executed, "Transação já executada");_;}// Verifica se a transação já foi confirmadamodifier notConfirmed(uint _txIndex) {require(!isConfirmed[_txIndex][msg.sender], "Transação já confirmada");_;}// Construtor do contrato da carteiraconstructor(address[] memory _owners, uint _numConfirmationsRequired) {require(_owners.length > 0, "proprietários necessários");require(_numConfirmationsRequired > 0 &&_numConfirmationsRequired <= _owners.length,"número inválido de confirmações necessárias");for (uint i = 0; i < _owners.length; i++) {address owner = _owners[i];require(owner != address(0), "proprietário inválido");require(!isOwner[owner], "proprietário não é único");isOwner[owner] = true;owners.push(owner);}numConfirmationsRequired = _numConfirmationsRequired;}receive() external payable {emit Deposit(msg.sender, msg.value, address(this).balance);}// Função que irá enviar uma transaçãofunction submitTransaction(address _to,uint _value,bytes memory _data) public onlyOwner {uint txIndex = transactions.length;transactions.push(Transaction({to: _to,value: _value,data: _data,executed: false,numConfirmations: 0}));emit SubmitTransaction(msg.sender, txIndex, _to, _value, _data);}// Função que irá confirmar a transação por meio do// hash da transação passada por parâmetrofunction confirmTransaction(uint _txIndex)publiconlyOwnertxExists(_txIndex)notExecuted(_txIndex)notConfirmed(_txIndex){Transaction storage transaction = transactions[_txIndex];transaction.numConfirmations += 1;isConfirmed[_txIndex][msg.sender] = true;emit ConfirmTransaction(msg.sender, _txIndex);}// Função que irá executar a transaçãofunction executeTransaction(uint _txIndex)publiconlyOwnertxExists(_txIndex)notExecuted(_txIndex){Transaction storage transaction = transactions[_txIndex];require(transaction.numConfirmations >= numConfirmationsRequired,"Não pode executar a transação");transaction.executed = true;(bool success, ) = transaction.to.call{value: transaction.value}(transaction.data);require(success, "Transação falhou");emit ExecuteTransaction(msg.sender, _txIndex);}// Função que irá revogar a aprovação de uma transação// através do hash passado por parâmetrofunction revokeConfirmation(uint _txIndex)publiconlyOwnertxExists(_txIndex)notExecuted(_txIndex){Transaction storage transaction = transactions[_txIndex];require(isConfirmed[_txIndex][msg.sender], "Transação não confirmada");transaction.numConfirmations -= 1;isConfirmed[_txIndex][msg.sender] = false;emit RevokeConfirmation(msg.sender, _txIndex);}// Retorna a lista de proprietários da carteirafunction getOwners() public view returns (address[] memory) {return owners;}// Retorna a quantidade de transações existentesfunction getTransactionCount() public view returns (uint) {return transactions.length;}// Retorna uma transação através do hash passado por parâmetrofunction getTransaction(uint _txIndex)publicviewreturns (address to,uint value,bytes memory data,bool executed,uint numConfirmations){Transaction storage transaction = transactions[_txIndex];return (transaction.to,transaction.value,transaction.data,transaction.executed,transaction.numConfirmations);}}
Como exemplo, poderíamos criar um contrato simples para testar o envio de transações para a carteira
// SPDX-License-Identifier: MITpragma solidity ^0.8.13;contract TestContract {uint public i;function callMe(uint j) public {i += j;}function getData() public pure returns (bytes memory) {return abi.encodeWithSignature("callMe(uint256)", 123);}}