WEB3DEV

Cover image for Programação Solidity: Transferindo Valor Entre Dois Contratos Inteligentes
Paulo Gio
Paulo Gio

Posted on

Programação Solidity: Transferindo Valor Entre Dois Contratos Inteligentes

A rede Ethereum permite transferências entre diferentes tipos de contas, incluindo dois usuários, dois contratos, um contrato e um usuário, ou um usuário e um contrato. Neste artigo, exploraremos em detalhes o cenário de contrato para contrato e examinaremos os diferentes métodos disponíveis em Solidity para transferir valor entre eles.

https://miro.medium.com/v2/resize:fit:1100/format:webp/1*p8ij6YnvXSbfkfydlDYr2w.jpeg

Transferência Ethereum de uma conta para outra. freepik.com

Transferir valor na rede Ethereum requer uma cuidadosa consideração e vários passos importantes. Este guia abrange tudo o que você precisa saber sobre a transferência de ether (ETH), a criptomoeda nativa da rede, entre contas usando a linguagem de programação Solidity.

Ao iniciar uma transferência, uma transação é criada e enviada para a rede com informações, incluindo os endereços do remetente e do destinatário, a quantidade de ether a ser transferida, o limite e o preço do gás. Em Solidity, wei é a unidade de ether usada para transferências, com 1018 wei equivalente a 1 ether.

Após a transação ser transmitida para a rede, ela é validada e executada pelos nós, com quaisquer contratos inteligentes associados sendo executados. Uma vez validada, a transação é adicionada ao pool de transações da rede, aguardando para ser minerada por um minerador.

Nota do tradutor: Desde o “The Merge” da Ethereum (15/09/22), as transações da rede não são mais mineradas por mineradores (Mecanismo de Consenso de Prova de Trabalho (PoW)), e sim validadas por validadores (Mecanismo de Consenso de Prova de Participação (PoS)).

Quando um bloco é minerado com sucesso pelo minerador, a transação é executada, e o ether é transferido da conta do remetente para a conta do destinatário. O minerador recebe a taxa de gás associada à transação como recompensa por incluí-la no bloco.

Em resumo, transferir ether na rede Ethereum envolve criar e enviar uma transação, validá-la e executá-la, e pagar uma taxa de gás para os mineradores processarem a transação. É importante considerar todos os fatores envolvidos na transferência, como preços e limites de gás, para garantir uma transferência bem-sucedida e econômica entre as contas desejadas.

Para saber mais sobre enviar e receber ether com o Solidity, visite a documentação oficial do Solidity para obter informações detalhadas.

Transferir valor entre dois contratos inteligentes:

Transferir valor entre contratos inteligentes é uma tarefa comum no Solidity. No entanto, é importante estar ciente dos vários métodos disponíveis e dos possíveis riscos de segurança envolvidos para escrever código seguro e protegido.

Neste artigo, veremos como transferir valor entre dois contratos inteligentes usando o Solidity, incluindo exemplos de uso de transfer, send e call. Também discutiremos as melhores práticas para lidar com erros e gerenciamento de segurança para ajudar a prevenir vulnerabilidades, como ataques DDoS e de reentrância.

Os contratos de amostra:

Para demonstrar a transferência de valor entre dois contratos inteligentes, usaremos dois contratos simples: Sender (remetente) e Recipient (destinatário).

Transfer (2300 de gás, lança um erro):

O contrato Sender terá uma função que permite transferir ether para o contrato Recipient. No contrato Sender, a função transferToRecipient recebe dois parâmetros: o endereço payable do contrato Recipient e a quantidade de ether a ser transferida.

O método transfer é usado para transferir ether do contrato Sender para o contrato Recipient. Se a transferência falhar por qualquer motivo, incluindo a não existência do endereço do destinatário, um erro é lançado e a transferência de ether não será executada, e quaisquer mudanças de estado que foram feitas antes da exceção ser lançada serão revertidas. Melhor ainda, um limite de gás de 2300 é colocado no método de transferência, o que não apenas garante que a operação de transferência seja concluída dentro do limite de gás, mas também atua como uma proteção contra ataques de reentrância.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
// Contrato do remetente para enviar ether
contract Sender {
   function transferToRecipient(address payable recipientAddress, uint amount) public {
       require(balance >= amount, "Insufficient balance.");
       // Transferir ether para o contrato do Destinatário usando o método transfer
       recipientAddress.transfer(amount);
   }
}
Enter fullscreen mode Exit fullscreen mode

