---
# System prepended metadata

title: 'Solidity新特性 —— 自定义存储布局 '
tags: [EVM, Solidity]

---


## 0x00: 介绍

这周，Solidity发布了新的版本——0.8.29。新的特点可分为**语言层面**和**编译器层面**。

对于语言层面新特点，允许将合约的存储定位到任意位置。本篇文章会来讲讲这个，并且用代码进行演示。

至于编译器的新特点，可自行查看官方[blog](https://soliditylang.org/blog/2025/03/12/solidity-0.8.29-release-announcement/)。

<br>

先来看看，定义合约存储位置的语法：

```solidity
contract C layout at 2**255 - 42 {
    uint x;
}
```

`layout at 2**255 - 42`，即把该合约存储位置设为了从**2\*\*255 - 42**开始。注意，目前只能用**字面表达式**，而无法使用变量来设置。

> 点击[这里](https://docs.soliditylang.org/en/v0.8.29/contracts.html#custom-storage-layout)查看自定义存储布局的官方文档描述。

<br>

## 0x01：初步测试

随着Solidity发布新版本，remix也是很快地支持了0.8.29版本。下面来编写个demo试试这个特点。

```solidity
//SPDX-License-Identifier:MIT
pragma solidity 0.8.29;

contract Demo layout at 0x666 {  // 设到0x666
    uint256 public varA;

    function getVarASlot() public pure returns(uint256 slot) {
        assembly {
            slot := varA.slot   // 获取varA的slot位置
        }
    }
}
```

<br>

将编译器设为0.8.29版本，然后点击编译。
![image-20250313214746226](https://hackmd.io/_uploads/BJJlxdenJx.png)

<br>

编译完成后进行部署，然后调用`getVarASlot()`，得到的结果为1638，也即0x666。可见，变量确实是从我们设定的位置开始存储。

![image-20250313214836396](https://hackmd.io/_uploads/By-ZeOl3kg.png)


<br>

## 0x02：继承测试

下面接着编写个DemoParent合约来试试继承时的效果。

```solidity
//SPDX-License-Identifier:MIT
pragma solidity 0.8.29;

contract DemoParent layout at 0x888 {
    uint256 public varB;
}
```
<br>

在Demo合约继承DemoParent合约时，很遗憾的发生了报错警告：**不能继承一个自定义存储布局的合约**。

![image-20250313215920166](https://hackmd.io/_uploads/BkE7x_xh1l.png)

<br>

接着，我去掉`layout at 0x888`，报错也随之消失。目前DemoParent为：

```solidity
//SPDX-License-Identifier:MIT
pragma solidity 0.8.29;

contract DemoParent {
    uint256 public varB;
}
```

<br>

我想接着测试，看看Demo在继承DemoParent时，Demo的自定义存储布局会不会对父合约起作用。据此，我在Demo合约中编写了个获取`varB`变量slot位置的函数：

```solidity
//SPDX-License-Identifier:MIT
pragma solidity 0.8.29;

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

contract Demo layout at 0x666 is DemoParent{
    uint256 public varA;

    function getVarASlot() public pure returns(uint256 slot) {
        assembly {
            slot := varA.slot
        }
    }

    function getVarBSolt() public pure returns(uint256 slot) {
        assembly {
            slot := varB.slot
        }
    }
}
```

<br>


编译、部署、执行`getVarBSolt()`函数，从结果来看是会对父合约起作用的：

![image-20250313220647060](https://hackmd.io/_uploads/Bk9Hedx3kx.png)


<br>

## 0x03：个人想法

官方表明，支持这一语言特性是为了辅助即将到来的**Pectra**升级中的**EIP-7702**提案。不过我第一眼想到的是EIP-7201规范，该规范用于可升级代理模式中避免升级后的存储冲突。

![image-20250313222606360](https://hackmd.io/_uploads/Hkc8xOe31g.png)

<br>

新的特点刚好可以简化代码的编写。不过目前的自定义存储布局，就像上面测试那样，父合约无法与子合约分开定义，估计还是难以满足需求。

至于下一步，官方说会实现使用常数(constants)来定义存储布局，我大概理解成是可以在构造函数执行时，通过传入参数来进行定义。

值得期待……