EVM 中的钻石:突破限制的可扩展代理

本文介绍了 Diamond (EIP-2535) 协议,它是一种将智能合约的功能模块化并使其可升级的方法。Diamond 允许将合约逻辑分割成多个 facet,这些 facet 可以独立升级,从而突破了 EVM 的 24KB 代码大小限制,并提供了一种组织和管理大型智能合约代码库的有效方式。文章还提供了示例代码和部署步骤,展示了如何使用 Diamond 协议来构建可升级的智能合约。

在之前的博客文章(part1, part2)中,我们了解到代理最初是作为一种升级合约的方式。之后,我们有了 UUPS 以提高效率,以及用于廉价克隆的最小代理。但是,如果一个可升级的合约不够用怎么办?如果需要数百个模块,每个模块都可以独立升级,而不会破坏其余模块怎么办?

这就是 Diamonds (EIP-2535 )的用武之地。

摘自 EIP 规范:https://learnblockchain.cn/docs/eips/EIPS/eip-2535

工作原理图示

摘自 EIP 规范:https://learnblockchain.cn/docs/eips/EIPS/eip-2535

在这里我们可以看到:

  • FacetADiamond1Diamond2 使用
  • FacetBDiamond1Diamond2 使用

添加/替换/删除 函数

IDiamond 接口:

每个 Diamond 必须 实现 IDiamond 接口。

  • 在部署时,发出一个 DiamondCut 事件,列出所有 immutable 函数和任何正在添加的 external 函数。
  • 任何时候你 添加替换删除 外部函数时,你必须发出一个 DiamondCut 事件。这适用于 所有 升级和函数更改,无论是通过 diamondCut 执行的,还是通过任何其他机制执行的。
interface IDiamond {
    enum FacetCutAction {Add, Replace, Remove}

    struct FacetCut {
        address facetAddress;
        FacetCutAction action;
        bytes4[] functionSelectors;
    }

    event DiamondCut(FacetCut[] _diamondCut, address _init, bytes _calldata);
}

IDiamondCut 接口:

interface IDiamondCut is IDiamond {
    /// @notice 添加/替换/删除任意数量的函数,并可选择使用 delegatecall 执行一个函数
    /// @param _diamondCut 包含 facet 地址和函数选择器
    /// @param _init 要执行 _calldata 的合约或 facet 的地址
    /// @param _calldata 一个函数调用,包括函数选择器和参数
    ///                  _calldata 在 _init 上使用 delegatecall 执行
    function diamondCut(
        FacetCut[] calldata _diamondCut,
        address _init,
        bytes calldata _calldata
    ) external;
}
  • Diamonds 通过 selector → facet address 映射来路由调用;更改暴露的函数 = 更新此映射。如果计划在部署后修改该映射,请实现 IDiamondCut
  • diamondCut(FacetCut[] cuts, address init, bytes calldata_) 在一个 tx 中原子性地应用 添加/替换/删除(防止不一致状态),并且是工具/UI 期望的标准入口点。

其中:

1. FacetCut: {facetAddress, action (Add|Replace|Remove), selectors[]}.

2. Add: 每个 selector 必须是 unset: 映射到 facetAddress,否则 revert

3. Replace: 每个 selector 必须 已经存在 并且 不能 已经指向 facetAddress,unset 或相同目标将 revert

4. Remove: 每个 selector 必须 存在。删除一个 unset 的 selector 将 revert

5. Immutable functions 不能被替换/删除,任何尝试都会 revert

使用此流程来保持升级 明确、有意且可互操作

IDiamondLoupe 接口

Diamonds 必须支持通过实现 IDiamondLoupe 接口来检查 facet 和函数。

// A loupe is a small magnifying glass used to look at diamonds.
// These functions look at diamonds
// loupe 是一种用于观察钻石的小型放大镜。
// 这些函数用于观察钻石
interface IDiamondLoupe {
    struct Facet {
        address facetAddress;
        bytes4[] functionSelectors;
    }

