Solidity透明代理与UUPS代理的最核心区别

共同点

  1. 基础目标相同
  • 两种模式都旨在实现合约的可升级性
  • 都遵循代理模式的基本原理:将存储与逻辑分离
  1. 存储标准统一
  • 都使用EIP-1967定义的存储槽存储实现地址:
  • 计算方式相同:bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1)
  1. 代理转发机制
  • 都使用delegatecall将调用转发到逻辑合约
     
    // 两种代理模式都使用类似的fallback函数转发逻辑
       fallback() external payable {
           _fallback();
       }
       
       function _fallback() internal {
           _delegate(_implementation());
       }

核心区别

1. 升级逻辑的位置(最根本区别)

透明代理代码
 
// 透明代理中,升级逻辑在代理合约中
contract TransparentProxy is Proxy {
    bytes32 private constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
    bytes32 private constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
    
    // 升级函数位于代理合约
    function upgradeTo(address newImplementation) external {
        require(msg.sender == admin(), "Only admin can upgrade");
        assembly {
            sstore(_IMPLEMENTATION_SLOT, newImplementation)
        }
    }
    
    // 读取实现地址
    function _implementation() internal view override returns (address) {
        address impl;
        assembly {
            impl := sload(_IMPLEMENTATION_SLOT)
        }
        return impl;
    }
}
 
UUPS代理代码
 
// UUPS代理非常简单,仅包含转发逻辑
contract UUPSProxy is ERC1967Proxy {
    constructor(address _logic, bytes memory _data) ERC1967Proxy(_logic, _data) {}
}

// 升级逻辑位于逻辑合约中
contract UUPSLogicV1 is Initializable, UUPSUpgradeable {
    // 继承自UUPSUpgradeable,包含升级逻辑
    
    // 升级函数在逻辑合约中
    function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
    
    // 升级实现由UUPSUpgradeable提供:
    // function upgradeToAndCall(address newImplementation, bytes memory data) external payable
}

2. 函数选择器冲突的解决方式

透明代理的选择器冲突处理
 
UUPS代理的处理方式
 

3. 实现地址的读写方式

透明代理
 
// 在代理合约中管理实现地址
function _implementation() internal view override returns (address) {
    address impl;
    assembly {
        impl := sload(_IMPLEMENTATION_SLOT)
    }
    return impl;
}

function upgradeTo(address newImplementation) external {
    require(msg.sender == admin(), "Only admin can upgrade");
    assembly {
        sstore(_IMPLEMENTATION_SLOT, newImplementation)
    }
}
UUPS代理
 
// 在ERC1967Upgrade.sol中定义
abstract contract ERC1967Upgrade {
    // 读取实现地址
    function _getImplementation() internal view returns (address) {
        return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
    }
    
    // 更新实现地址(由逻辑合约调用)
    function _upgradeToAndCallUUPS(
        address newImplementation,
        bytes memory data,
        bool forceCall
    ) internal {
        // 检查新实现是否支持UUPS
        if (StorageSlot.getBooleanSlot(_ROLLBACK_SLOT).value) {
            _setImplementation(newImplementation);
        } else {
            try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {
                require(slot == _IMPLEMENTATION_SLOT, "ERC1967Upgrade: unsupported proxiableUUID");
            } catch {
                revert("ERC1967Upgrade: new implementation is not UUPS");
            }
            _upgradeToAndCall(newImplementation, data, forceCall);
        }
    }
}

4. 初始化和升级调用对比

透明代理部署和升级
 
UUPS代理部署和升级
 
 

适用场景

  • 透明代理适合
  • 安全性要求极高的项目
  • 可接受较高Gas成本的应用
  • 大型DeFi协议或金融应用
  • UUPS代理适合
  • 注重Gas效率的应用
  • 频繁交互的合约
  • 有完善测试和审计流程的团队

结论

透明代理和UUPS代理最核心的区别在于升级逻辑的位置透明代理将升级逻辑放在代理合约中,而UUPS将升级逻辑放在实现合约中。这一基本差异导致了它们在Gas效率、安全模型和使用方式上的不同。
从代码层面可以清晰看到:透明代理的代理合约更复杂,包含了权限控制和升级逻辑;而UUPS代理的代理合约非常简洁,大部分逻辑都封装在实现合约中。理解这些代码差异有助于选择最适合项目需求的代理模式。
posted @ 2025-03-14 10:24  若-飞  阅读(108)  评论(0)    收藏  举报