Почему функция называется upgradeToAndCall(), а не просто upgradeTo()?
При обновлении контракта Логики можно сделать вызов, как если бы ProxyAdmin был msg.sender и транзакция делегировала вызов в Логику, как если бы это было обычное взаимодействие с прокси. Конечно, внутри fallback() этого не произойдет, потому что вызовы ProxyAdmin направляются в логику обновления.
Приведенный ниже код взят из ERC1967Utils.sol, с которым TransparentUpgradeableProxy компонуется для обеспечения возможности обновления слота Логики. Библиотека предоставляет внутреннюю вспомогательную функцию для обновления слота, содержащего адрес Логики.
/**
* @dev Performs implementation upgrade with additional setup call if data is nonempty.
* This function is payable only if the setup call is performed, otherwise `msg.value` is rejected
* to avoid stuck value in the contract.
*
* Emits an {IERC1967-Upgraded} event.
*/
function upgradeToAndCall(address newImplementation, bytes memory data) internal {
_setImplementation(newImplementation);
emit IERC1967.Upgraded(newImplementation);
if (data.length > 0) {
Address.functionDelegateCall(newImplementation, data);
} else {
_checkNonPayable();
}
}
Он будет выполнять delegatecall в контракт Логики только в том случае, если data.length > 0.
upgradeToAndCall() также выполняет delegatecall от прокси к Логике в той же транзакции, что и обновление. Это то же самое, как если бы ProxyAdmin вызвал прокси, используя любые calldata, указанные в data, а затем прокси сделал бы delegatecall в самой Логике.
Таким образом, ProxyAdmin может делать произвольные вызовы прокси.
Обратите внимание, что upgradeToAndCall не требует, чтобы обновленный контракт был другой реализацией - можно «обновиться» до той же самой Логики.
Из этого следует, что контракт ProxyAdmin может делать произвольные delegatecall в контракт Логики через прокси - но отправителем msg.sender с точки зрения Transparent Proxy является ProxyAdmin.
Единственное ограничение, которое накладывает ProxyAdmin на обновление, заключается в том, что он не может обновить пустой контракт (адрес без байткода). Функция _setImplementation проверяет, что длина кода новой реализации больше нуля.
/**
* @dev Stores a new address in the ERC-1967 implementation slot.
*/
function _setImplementation(address newImplementation) private {
if (newImplementation.code.length == 0) {
revert ERC1967InvalidImplementation(newImplementation);
}
StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value = newImplementation;
}
Подведем итоги:
1. Transparent Upgradeable Proxy - это шаблон проектирования для предотвращения столкновения селекторов функций между прокси и Логикой;
2. Функция fallback является единственной публичной функцией в Transparent Upgradeable Proxy;
3. Функциональность обновления может быть вызвана только администратором через функцию fallback. Все вызовы с неадминистративных адресов превращаются в delegatecalls в прокси;
4. Transparent Upgradeable Proxy использует неизменяемую переменную для хранения адреса администратора, чтобы сэкономить газ. Для того, чтобы соответствовать ERC-1967, он хранит адрес администратора в слоте admin, указанном в ERC-1967, даже если он никогда не читает из этого слота;
5. Поскольку администратор не может быть изменен, он устанавливается в смарт-контракт под названием AdminProxy. AdminProxy раскрывает единственную функцию upgradeAndCall(), которая может быть вызвана только владельцем AdminProxy. Владелец AdminProxy может быть изменен.
#proxy #transparent