Nesse artigo iremos abordar sobre a utilização das funções call
, fallback
, delegatecall
e chamadas de outros contratos no seu contrato inteligente.
Falaremos sobre call
, fallback
e delegatecall
.
Call (Chamar)
call
é uma função de baixo nível para interagir com outros contratos.
Este é o método recomendado para usar quando você está apenas enviando Ether por meio da chamada da função fallback
.
No entanto, não é a maneira recomendada de chamar funções existentes.
// SPDX-License-Identifier: MITpragma solidity ^0.8.13;// Definição do contrato Receiver ou Recebedorcontract Receiver {event Received(address caller, uint amount, string message);fallback() external payable {emit Received(msg.sender, msg.value, "Fallback foi chamado");}function foo(string memory _message, uint _x) public payable returns (uint) {emit Received(msg.sender, msg.value, _message);return _x + 1;}}// Definição do contrato Caller ou Chamadorcontract Caller {event Response(bool success, bytes data);// Vamos imaginar que o contrato B não tenha o código fonte para// contrato A, mas sabemos o endereço de A e a função a ser chamada.function testCallFoo(address payable _addr) public payable {// Você pode enviar éter e especificar uma quantidade de gás personalizada(bool success, bytes memory data) = _addr.call{value: msg.value, gas: 5000}(abi.encodeWithSignature("foo(string,uint256)", "call foo", 123));emit Response(success, data);}// Chamar uma função que não existe aciona a função de fallback.function testCallDoesNotExist(address _addr) public {(bool success, bytes memory data) = _addr.call(abi.encodeWithSignature("doesNotExist()"));emit Response(success, data);}}
Fallback (Retorno)
fallback
é uma função que não recebe nenhum argumento e não retorna nada.
É executada Quando
- uma função que não existe é chamada
- o Ether enviado diretamente para um contrato, mas
receive()
não existe oumsg.data
não está vazio
fallback
tem um limite de gas de 2300 quando chamado por transfer
ou send
.
// SPDX-License-Identifier: MITpragma solidity ^0.8.13;contract Fallback {event Log(uint gas);// A função de fallback deve ser declarada como externa.fallback() external payable {// send / transfer (envia 2300 gas para esta função fallback)// call (envia todo gas)emit Log(gasleft());}// Função auxiliar para verificar o saldo deste contratofunction getBalance() public view returns (uint) {return address(this).balance;}}contract SendToFallback {function transferToFallback(address payable _to) public payable {_to.transfer(msg.value);}function callFallback(address payable _to) public payable {(bool sent, ) = _to.call{value: msg.value}("");require(sent, "Falha ao enviar Ether");}}
Delegatecall
delegatecall
é uma função de baixo nível semelhante a call
.
Quando o contrato A
é executado delegatecall
para o contrato B
, o código de B
é executado com o armazenamento do contrato A
, msg.sender
e msg.value
.
// SPDX-License-Identifier: MITpragma solidity ^0.8.13;// NOTA: Implemente este contrato primeirocontract B {// NOTA: layout de armazenamento deve ser o mesmo do contrato Auint public num;address public sender;uint public value;function setVars(uint _num) public payable {num = _num;sender = msg.sender;value = msg.value;}}contract A {uint public num;address public sender;uint public value;function setVars(address _contract, uint _num) public payable {// O armazenamento de A está definido, B não é modificado.(bool success, bytes memory data) = _contract.delegatecall(abi.encodeWithSignature("setVars(uint256)", _num));}}
Seletor de Função
Quando uma função é chamada, os primeiros 4 bytes calldata
especificam qual função chamar.
Esses 4 bytes são chamados de seletor de função.
Por exemplo, este código abaixo. Ele usa call
para executar transfer
um contrato no endereço addr
.
addr.call(abi.encodeWithSignature("transfer(address,uint256)", 0xSomeAddress, 123))
Os primeiros 4 bytes retornados abi.encodeWithSignature(....)
são o seletor de função.
Posso economizar uma pequena quantidade de gas se pré-computar e inserir o seletor de função em seu código?
Aqui está como o seletor de função é calculado.
// SPDX-License-Identifier: MITpragma solidity ^0.8.13;contract FunctionSelector {/*"transfer(address,uint256)"0xa9059cbb"transferFrom(address,address,uint256)"0x23b872dd*/function getSelector(string calldata _func) external pure returns (bytes4) {return bytes4(keccak256(bytes(_func)));}}
Chamando outro contrato
Um contrato pode chamar outros contratos de 2 maneiras.
A maneira mais fácil é apenas chamá-lo da seguinte maneira: A.foo(x, y, z)
.
Outra maneira de chamar outros contratos é usar o call
. Este método não é o mais recomendado.
// SPDX-License-Identifier: MITpragma solidity ^0.8.13;// Contrato a ser chamadocontract Callee {uint public x;uint public value;function setX(uint _x) public returns (uint) {x = _x;return x;}function setXandSendEther(uint _x) public payable returns (uint, uint) {x = _x;value = msg.value;return (x, value);}}// Contrato que realizará as chamadascontract Caller {// Dessa forma passamos o contrato Callee por parâmetro// através do _callee conseguimos acessar todas as funções// públicas do contrato Calleefunction setX(Callee _callee, uint _x) public {uint x = _callee.setX(_x);}function setXFromAddress(address _addr, uint _x) public {// Dessa forma, definimos o contrato Callee dentro// da função, passando o endereço por parâmetro no// contrato Callee e então podemos chamar suas funçõesCallee callee = Callee(_addr);callee.setX(_x);}function setXandSendEther(Callee _callee, uint _x) public payable {(uint x, uint value) = _callee.setXandSendEther{value: msg.value}(_x);}}