Trabalhando com Proxy

porMatheusem07/06/2022

Nesse artigo iremos aprender o que é um Proxy e para que ele server na Blockchain, como implantá-lo e suas aplicações.

O que é um Proxy e para que ele serve na Blockchain?

Um Proxy, resumidamente, é uma ponte entre versões de um contrato implantado na blockchain.

Tá ok, mas para que serve? O proxy serve para fazer uma atualização de um contrato já implantado, ou seja, mesmo após implantar seu contrato inteligente, você poderá atualizá-lo com novas funcionalidades, corrigir possíveis bugs no contrato, alterar lógicas já implantadas, resumindo, o proxy é como se fosse um versionador de código para os contratos da blockchain.

Se você implantar um novo contrato sem ter implantando um Proxy junto, você não poderá realizar atualizações, por isso, antes de implementar um contrato, analise se você precisará atualizar o contrato ou ele será um contrato já 100% planejado e estático, sem necessidade de alterações.

Exemplo de Proxy básico (MinimalProxy)

Se você tiver um contrato que será implantado várias vezes, use o contrato de proxy mínimo para implantá-los de forma econômica.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
// Código original
// https://github.com/optionality/clone-factory/blob/master/contracts/CloneFactory.sol
contract MinimalProxy {
function clone(address target) external returns (address result) {
// converte o endereço para 20 bytes
bytes20 targetBytes = bytes20(target);
// código atual //
// 3d602d80600a3d3981f3363d3d373d3d3d363d73bebebebebebebebebebebebebebebebebebebebe5af43d82803e903d91602b57fd5bf3
// código de criação //
// copie o código de tempo de execução na memória e retorne-o
// 3d602d80600a3d3981f3
// código de tempo de execução //
// código da delegatecall para endereço
// 363d3d373d3d3d363d73 address 5af43d82803e903d91602b57fd5bf3
assembly {
/*
lê os 32 bytes de memória começando no ponteiro armazenado em 0x40
Em solidity, o slot 0x40 na memória é especial: contém o "ponteiro de memória livre"
que aponta para o final da memória atualmente alocada.
*/
let clone := mload(0x40)
// armazenar 32 bytes na memória começando em "clone"
mstore(
clone,
0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000
)
/*
| 20 bytes |
0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000
^
ponteiro
*/
// armazenar 32 bytes na memória começando em "clone" + 20 bytes
// 0x14 = 20
mstore(add(clone, 0x14), targetBytes)
/*
| 20 bytes | 20 bytes |
0x3d602d80600a3d3981f3363d3d373d3d3d363d73bebebebebebebebebebebebebebebebebebebebe
^
ponteiro
*/
// armazenar 32 bytes na memória começando em "clone" + 40 bytes
// 0x28 = 40
mstore(
add(clone, 0x28),
0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000
)
/*
| 20 bytes | 20 bytes | 15 bytes |
0x3d602d80600a3d3981f3363d3d373d3d3d363d73bebebebebebebebebebebebebebebebebebebebe5af43d82803e903d91602b57fd5bf3
*/
// criar novo contrato
// enviar 0 Ether
// o código começa no ponteiro armazenado em "clone"
// tamanho do código 0x37 (55 bytes)
result := create(0, clone, 0x37)
}
}
}

Exemplo de Proxy atualizável (Upgradeable Proxy)

Exemplo de contrato de proxy atualizável. Nunca use isso em produção, pois você deve desenvolver seu próprio Proxy.

Este exemplo mostra como usar delegatecalle para retornar dados quando fallback é chamado.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract Proxy {
address public implementation;
function setImplementation(address _imp) external {
implementation = _imp;
}
function _delegate(address _imp) internal virtual {
assembly {
// calldatacopy(t, f, s)
// copiar s bytes de calldata na posição f para mem na posição t
calldatacopy(0, 0, calldatasize())
// delegatecall(g, a, in, insize, out, outsize)
// - chama o contrato no endereço a
// - com entrada mem[in…(in+insize))
// - fornecendo gas g
// - e retorna mem[out…(out+outsize))
// - retornando 0 em caso de erro e 1 em caso de sucesso
let result := delegatecall(gas(), _imp, 0, calldatasize(), 0, 0)
// returndatacopy(t, f, s)
// copia s bytes de returndata na posição f para mem na posição t
returndatacopy(0, 0, returndatasize())
switch result
case 0 {
// revert(p, s)
// terminar a execução, reverte as alterações de estado, return data mem[p…(p+s))
revert(0, returndatasize())
}
default {
// return(p, s)
// fim da execução, return data mem[p…(p+s))
return(0, returndatasize())
}
}
}
fallback() external payable {
_delegate(implementation);
}
}
// Primeira versão do contrato
contract V1 {
address public implementation;
uint public x;
function inc() external {
x += 1;
}
}
// Segunda versão do contrato
contract V2 {
address public implementation;
uint public x;
function inc() external {
x += 1;
}
function dec() external {
x -= 1;
}
}

Testar no Remix