    /// @notice 获取所有 facet 地址及其四字节函数选择器。
    /// @return facets_ Facet
    // 获取所有 facet 地址及其四字节函数选择器。
    function facets() external view returns (Facet[] memory facets_);

    /// @notice 获取特定 facet 支持的所有函数选择器。
    /// @param _facet The facet address.
    /// @return facetFunctionSelectors_
    // 获取特定 facet 支持的所有函数选择器。
    function facetFunctionSelectors(address _facet) external view returns (bytes4[] memory facetFunctionSelectors_);

    /// @notice 获取 diamond 使用的所有 facet 地址。
    /// @return facetAddresses_
    // 获取 diamond 使用的所有 facet 地址。
    function facetAddresses() external view returns (address[] memory facetAddresses_);

    /// @notice 获取支持给定 selector 的 facet。
    /// @dev 如果未找到 facet,则返回 address(0)。
    /// @param _functionSelector The function selector.
    /// @return facetAddress_ The facet address.
    // 获取支持给定 selector 的 facet。
    // 如果未找到 facet,则返回 address(0)。
    function facetAddress(bytes4 _functionSelector) external view returns (address facetAddress_);
}

loupe 函数可以在用户界面软件中使用。用户界面调用这些函数来提供关于和可视化 diamonds 的信息。loupe 函数可以在部署功能、升级功能、测试和其他软件中使用。

示例:

src/LibDiamond.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {IDiamondCut} from "./interfaces/IDiamondCut.sol";

