В Solidity распространенной и эффективной практикой является обновление смарт-контрактов через режим прокси, который позволяет выполнять обновления без нарушения функциональности существующих контрактов. Основная идея этой модели состоит в том, чтобы отделить состояние контракта от основной логики, чтобы обновленную логику можно было развернуть в новом контракте, а затем новую логику можно было вызвать через прокси-контракт для достижения цели обновление.
Во-первых, предположим, что существует первоначальная версия смарт-контракта (называемая контрактом реализации), которая содержит переменные состояния и основную бизнес-логику.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.2 <0.9.0;
// Первоначальная версия контракта
contract MyContract {
uint public data;
function setData(uint _data) public {
data = _data;
}
function getData() public view returns (uint) {
return data;
}
}
Затем создайте новую версию контракта, содержащую новую логику или исправления.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.2 <0.9.0;
// Обновленная версия контракта
contract MyContractV2 {
uint public data;
mapping(address => bool) public accessAllowed;
function setData(uint _data) public {
require(accessAllowed[msg.sender], "Access not allowed");
data = _data;
}
function getData() public view returns (uint) {
return data;
}
function grantAccess(address _addr) public {
accessAllowed[_addr] = true;
}
function revokeAccess(address _addr) public {
accessAllowed[_addr] = false;
}
}
Создайте прокси-контракт для перенаправления вызовов на фактическую реализацию контракта. Прокси-контракт обычно поддерживает тот же интерфейс, что и исходная версия, и содержит адрес, указывающий на текущую версию реализации.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.2 <0.9.0;
// агентский договор
contract MyContractProxy {
address public currentVersion;
address public owner;
constructor(address _currentVersion) {
currentVersion = _currentVersion;
owner = msg.sender;
}
// Переадресовать все звонки на актуальную версию договора
fallback() external payable {
address implementation = currentVersion;
require(implementation != address(0), "Contract implementation not set");
assembly {
let ptr := mload(0x40)
calldatacopy(ptr, 0, calldatasize())
let result := delegatecall(gas(), implementation, ptr, calldatasize(), 0, 0)
returndatacopy(ptr, 0, returndatasize())
switch result
case 0 { revert(ptr, returndatasize()) }
default { return(ptr, returndatasize()) }
}
}
// Обновить версию реализации контракта
function upgrade(address newVersion) public {
require(msg.sender == owner, "Only the owner can upgrade");
currentVersion = newVersion;
}
}
В договоре выше мы fallback
Основная логика агентского договора реализована в функции. Сначала он копирует данные входящего вызова в память, а затем использует delegatecall
Перенаправьте вызов логическому контракту и выполните его код в контексте текущего контракта. Наконец, согласно delegatecall
Результат определяет, следует ли откатить транзакцию и вернуть данные об ошибке или вернуть успешные данные.
let ptr := mload(0x40)
mload(0x40)
Чтение ячейки памяти 0x40
значение, местоположение часто называют "free memory указатель" (указатель свободной памяти), который указывает на начало текущей свободной памяти.let ptr := mload(0x40)
Сохраните этот адрес свободной памяти в переменной ptr
для последующего использования.calldatacopy(ptr, 0, calldatasize())
calldatacopy
Будет вызывать данные (включая селектор функций и параметры) из входных данных сообщения, копировать в память середина.ptr
это начальная ячейка памяти.0
Начальная позиция вызывающих данных.calldatasize()
Возвращает размер вызывающих данных.ptr
Начать хранить.let result := delegatecall(gas(), implementation, ptr, calldatasize(), 0, 0)
delegatecall
это EVM Код операции, используемый для выполнения кода в контексте другого контракта с сохранением хранилища текущего контракта, msg.sender. и msg.value。gas()
Возвращает доступный на данный момент остаток gas。implementation
— адрес логического контракта.ptr
Начальное место в памяти, где хранятся данные вызова.calldatasize()
размер вызывающих данных.0
— место хранения возвращаемых данных (изначально установлено значение 0)。0
— размер возвращаемых данных (изначально установлен на 0)。result
середина.returndatacopy(ptr, 0, returndatasize())
returndatacopy
Вернет данные из места возврата вызова копировать в память середина.ptr
это начальная ячейка памяти.0
Начальная позиция возвращаемых данных.returndatasize()
Возвращает предыдущий вызов (т.е. delegatecall
)Размер возвращаемых данных。delegatecall
Возвращаемые данные копировать в память, из ptr
Начать хранить.switch result
switch
заявление, основанное на result
Значение используется для обработки ветвей.result
да delegatecall
Возвращаемое значение, если вызов успешен, равно 1. Если не получится, то это будет 0。case 0 { revert(ptr, returndatasize()) }
result
для 0, означает delegatecall
Вызов не удался.revert(ptr, returndatasize())
Транзакция будет отменена и возвращены неверные данные.ptr
да Исходное положение ошибочных данных в памяти.returndatasize()
да Размер данных об ошибке.default { return(ptr, returndatasize()) }
result
для Нет 0, означает delegatecall
Звонок прошел успешно.return(ptr, returndatasize())
Успешные данные будут возвращены.ptr
да Начальная позиция возвращаемых данных в памяти.returndatasize()
да возвращает размер данных.Проще говоря, этот ассемблерный код есть в агентском контракте. fallback
В функции выполняются следующие операции:
delegatecall
Перенаправьте вызов логическому контракту и выполните его код в контексте текущего контракта.delegatecall
Результат определяет, следует ли откатить транзакцию и вернуть данные об ошибке или вернуть успешные данные.Эта модель гарантирует, что прокси-контракт может гибко перенаправлять вызовы и выполнять определенную бизнес-логику в соответствии с реализацией логического контракта.
MyContract
)иагентский договор(MyContractProxy
),Воляагентский Инициализация договора для указывает на первоначальную версию.MyContractV2
)。upgrade
Функция для обновления текущей версии до адреса контракта новой версии.Следует отметить, что модель прокси должна быть тщательно разработана, чтобы обеспечить совместимость новой версии контракта со старой версией, а также безопасность и прозрачность процесса обновления.