Recentemente, tenho revisitado o Solidity para consolidar alguns detalhes e escrever um "Guia de Introdução ao Solidity" para iniciantes (os mestres em programação podem procurar por outro tutorial), com atualizações semanais de 1 a 3 artigos.
Twitter: @0xAA_Science
Comunidade: Discord | Grupo no WhatsApp | Site oficial wtf.academy
Todo o código e tutorial estão disponíveis no GitHub: github.com/AmazingAng/WTF-Solidity
O delegatecall é uma função de baixo nível do tipo address em Solidity, semelhante ao call. Se delegate significa delegação/representação, então o que o delegatecall está delegando?
Quando o usuário A chama o contrato B para chamar o contrato C, a função executada é a do contrato C, e o contexto é do contrato C: msg.sender é o endereço de B, e se a função alterar variáveis de estado, os efeitos serão aplicados às variáveis do contrato C.
Enquanto, ao usar o delegatecall do usuário A no contrato B para chamar o contrato C, a função executada ainda é a do contrato C, mas o contexto continua sendo do contrato B: msg.sender é o endereço de A, e as alterações nas variáveis de estado serão aplicadas ao contrato B.
Podemos entender da seguinte maneira: um investidor (usuário A) confia seus ativos (variáveis de estado do contrato B) a um agente de investimento de risco (contrato C) para administrar. A função executada é do agente de investimento de risco, mas as alterações afetam os ativos.
A sintaxe do delegatecall é semelhante à do call:
enderecoDoContratoAlvo.delegatecall(códigoBinário);O códigoBinário é obtido utilizando a função de codificação estruturada abi.encodeWithSignature:
abi.encodeWithSignature("assinaturaDaFunção", parâmetrosSeparadosPorVírgulas)A assinaturaDaFunção é "nomeDaFunção(tiposDeParâmetrosSeparadosPorVírgulas)", por exemplo, abi.encodeWithSignature("f(uint256,address)", _x, _addr).
Diferentemente do call, o delegatecall pode definir a quantidade de gas a ser enviado na transação, mas não pode definir a quantidade de ETH.
Atenção: O
delegatecallpossui riscos de segurança. Deve-se garantir que a estrutura de armazenamento de variáveis de estado dos contratos atual e de destino seja a mesma, e que o contrato de destino seja seguro, caso contrário, pode resultar em perda de ativos.
Atualmente, o delegatecall é principalmente utilizado em dois cenários:
-
Contrato de Procuração (
Proxy Contract): separa o armazenamento de variáveis e a lógica do contrato. O contrato de Procuração (Proxy Contract) armazena todas as variáveis pertinentes e mantém o endereço da lógica do contrato; todas as funções estão dentro do contrato de lógica (Logic Contract), sendo executadas por meio dedelegatecall. Para atualizar, basta direcionar o contrato de Procuração para o novo contrato de lógica. -
Diamantes EIP-2535: Diamantes são contratos de procuração com múltiplos contratos de implementação. Para mais informações, consulte: Introdução ao Padrão Diamante.
Chamada: Você (A) chama o contrato B para chamar o contrato de destino C.
Primeiramente, vamos escrever um contrato de destino C simples: com duas variáveis públicas num e sender', sendo uint256eaddress, respectivamente; uma função que define numcomo o valor recebido em_nume definesendercomomsg.sender`.
// Contrato de Destino C
contract C {
uint public num;
address public sender;
function setVars(uint _num) public payable {
num = _num;
sender = msg.sender;
}
}Em seguida, o contrato B deve ter a mesma estrutura de armazenamento de variáveis do contrato de destino C, com duas variáveis e na mesma ordem: num e sender.
contract B {
uint public num;
address public sender;
}Agora, vamos chamar a função setVars do contrato C usando call e delegatecall para entender melhor as diferenças.
A função callSetVars chama o setVars usando call. Ela recebe dois parâmetros, _addr e _num, correspondentes ao endereço do contrato C e ao parâmetro do setVars.
// Chama o setVars() do contrato C usando a função call, modificando as variáveis de estado do contrato C
function callSetVars(address _addr, uint _num) external payable {
// chamar setVars()
(bool success, bytes memory data) = _addr.call(
abi.encodeWithSignature("setVars(uint256)", _num)
);
}Já a função delegatecallSetVars chama o setVars usando delegatecall. Assim como a função callSetVars, ela recebe dois parâmetros, _addr e _num, correspondentes ao endereço do contrato C e ao parâmetro do setVars.
// Chama o setVars() do contrato C usando delegatecall, modificando as variáveis de estado do contrato B
function delegatecallSetVars(address _addr, uint _num) external payable {
// delegatecall setVars()
(bool success, bytes memory data) = _addr.delegatecall(
abi.encodeWithSignature("setVars(uint256)", _num)
);
}-
Primeiro, implantamos os contratos
BeC. -
Após a implantação, verificamos os valores iniciais das variáveis de estado do contrato
C, que também são os mesmos para o contratoB. -
Em seguida, executamos a função
callSetVarsdo contratoB, passando o endereço do contratoCe10. -
Após a execução, as variáveis de estado do contrato
Csão alteradas:numé definido como10esenderé o endereço do contratoB. -
Agora, executamos a função
delegatecallSetVarsdo contratoB, passando o endereço do contratoCe100. -
Devido ao
delegatecall, o contexto é do contratoB. Após a execução, as variáveis de estado do contratoBserão alteradas:numé definido como100esenderé o endereço da sua carteira. As variáveis de estado do contratoCnão serão modificadas.
Neste artigo, exploramos a função delegatecall, mais um recurso de baixo nível em Solidity. Semelhante ao call, o delegatecall é usado para chamar outros contratos, com a diferença sendo o contexto de execução: B chama C, o contexto é C; enquanto B delegatecall C, o contexto é B. Atualmente, a principal aplicação do delegatecall é em contratos de procuração e nos Diamantes EIP-2535.