library LibDiamond {
    bytes32 internal constant DIAMOND_STORAGE_POSITION =
        keccak256("diamond.standard.diamond.storage");

    error NotContractOwner();
    error FunctionNotFound();
    error InitFailed(bytes returndata);

    struct FacetAddressAndPosition {
        address facetAddress;
        uint96 functionSelectorPosition;
    }

    struct FacetFunctionSelectors {
        bytes4[] functionSelectors;
        uint256 facetAddressPosition;
    }

    struct DiamondStorage {
        mapping(bytes4 => FacetAddressAndPosition) selectorToFacetAndPos;
        mapping(address => FacetFunctionSelectors) facetFunctionSelectors;
        address[] facetAddresses;
        address contractOwner;
    }

    function diamondStorage() internal pure returns (DiamondStorage storage ds) {
        bytes32 pos = DIAMOND_STORAGE_POSITION;
        assembly { ds.slot := pos }
    }

    // ------------- owner -------------
    // ------------- 所有者 -------------
    event OwnershipTransferred(address indexed prevOwner, address indexed newOwner);

    function setContractOwner(address _newOwner) internal {
        DiamondStorage storage ds = diamondStorage();
        address prev = ds.contractOwner;
        ds.contractOwner = _newOwner;
        emit OwnershipTransferred(prev, _newOwner);
    }

    function enforceIsContractOwner() internal view {
        if (msg.sender != diamondStorage().contractOwner) revert NotContractOwner();
    }

    // ------------- cut -------------
    // 使用接口类型:
    event DiamondCut(IDiamondCut.FacetCut[] _diamondCut, address _init, bytes _calldata);

    function diamondCut(
        IDiamondCut.FacetCut[] memory _cut,
        address _init,
        bytes memory _calldata
    ) internal {
        for (uint i; i < _cut.length; i++) {
            IDiamondCut.FacetCutAction action = _cut[i].action;
            if (action == IDiamondCut.FacetCutAction.Add) {
                addFunctions(_cut[i].facetAddress, _cut[i].functionSelectors);
            } else if (action == IDiamondCut.FacetCutAction.Replace) {
                replaceFunctions(_cut[i].facetAddress, _cut[i].functionSelectors);
            } else if (action == IDiamondCut.FacetCutAction.Remove) {
                removeFunctions(_cut[i].facetAddress, _cut[i].functionSelectors);
            }
        }
        emit DiamondCut(_cut, _init, _calldata);
        initializeDiamondCut(_init, _calldata);
    }

    function addFunctions(address _facet, bytes4[] memory _selectors) internal {
        require(_facet != address(0), "Add facet can't be 0");
        DiamondStorage storage ds = diamondStorage();

        FacetFunctionSelectors storage ffs = ds.facetFunctionSelectors[_facet];
        if (ffs.functionSelectors.length == 0) {
            ds.facetAddresses.push(_facet);
            ffs.facetAddressPosition = ds.facetAddresses.length - 1;
        }

        for (uint i; i < _selectors.length; i++) {
            bytes4 sel = _selectors[i];
            require(ds.selectorToFacetAndPos[sel].facetAddress == address(0), "Selector exists");
            ds.selectorToFacetAndPos[sel] = FacetAddressAndPosition({
                facetAddress: _facet,
                functionSelectorPosition: uint96(ffs.functionSelectors.length)
            });
            ffs.functionSelectors.push(sel);
        }
    }

    function replaceFunctions(address _facet, bytes4[] memory _selectors) internal {
        require(_facet != address(0), "Replace facet can't be 0");
        DiamondStorage storage ds = diamondStorage();

        FacetFunctionSelectors storage ffs = ds.facetFunctionSelectors[_facet];
        if (ffs.functionSelectors.length == 0) {
            ds.facetAddresses.push(_facet);
            ffs.facetAddressPosition = ds.facetAddresses.length - 1;
        }

        for (uint i; i < _selectors.length; i++) {
            bytes4 sel = _selectors[i];
            address oldFacet = ds.selectorToFacetAndPos[sel].facetAddress;
            require(oldFacet != _facet, "Replace same facet");
            _removeFunction(oldFacet, sel);
            ds.selectorToFacetAndPos[sel] = FacetAddressAndPosition({
                facetAddress: _facet,
                functionSelectorPosition: uint96(ffs.functionSelectors.length)
            });
            ffs.functionSelectors.push(sel);
        }
    }

    function removeFunctions(address _facet, bytes4[] memory _selectors) internal {
        require(_facet == address(0), "Remove facet must be 0");
        for (uint i; i < _selectors.length; i++) {
            _removeFunction(diamondStorage().selectorToFacetAndPos[_selectors[i]].facetAddress, _selectors[i]);
        }
    }

    function _removeFunction(address _facet, bytes4 _sel) private {
        DiamondStorage storage ds = diamondStorage();
        FacetAddressAndPosition memory fap = ds.selectorToFacetAndPos[_sel];
        address facet = fap.facetAddress;
        require(facet != address(0), "Selector !exist");

        FacetFunctionSelectors storage ffs = ds.facetFunctionSelectors[facet];
        uint last = ffs.functionSelectors.length - 1;
        uint pos = fap.functionSelectorPosition;
        if (pos != last) {
            bytes4 lastSel = ffs.functionSelectors[last];
            ffs.functionSelectors[pos] = lastSel;
            ds.selectorToFacetAndPos[lastSel].functionSelectorPosition = uint96(pos);
        }
        ffs.functionSelectors.pop();
        delete ds.selectorToFacetAndPos[_sel];

        if (ffs.functionSelectors.length == 0) {
            uint lastFacetPos = ds.facetAddresses.length - 1;
            uint facetPos = ffs.facetAddressPosition;
            if (facetPos != lastFacetPos) {
                address lastFacetAddr = ds.facetAddresses[lastFacetPos];
                ds.facetAddresses[facetPos] = lastFacetAddr;
                ds.facetFunctionSelectors[lastFacetAddr].facetAddressPosition = facetPos;
            }
            ds.facetAddresses.pop();
            delete ds.facetFunctionSelectors[facet];
        }
    }

    function initializeDiamondCut(address _init, bytes memory _calldata) private {
        if (_init == address(0)) {
            require(_calldata.length == 0, "Init addr is 0");
            return;
        }
        (bool ok, bytes memory ret) = _init.delegatecall(_calldata);
        if (!ok) revert InitFailed(ret);
    }
}

