title | tags | ||
---|---|---|---|
55. Chamada Múltipla |
|
Recentemente, tenho estudado solidity novamente para revisar os detalhes e escrever um guia simplificado de "WTF Solidity" para iniciantes (programadores experientes podem procurar outros tutoriais). Serão publicadas de 1 a 3 lições por semana.
Twitter: @0xAA_Science
Comunidade: Discord|Grupo do WeChat|Site oficial wtf.academy
Todo o código e tutoriais estão disponíveis no GitHub: github.com/AmazingAng/WTF-Solidity
Nesta lição, vamos falar sobre o contrato MultiCall, que tem como objetivo executar várias chamadas de função em uma única transação, reduzindo significativamente as taxas e aumentando a eficiência.
Em Solidity, o contrato MultiCall permite que executemos várias chamadas de função em uma única transação. Suas vantagens são as seguintes:
-
Conveniência: Com o MultiCall, você pode chamar diferentes funções de diferentes contratos em uma única transação, usando diferentes parâmetros para cada chamada. Por exemplo, você pode consultar o saldo de várias contas de tokens ERC20 de uma só vez.
-
Economia de gás: O MultiCall permite combinar várias transações em uma única transação com várias chamadas, economizando gás.
-
Atomicidade: O MultiCall permite que o usuário execute todas as operações em uma única transação, garantindo que todas as operações sejam bem-sucedidas ou todas falhem, mantendo a atomicidade. Por exemplo, você pode realizar uma série de transações de tokens em uma ordem específica.
Agora vamos estudar o contrato MultiCall, que é uma versão simplificada do contrato MultiCall da MakerDAO MultiCall.
O contrato MultiCall define duas estruturas:
-
Call
: Esta é uma estrutura de chamada que contém o contrato de destino a ser chamadotarget
, uma flag indicando se a chamada pode falharallowFailure
e os dados da chamadacallData
. -
Result
: Esta é uma estrutura de resultado que contém uma flag indicando se a chamada foi bem-sucedidasuccess
e os dados de retorno da chamadareturnData
.
O contrato contém apenas uma função para executar chamadas múltiplas:
multicall()
: Esta função recebe um array de estruturas Call como parâmetro, garantindo que o tamanho dos targets e callData sejam iguais. A função executa as chamadas em um loop e reverte a transação se alguma chamada falhar.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract Multicall {
// Estrutura Call, contendo o contrato de destino target, a flag allowFailure e os dados da chamada callData
struct Call {
address target;
bool allowFailure;
bytes callData;
}
// Estrutura Result, contendo a flag success e os dados de retorno da chamada returnData
struct Result {
bool success;
bytes returnData;
}
/// @notice Combina várias chamadas (com diferentes contratos/métodos/parâmetros) em uma única chamada
/// @param calls Array de estruturas Call
/// @return returnData Array de estruturas Result
function multicall(Call[] calldata calls) public returns (Result[] memory returnData) {
uint256 length = calls.length;
returnData = new Result[](length);
Call calldata calli;
// Executa as chamadas em um loop
for (uint256 i = 0; i < length; i++) {
Result memory result = returnData[i];
calli = calls[i];
(result.success, result.returnData) = calli.target.call(calli.callData);
// Se tanto calli.allowFailure quanto result.success forem falsos, reverte a transação
if (!(calli.allowFailure || result.success)){
revert("Multicall: call failed");
}
}
}
}
-
Primeiro, implantamos um contrato ERC20 muito simples chamado
MCERC20
e anotamos o endereço do contrato.// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract MCERC20 is ERC20{ constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_){} function mint(address to, uint amount) external { _mint(to, amount); } }
-
Implantamos o contrato
MultiCall
. -
Obtemos os
calldata
para as chamadas. Vamos criar 50 e 100 unidades de tokens para dois endereços. Você pode preencher os parâmetros da funçãomint()
na página de chamadas do Remix e clicar no botão Calldata para copiar o calldata codificado. Exemplo:to: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 amount: 50 calldata: 0x40c10f190000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc40000000000000000000000000000000000000000000000000000000000000032
Se você não está familiarizado com
calldata
, pode ler a lição 29 do WTF Solidity. -
Usamos a função
multicall()
do contratoMultiCall
para chamar a funçãomint()
do contrato ERC20 e criar 50 e 100 unidades de tokens para dois endereços. Exemplo:calls: [["0x0fC5025C764cE34df352757e82f7B5c4Df39A836", true, "0x40c10f190000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc40000000000000000000000000000000000000000000000000000000000000032"], ["0x0fC5025C764cE34df352757e82f7B5c4Df39A836", false, "0x40c10f19000000000000000000000000ab8483f64d9c6d1ecf9b849ae677dd3315835cb20000000000000000000000000000000000000000000000000000000000000064"]]
-
Usamos a função
multicall()
do contratoMultiCall
para chamar a funçãobalanceOf()
do contrato ERC20 e verificar o saldo dos dois endereços que criamos tokens anteriormente. O seletor da funçãobalanceOf()
é0x70a08231
. Exemplo:[["0x0fC5025C764cE34df352757e82f7B5c4Df39A836", true, "0x70a082310000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc4"], ["0x0fC5025C764cE34df352757e82f7B5c4Df39A836", false, "0x70a08231000000000000000000000000ab8483f64d9c6d1ecf9b849ae677dd3315835cb2"]]
Você pode verificar os valores de retorno das chamadas na seção
decoded output
. Os saldos dos dois endereços são `0x000000000000000000000000000000000000000