Payable: funções e endereços declarados payable podem receber ether no contrato.

O contrato Recipient terá a funcionalidade necessária para receber Ether e uma função que permite que ele retire o ether transferido.

pragma solidity ^0.8.17;
// Contrato de destinatário para receber ether
contract Recipient {
     //--- Eventos opcionais para auxiliar nossos esforços de depuração.
    event ReceivedWithData(address sender, uint256 amount, bytes data);
    event ReceivedWithoutData(address sender, uint256 amount);
   address payable public owner;    // Defina o proprietário como o criador do contrato
   constructor() {
       owner = payable(msg.sender);
   }    // Essa função é marcada como payable (pagável) para que o contrato possa receber ether.
   // Também é chamado de "receber" porque esta é a função que é chamada quando o ether é enviado para o contrato sem nenhum dado de função.
   receive() external payable {
       //--- Evite adicionar qualquer código aqui, mas para fins de depuração, vamos emitir um evento:
       emit ReceivedWithoutData(msg.sender, msg.value);
   }    fallback() external payable {
     //--- Evite adicionar qualquer código aqui, mas para fins de depuração, vamos emitir um evento:
     emit Received(msg.sender, msg.value, msg.data);
   }    // Esta função permitirá que o proprietário deste contrato transfira o Ether do contrato para sua própria conta.
   function withdraw() external {
       // Certifique-se de que apenas o proprietário possa chamar esta função
       require(msg.sender == owner, "Only the owner can withdraw.");
       // Transfira todo o saldo deste contrato para o proprietário
       uint256 balance = address(this).balance;
       // Certifique-se de que haja um saldo diferente de zero para transferir
       require(0 != balance, "There is no Ether to withdraw.");
       owner.transfer(balance);
   }
}
Enter fullscreen mode Exit fullscreen mode

Um contrato que recebe Ether deve ter pelo menos uma das funções abaixo:

receive() external payable

fallback() external payable
Enter fullscreen mode Exit fullscreen mode

A função receive() é chamada se o parâmetro msg.data estiver vazio, caso contrário, a função fallback() é chamada.

https://miro.medium.com/v2/resize:fit:1100/format:webp/1*mHmtcCAbzzuQOMFx2Es2iw.jpeg

Certifique-se de que seus fundos não fiquem bloqueados indefinidamente em seu contrato inteligente. coingeek.com

Ao desenvolver contratos inteligentes, é importante incluir a funcionalidade necessária para evitar que os fundos fiquem bloqueados no contrato indefinidamente. No caso do contrato Recipient, adicionamos uma função de “saque” para permitir que o destinatário acesse e use os fundos transferidos para eles. Isso garante que os fundos não sejam permanentemente inacessíveis e fornece flexibilidade para o destinatário usá-los conforme pretendido.

Quando a função transferToRecipient é chamada, ela primeiro verifica se o contrato destinatário possui uma função pagável que possa receber o ether. Se o contrato destinatário não tiver nenhuma função payable, a transferência falhará.

Portanto, é essencial garantir que o contrato destinatário tenha uma função pagável, como a função receive ou fallback, para receber ether enviado do contrato remetente. Caso contrário, a transferência falhará e a transação será revertida.

send (2300 de gás, retorna bool):

O mesmo contrato Sender pode ser reescrito usando o método send:

pragma solidity ^0.8.17;
contract Sender {
   function transferToRecipient(address payable recipient, uint256 amount) public {
       require(address(this).balance >= amount, "Insufficient balance");
       (bool success, ) = recipient.send(amount);
       require(success, "Transfer failed");
   }
}
Enter fullscreen mode Exit fullscreen mode

Nesta implementação, a função transferToRecipient usa o método send para transferir o ether para o endereço do destinatário. Ao contrário do método transfer, o método send retorna um valor booleano indicando se a transferência foi bem-sucedida ou não. O método send também limita a quantidade de gás que pode ser usada em 2300, semelhante ao método transfer.

Finalmente, a função verifica se a transferência foi bem-sucedida usando o valor booleano retornado pelo método send e lança um erro se não foi.