src/interfaces/IDiamondCut.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface IDiamondCut {
    enum FacetCutAction { Add, Replace, Remove }
    struct FacetCut {
        address facetAddress;
        FacetCutAction action;
        bytes4[] functionSelectors;
    }

    /// @notice 添加/替换/删除函数 & 可选择执行 init
    // 添加/替换/删除函数 & 可选择执行 init
    function diamondCut(
        FacetCut[] calldata _diamondCut,
        address _init,
        bytes calldata _calldata
    ) external;

    event DiamondCut(FacetCut[] _diamondCut, address _init, bytes _calldata);
}

src/interfaces/IDiamondLoupe.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface IDiamondLoupe {
    struct Facet { address facetAddress; bytes4[] functionSelectors; }

    function facets() external view returns (Facet[] memory facets_);
    function facetFunctionSelectors(address _facet) external view returns (bytes4[] memory _selectors);
    function facetAddresses() external view returns (address[] memory facetAddresses_);
    function facetAddress(bytes4 _functionSelector) external view returns (address facetAddress_);
}

src/Diamond.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {LibDiamond} from "./LibDiamond.sol";
import {IDiamondCut} from "./interfaces/IDiamondCut.sol";

/// @notice The Diamond itself: holds storage + fallback router
// @notice Diamond 本身:持有存储 + fallback 路由器
contract Diamond {
    constructor(address _contractOwner, IDiamondCut.FacetCut[] memory _cut, address _init, bytes memory _calldata) {
        LibDiamond.setContractOwner(_contractOwner);
        LibDiamond.diamondCut(_cut, _init, _calldata);
    }

    // loupe & ownership are immutable if you put them here,
    // but to keep it idiomatic we route everything via facets.
    // 如果你把 loupe & ownership 放在这里,它们是 immutable 的,
    // 但为了保持惯用性,我们通过 facets 路由一切。

    fallback() external payable {
        address facet = LibDiamond.diamondStorage().selectorToFacetAndPos[msg.sig].facetAddress;
        if (facet == address(0)) revert LibDiamond.FunctionNotFound();
        assembly {
            calldatacopy(0, 0, calldatasize())
            let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0)
            returndatacopy(0, 0, returndatasize())
            switch result
            case 0 { revert(0, returndatasize()) }
            default { return(0, returndatasize()) }
        }
    }

    receive() external payable {}
}

src/facets/DiamondCutFacet.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {LibDiamond} from "../LibDiamond.sol";
import {IDiamondCut} from "../interfaces/IDiamondCut.sol";

contract DiamondCutFacet is IDiamondCut {
    function diamondCut(
        FacetCut[] calldata _cut,
        address _init,
        bytes calldata _calldata
    ) external override {
        LibDiamond.enforceIsContractOwner();
        LibDiamond.diamondCut(_cut, _init, _calldata);
    }
}

src/facets/DiamondLoupeFacet.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {LibDiamond} from "../LibDiamond.sol";
import {IDiamondLoupe} from "../interfaces/IDiamondLoupe.sol";

contract DiamondLoupeFacet is IDiamondLoupe {
    function facets() external view override returns (Facet[] memory facets_) {
        LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage();
        address[] memory addrs = ds.facetAddresses;
        facets_ = new Facet[](addrs.length);
        for (uint i; i < addrs.length; i++) {
            bytes4[] memory selectors = ds.facetFunctionSelectors[addrs[i]].functionSelectors;
            facets_[i] = Facet({facetAddress: addrs[i], functionSelectors: selectors});
        }
    }

    function facetFunctionSelectors(address _facet) external view override returns (bytes4[] memory _selectors) {
        _selectors = LibDiamond.diamondStorage().facetFunctionSelectors[_facet].functionSelectors;
    }

    function facetAddresses() external view override returns (address[] memory facetAddresses_) {
        facetAddresses_ = LibDiamond.diamondStorage().facetAddresses;
    }

    function facetAddress(bytes4 _sel) external view override returns (address facetAddress_) {
        facetAddress_ = LibDiamond.diamondStorage().selectorToFacetAndPos[_sel].facetAddress;
    }
}

