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
在这里我们可以看到:
FacetA被Diamond1和Diamond2使用FacetB被Diamond1和Diamond2使用
添加/替换/删除 函数
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 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~