Proxy atualizável
WEB3DEV Team
# Proxy atualizável
Exemplo de contrato de proxy atualizável. Nunca use isso em produção.
Este exemplo mostra
- como usar
delegatecalle
retornar dados quandofallback
é chamado. - como armazenar o endereço de
admin
eimplementation
em um slot específico.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
// Padrão de proxy atualizável transparente
contract CounterV1 {
uint public count;
function inc() external {
count += 1;
}
}
contract CounterV2 {
uint public count;
function inc() external {
count += 1;
}
function dec() external {
count -= 1;
}
}
contract BuggyProxy {
address public implementation;
address public admin;
constructor() {
admin = msg.sender;
}
function _delegate() private {
(bool ok, bytes memory res) = implementation.delegatecall(msg.data);
require(ok, "delegatecall failed");
}
fallback() external payable {
_delegate();
}
receive() external payable {
_delegate();
}
function upgradeTo(address _implementation) external {
require(msg.sender == admin, "not authorized");
implementation = _implementation;
}
}
contract Dev {
function selectors()
external
view
returns (
bytes4,
bytes4,
bytes4
)
{
return (
Proxy.admin.selector,
Proxy.implementation.selector,
Proxy.upgradeTo.selector
);
}
}
contract Proxy {
// Todas as funções/variáveis devem ser privadas, encaminhe todas as chamadas para fallback
// -1 para pré-imagem desconhecida
// 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc
bytes32 private constant IMPLEMENTATION_SLOT =
bytes32(uint(keccak256("eip1967.proxy.implementation")) - 1);
// 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103
bytes32 private constant ADMIN_SLOT =
bytes32(uint(keccak256("eip1967.proxy.admin")) - 1);
constructor() {
_setAdmin(msg.sender);
}
modifier ifAdmin() {
if (msg.sender == _getAdmin()) {
_;
} else {
_fallback();
}
}
function _getAdmin() private view returns (address) {
return StorageSlot.getAddressSlot(ADMIN_SLOT).value;
}
function _setAdmin(address _admin) private {
require(_admin != address(0), "admin = zero address");
StorageSlot.getAddressSlot(ADMIN_SLOT).value = _admin;
}
function _getImplementation() private view returns (address) {
return StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value;
}
function _setImplementation(address _implementation) private {
require(_implementation.code.length > 0, "implementation is not contract");
StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value = _implementation;
}
// Interface do administrador//
function changeAdmin(address _admin) external ifAdmin {
_setAdmin(_admin);
}
// 0x3659cfe6
function upgradeTo(address _implementation) external ifAdmin {
_setImplementation(_implementation);
}
// 0xf851a440
function admin() external ifAdmin returns (address) {
return _getAdmin();
}
// 0x5c60da1b
function implementation() external ifAdmin returns (address) {
return _getImplementation();
}
// Interface do usuário //
function _delegate(address _implementation) internal virtual {
assembly {
// Copia msg.data. Assumimos o controle total da memória nesta montagem em linha
// bloquear porque não retornará ao código Solidity. Nós sobrescrevemos o
// Nós sobrescrevemos o bloco de rascunho de Solidity na posição de memória 0.
// calldatacopy(t, f, s) - copia S bytes de calldata na posição f para mem na posição t
// calldatasize() - tamanho dos dados da chamada em bytes
calldatacopy(0, 0, calldatasize())
// Chama a implementação.
// out and outsize são 0 porque ainda não sabemos o tamanho.
// delegatecall(g, a, in, insize, out, outsize) -
// - contrato de chamada no endereço a
// - com entrada mem[in…(in+insize))
// - fornecimento de gás g
// - área de saída mem[out…(out+outsize))
// - retornando 0 em caso de erro (por exemplo, falta de gás) e 1 em caso de sucesso
let result := delegatecall(gas(), _implementation, 0, calldatasize(), 0, 0)
// Copie os dados retornados.
// returndatacopy(t, f, s) - copia S bytes de returndata na posição f para mem na posição t
// returndatasize() - tamanho do último returndata
returndatacopy(0, 0, returndatasize())
switch result
// delegatecall retorna 0 em caso de erro.
case 0 {
// revert(p, s) - finaliza a execução, reverte mudanças de estado, retorna dados mem[p…(p+s))
revert(0, returndatasize())
}
default {
// return(p, s) - finaliza a execução, retorna dados mem[p…(p+s))
return(0, returndatasize())
}
}
}
function _fallback() private {
_delegate(_getImplementation());
}
fallback() external payable {
_fallback();
}
receive() external payable {
_fallback();
}
}
contract ProxyAdmin {
address public owner;
constructor() {
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner, "not owner");
_;
}
function getProxyAdmin(address proxy) external view returns (address) {
(bool ok, bytes memory res) = proxy.staticcall(
abi.encodeCall(Proxy.implementation, ())
);
require(ok, "call failed");
return abi.decode(res, (address));
}
function getProxyImplementation(address proxy) external view returns (address) {
(bool ok, bytes memory res) = proxy.staticcall(abi.encodeCall(Proxy.admin, ()));
require(ok, "call failed");
return abi.decode(res, (address));
}
function changeProxyAdmin(address payable proxy, address admin) external onlyOwner {
Proxy(proxy).changeAdmin(admin);
}
function upgrade(address payable proxy, address implementation) external onlyOwner {
Proxy(proxy).upgradeTo(implementation);
}
}
library StorageSlot {
struct AddressSlot {
address value;
}
function getAddressSlot(bytes32 slot)
internal
pure
returns (AddressSlot storage r)
{
assembly {
r.slot := slot
}
}
}
contract TestSlot {
bytes32 public constant slot = keccak256("TEST_SLOT");
function getSlot() external view returns (address) {
return StorageSlot.getAddressSlot(slot).value;
}
function writeSlot(address _addr) external {
StorageSlot.getAddressSlot(slot).value = _addr;
}
}