A diferença entre os métodos transfer e send é que o método transfer lança um erro se a transferência falhar, enquanto o método send retorna um valor booleano indicando se a transferência foi bem-sucedida ou não. Independentemente do valor de retorno, quaisquer alterações de estado que foram feitas antes que o método send fosse chamado ainda serão salvas na blockchain, mas a transferência de ether não será executada se for retornado false.

O método send pode ser usado em situações em que as consequências de uma transferência falhada não são graves e onde é desejável ter mais controle sobre o fluxo da transação. No entanto, o método transfer é preferido em situações em que o fluxo da transação é mais direto e onde é importante garantir que a transferência ocorra com sucesso ou falhe atomicamente.

call (encaminha todo o gás ou define o gás, retorna bool):

O mesmo contrato Sender pode ser reescrito usando o método call:

pragma solidity ^0.8.17;
contract Sender {
   function transferToRecipient(address payable recipient, uint256 amount) public {
       require(address(this).balance >= amount, "Insufficient balance");
       (bool success, bytes memory data) = recipient.call{value: amount}("");
       require(success, "Transfer failed");
   }
}
Enter fullscreen mode Exit fullscreen mode

Nesta implementação, a função transferToRecipient usa o método call para transferir o ether para o endereço do destinatário. O método call permite mais personalização da transação, incluindo a especificação de limites de gás e a passagem de argumentos para o contrato destinatário. Neste caso, passamos um array de bytes vazio como o parâmetro de dados para indicar que não estamos passando nenhum argumento para o contrato destinatário.

Assim como o método send, o método call retorna um valor booleano indicando se a transação foi bem-sucedida ou não. Além disso, ele retorna um array de bytes contendo quaisquer dados que foram retornados pelo contrato destinatário.

Finalmente, a função verifica se a transferência foi bem-sucedida usando o valor booleano retornado pelo método call e lança um erro se não foi.

Em comparação com o método send, o método call permite mais personalização da transação, incluindo a especificação de limites de gás e a passagem de argumentos para o contrato destinatário. No entanto, também é mais complexo de usar e pode ser mais propenso a erros se não for usado com cuidado. Além disso, o custo de gás de uma transação call pode ser maior do que o de uma transação send devido às opções de personalização adicionais disponíveis.

Para especificar limites de gás no método call, podemos usar a seguinte implementação:

(bool success, bytes memory data) = recipient.call{value: amount, gas: 2300}("");
Enter fullscreen mode Exit fullscreen mode

Nesta implementação, adicionamos um parâmetro gas ao método call, especificando um limite de gás de 2300. Este é o mesmo limite de gás imposto no método transfer da Ethereum.

Ao definir um limite de gás de 2300, estamos limitando a quantidade de gás que pode ser usada pelo contrato destinatário ao receber a transferência de ether. Esta é uma medida de segurança para evitar ataques de reentrância, onde um contrato malicioso poderia tentar chamar repetidamente de volta para o contrato Sender antes que a transferência original tenha sido concluída, potencialmente roubando fundos ou causando outros comportamentos inesperados.

É importante observar que definir um limite de gás de 2300 pode não ser apropriado para todos os contratos, e o limite de gás deve ser cuidadosamente escolhido com base nos requisitos específicos do contrato e nos riscos potenciais envolvidos. Além disso, é sempre importante testar completamente os contratos em busca de possíveis vulnerabilidades de segurança, incluindo ataques de reentrância, antes de implantá-los na rede Ethereum.

Em geral, a escolha entre os métodos send e call depende dos requisitos específicos do contrato e do nível de personalização e controle necessários sobre o fluxo da transação.

Obrigado pelo seu interesse neste tópico, se você tiver alguma solicitação específica para meus artigos futuros, sinta-se à vontade para me enviar uma mensagem.

Continue sua aprendizagem visitando links úteis como estes…

https://docs.soliditylang.org/en/v0.8.19/

https://medium.com/coinmonks/solidity-transfer-vs-send-vs-call-function-64c92cfc878a

https://solidity-by-example.org/

Obrigado, ChatGPT, por me ajudar a escrever este artigo.

Artigo original publicado por Sam Vishwas. Traduzido por Paulinho Giovannini.

Top comments (0)