src/facets/OwnershipFacet.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {LibDiamond} from "../LibDiamond.sol";

contract OwnershipFacet {
    function owner() external view returns (address) {
        return LibDiamond.diamondStorage().contractOwner;
    }

    function transferOwnership(address _newOwner) external {
        LibDiamond.enforceIsContractOwner();
        LibDiamond.setContractOwner(_newOwner);
    }
}

src/facets/ExampleFacet.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract ExampleFacet {
    uint256 internal counter;

    function increment() external { unchecked { counter++; } }
    function getCounter() external view returns (uint256) { return counter; }
}

设置合约:

让我们编写一个脚本来设置所有的 facets 和 diamond:

script/deployDiamond.s.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "forge-std/Script.sol";

import {Diamond} from "../src/Diamond.sol";
import {IDiamondCut} from "../src/interfaces/IDiamondCut.sol";
import {DiamondCutFacet} from "../src/facets/DiamondCutFacet.sol";
import {DiamondLoupeFacet} from "../src/facets/DiamondLoupeFacet.sol";
import {OwnershipFacet} from "../src/facets/OwnershipFacet.sol";
import {ExampleFacet} from "../src/facets/ExampleFacet.sol";

contract DeployDiamond is Script {
    function run() external {
        // load deployer key from env (use ANVIL default or your own)
        // 从 env 加载部署者密钥(使用 ANVIL 默认值或你自己的)
        uint256 pk = vm.envUint("PRIVATE_KEY");
        address deployer = vm.addr(pk);

        vm.startBroadcast(pk);

        // 1) deploy facets
        // 1) 部署 facets
        DiamondCutFacet cutFacet = new DiamondCutFacet();
        DiamondLoupeFacet loupeFacet = new DiamondLoupeFacet();
        OwnershipFacet ownFacet = new OwnershipFacet();
        ExampleFacet exampleFacet = new ExampleFacet();

        // 2) build the facet cuts
        // 2) 构建 facet cuts
        IDiamondCut.FacetCut  [] memory cut = new IDiamondCut.FacetCut[](4);

        // DiamondCutFacet
        {
            bytes4  [] memory selectors = new bytes4[](1);
            selectors[0] = DiamondCutFacet.diamondCut.selector;
            cut[0] = IDiamondCut.FacetCut({
                facetAddress: address(cutFacet),
                action: IDiamondCut.FacetCutAction.Add,
                functionSelectors: selectors
            });
        }

        // DiamondLoupeFacet
        {
            bytes4[] memory selectors = new bytes4[](4);
            selectors[0] = DiamondLoupeFacet.facets.selector;
            selectors[1] = DiamondLoupeFacet.facetFunctionSelectors.selector;
            selectors[2] = DiamondLoupeFacet.facetAddresses.selector;
            selectors[3] = DiamondLoupeFacet.facetAddress.selector;
            cut[1] = IDiamondCut.FacetCut({
                facetAddress: address(loupeFacet),
                action: IDiamondCut.FacetCutAction.Add,
                functionSelectors: selectors
            });
        }

        // OwnershipFacet
        {
            bytes4 [] memory selectors = new bytes4[](2);
            selectors[0] = OwnershipFacet.owner.selector;
            selectors[1] = OwnershipFacet.transferOwnership.selector;
            cut[2] = IDiamondCut.FacetCut({
                facetAddress: address(ownFacet),
                action: IDiamondCut.FacetCutAction.Add,
                functionSelectors: selectors
            });
        }

        // ExampleFacet
        {
            bytes4  [] memory selectors = new bytes4[](2);
            selectors[0] = ExampleFacet.increment.selector;
            selectors[1] = ExampleFacet.getCounter.selector;
            cut[3] = IDiamondCut.FacetCut({
                facetAddress: address(exampleFacet),
                action: IDiamondCut.FacetCutAction.Add,
                functionSelectors: selectors
            });
        }

        // 3) deploy diamond with the cut
        // 3) 使用 cut 部署 diamond
        Diamond diamond = new Diamond(
            deployer,
            cut,
            address(0), // no init facet
            ""          // no init calldata
        );

        vm.stopBroadcast();

        console.log("Diamond deployed at:", address(diamond));
    }
}

让我们部署:

运行 anvil

anvil

forge 项目的其他终端中运行:

forge script script/deployDiamond.s.sol:DeployDiamond \
  --rpc-url http://localhost:8545 \
  --broadcast

这应该会部署 facets 和 diamond。

你应该会看到类似这样的东西:

让我们执行一些交易:

## Read the counter (should be 0 initially)
// 读取计数器(最初应为 0)
cast call 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9 \
  "getCounter()(uint256)" \
  --rpc-url http://localhost:8545

## Increment the counter (must be a tx)
// 增加计数器(必须是一个交易)
cast send 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9 \
  "increment()" \
  --rpc-url http://localhost:8545 \
  --private-key <YOUR_ANVIL_PK>

## Check again. (The output should be 1)
// 再次检查。(输出应为 1)
cast call 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9 \
  "getCounter()(uint256)" \
  --rpc-url http://localhost:8545

## Lets call other facet
// 让我们调用其他 facet
## Who is the owner? (should be the address you provided in the script)
// 谁是所有者?(应该是你在脚本中提供的地址)
cast call 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9 \
  "owner()(address)" \
  --rpc-url http://localhost:8545

让我们添加一个新的 facet:

src/facets/newFacet.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract ExampleFacetV2 {
    uint256 public counter;

    function increment() external {
        counter++;
    }

    function getCounter() external view returns (uint256) {
        return counter;
    }

    // New functionality!
    // 新功能!
    function decrement() external {
        require(counter > 0, "already zero");
        counter--;
    }
}

让我们部署新的 facet:

forge create src/facets/newFacet.sol:ExampleFacetV2 \
  --rpc-url http://localhost:8545 \
  --private-key <YOUR_ANVIL_PK> --broadcast

## The output should be something like:
// 输出应如下所示:
## [⠊] Compiling...
## No files changed, compilation skipped
// 没有文件更改,跳过编译
## Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
## Deployed to: 0x0165878A594ca255338adfa4d48449f69242Eb8F
## Transaction hash: 0xd78b181b3e57cab0f4d693b7e9963fff3f3a5a68d9d532274a0a6196cbf7bf13

通过 diamondCut 升级

## Get the 4 bytes of the new selector
// 获取新 selector 的 4 个字节
cast sig "decrement()"
## → 0x2baeceb7

## upgrage
// 升级
cast send 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9 \
  "diamondCut((address,uint8,bytes4[])[],address,bytes)" \
  '[(0x0165878A594ca255338adfa4d48449f69242Eb8F,0,[0x2baeceb7])]' \
  0x0000000000000000000000000000000000000000 \
  0x \
  --rpc-url http://localhost:8545 \
  --private-key <YOUR_ANVIL_PK>

## This Tx should Succeed
// 此 Tx 应该成功

让我们尝试执行 facets 之间的混合方法:

## Increment first
// 首先增加
cast send 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9 "increment()" \
  --rpc-url http://localhost:8545 --private-key <YOUR_ANVIL_PK>

## Check counter (should be 2 now, adds 1 the storage)
// 检查计数器(现在应为 2,在存储中添加 1)
cast call 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9 "getCounter()(uint256)" \
  --rpc-url http://localhost:8545
## → should be 1
// 应该是 1

## Call the new function
// 调用新函数
cast send 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9 "decrement()" \
  --rpc-url http://localhost:8545 --private-key <YOUR_ANVIL_PK>

## Check counter again
// 再次检查计数器
cast call 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9 "getCounter()(uint256)" \
  --rpc-url http://localhost:8545
## → should be back to 1
// 应该回到 1

概述

升级前:

0xd09de08a  ->  ExampleFacet   // increment()
0x06661abd  ->  ExampleFacet   // getCounter()

然后我们部署了一个新的 facet ExampleFacetV2,它添加了一个新函数:

  • decrement() → selector, e.g. 0x2baeceb7

并调用了

diamondCut(
  [(ExampleFacetV2, Add(0), [0x2baeceb7])],
  address(0),
  0x
)

升级后,diamond 的 selector 映射现在看起来像:

0xd09de08a  ->  ExampleFacet     // increment()
0x06661abd  ->  ExampleFacet     // getCounter()
0x2baeceb7  ->  ExampleFacetV2   // decrement()

快速完整性检查:

## Should output the first address of the example facet
// 应该输出 example facet 的第一个地址
cast call 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9 "facetAddress(bytes4)(address)" 0xd09de08a --rpc-url http://localhost:8545

## Should output the new address of the exampleV2 facet
// 应该输出 exampleV2 facet 的新地址
cast call 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9 "facetAddress(bytes4)(address)" 0xd09de08a --rpc-url http://localhost:8545

注意: 我们在这里没有触及存储布局,实际上两个 facet 示例都应该使用相同的存储布局。我们不会在这里触及它,以免使这篇文章过于庞大。快速示例:

library CounterStorage {
    // random slot: keccak256("example.counter.storage")
    // 随机槽:keccak256("example.counter.storage")
    bytes32 internal constant SLOT = keccak256("example.counter.storage");

    struct Layout {
        uint256 counter;
    }

    function layout() internal pure returns (Layout storage l) {
        bytes32 slot = SLOT;
        assembly { l.slot := slot }
    }
}

这可以是它们都使用的布局。Facets 必须在存储布局上达成一致。如果你不匹配布局,你的应用将损坏存储。始终使用库隔离存储。

为什么使用 Diamonds

开发者使用 Diamonds 而不是 “just another proxy” 有很多原因:

  • 一个地址,无限功能

一个 diamond 从一个地址暴露其所有功能。这使得部署、测试以及与其他智能合约、钱包和 UI 的集成更加简单。

  • 没有 24KB 合约大小上限

EVM 将合约字节码大小限制为 24KB。使用 Diamonds,你可以通过将逻辑拆分为 facets 同时保持一切在一个地址可访问来扩展超出此限制。

  • 有组织和模块化的代码库

大型系统可以分解为独立的 facets,每个 facet 处理一个特定的问题,同时仍然共享相同的存储。这使得事情有条理、更容易理解和 gas 高效。

  • 在你需要时可升级

可升级的 diamonds 可以添加、替换或删除功能,而无需触及现有部分。你可以随着时间的推移添加多少功能没有上限。

  • 如果需要,可以是 immutable 的

并非每个系统都需要永远的可升级性。你可以将 diamond 部署为 immutable,或者稍后使可升级的 diamond immutable 以获得额外的信任保证。

  • 重用现有已部署的合约

Diamonds 不需要一切都重新部署。你可以从已部署的合约中编写一个 diamond,有效地从链上代码构建自定义平台或库。

总结

Diamonds 是什么: 一个合约(diamond),它使用 delegatecall 将函数选择器路由到许多无状态 facets,因此所有状态都存在于一个地址。

它们如何工作: diamond 的 fallback 读取 msg.sig → 在 selector 映射中查找 facet → delegatecall 进入它 → 返回数据,就像 diamond 实现了该函数一样。

核心接口:

  • IDiamondCut → 标准的、原子性的添加/替换/删除 selectors(+ 可选的 init)。
  • IDiamondLoupe → 查询 facets/selectors 以进行 UI、工具和完整性检查。

为什么要使用它们: 打破 24KB 限制,为所有内容保留一个地址,将大型代码库组织成模块,独立升级部件,可选地 immutable 地冻结,甚至可以从现有的链上合约进行组合。

注意事项: 存储布局必须共享且明确;升级需要治理;审计涵盖更多表面;注意 selector 冲突和 init 排序。

心智模型: 一个地址,多个模块,共享状态。

  • 原文链接: medium.com/@andrey_obruc...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 1
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Andrey Obruchkov
Andrey Obruchkov
江湖只有他的大名,没有他的介绍。