跳转到主要内容
mip.watch
⌘K

MIP-4

Reserve Balance Introspection

最终 标准轨道 核心 已打包至 MONAD_NINE GitHub ↗ 论坛 ↗
构想 草案 审核中 最终审核 最终 持续更新

Add reserve balance precompile to query reserve balance violation state during transaction execution

作者
Category Labs
创建时间
2026-01-08
更新时间
2026年7月14日

利益相关方影响

此 MIP 为各受众群体带来的变化。严重度和操作标记由模型推导得出,并按 MIP 类型/分类设定确定性下限值。

要点速览

此提案增加了一个新的预编译合约,允许合约在执行过程中查询是否违反了储备余额规则,以便在交易完成前进行调整。

变更内容

  • 新增预编译合约:在地址`0x1001`添加`dippedIntoReserve`方法,用于查询当前是否违反储备余额。
  • 中途检查:合约可以在执行过程中检测储备违规状态,及时调整行为。
  • 调用限制:该方法只能通过`CALL`调用,其他调用方式会导致回退。
  • 返回值编码:返回值采用标准Solidity ABI编码,便于合约调用。
  • 向后兼容性:此提案不会影响现有合约,未使用该预编译的合约不受影响。

开发者

智能合约与 dapp 构建者、RPC 使用者、工具开发者

新增的预编译合约允许智能合约在执行过程中查询储备余额违规状态,开发者需要更新合约以使用这个新功能。

用户

钱包用户、EOA 持有者、dapp 访问者

这个更新引入了一个新的预编译接口,可以在交易执行过程中查询储备余额违规状态,但对普通用户没有直接影响。

验证者

节点运营者、RPC 运营者、委托者

这个MIP引入了一个新的预编译,可以在交易执行期间查询储备余额违规状态,但对验证者的收入和盈利能力没有直接影响。

基金会

Monad 基金会与 Category Labs 核心开发者

这个提案需要协调多个参与方,以确保新预编译的实现与现有的Monad机制兼容,并且需要更新文档以反映这一变化。

需要采取行动

2026年8月7日 19:00 生成 · 模型:gpt-4o-mini

规范

机器翻译
引用

为方便阅读的 AI 翻译,可能不准确或已过时。在治理、投票和争议中,英文原文是唯一权威文本。准确表述请参阅 GitHub 原文。

GitHub 英文原文 ↗
## 摘要

在地址 `0x1001` 添加一个新的预编译方法 `dippedIntoReserve`,该方法返回当前执行状态是否违反了储备余额。这使得合约能够在交易完成之前检测并恢复临时的储备违规。

## 动机

Monad 的储备余额机制(根据初始规范,算法 3)会撤销在执行结束时使任何触及账户低于其储备阈值的交易。然而,检查是在执行后进行的:合约在执行过程中无法知道它们是否处于违规状态。

储备余额预编译允许合约在执行过程中查询违规状态并相应调整行为——可以通过恢复余额、采取替代代码路径或提前以有意义的错误撤销。

## 规范

| 名称                           | 值          |
| ------------------------------ | ------------ |
| `GAS_DIPPED_INTO_RESERVE`      | `100`        |
| `SELECTOR_DIPPED_INTO_RESERVE` | `0x3a61584e` |

新的预编译地址为 `0x1001`,并满足以下 Solidity 接口:

```solidity
interface IReserveBalance {
    function dippedIntoReserve() external returns (bool);
}
```

`dippedIntoReserve` 的 Solidity 选择器为 `SELECTOR_DIPPED_INTO_RESERVE (0x3a61584e)`。

预编译必须通过 `CALL` 调用。
通过 `STATICCALL`、`DELEGATECALL` 或 `CALLCODE` 的调用必须撤销。

通过 [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702) 委托针对预编译地址的调用必须撤销。

调用数据必须恰好由 4 字节的 `SELECTOR_DIPPED_INTO_RESERVE` 组成。
任何其他调用数据都是无效的:如果调用数据短于 4 字节或不等于 `SELECTOR_DIPPED_INTO_RESERVE`,预编译必须撤销并返回错误信息 "method not supported"。
如果选择器后附加了额外的调用数据,预编译必须撤销并返回错误信息 "input is invalid"。

方法 `dippedIntoReserve()` 不是可支付的,并且在以非零值调用时必须撤销并返回错误信息 "value is nonzero"。

如果存在多个撤销条件,撤销错误信息必须与以下顺序评估的条件相对应:

1. 不是通过 `CALL` 调用
2. `gas < GAS_DIPPED_INTO_RESERVE`
3. `len(calldata) < 4`
4. `calldata[:4] != SELECTOR_DIPPED_INTO_RESERVE`
5. `value > 0`
6. `len(calldata) > 4`

撤销会消耗提供给调用帧的所有 gas。

成功时,方法 `dippedIntoReserve()` 评估 `DippedIntoReserve`(初始规范的算法 3)将返回的条件,替换当前状态为后执行状态。
检查考虑交易中触及的所有账户,无论调用深度如何。
该调用消耗 `GAS_DIPPED_INTO_RESERVE` gas。

返回值以 ABI 编码为 Solidity `bool`——即,返回数据中的 32 字节字。

## 理由

### 地址

预编译地址选择遵循现有的质押预编译 `0x1000`。

### 燃气成本

`dippedIntoReserve()` 的燃气成本相当于一次 `tload` 的成本。 
实现应逐步更新其状态以跟踪违反储备余额约束的情况,而不是在每次调用预编译时遍历当前交易中修改的账户集合。
因此,`dippedIntoReserve()` 应该是对这种违规状态的检查,因此其资源使用应与从瞬态存储加载相似。

这没有进行基准测试,但与质押预编译的燃气成本的基准测试和成本计算一致,这些成本是根据数据库加载和存储、事件和转账的数量计算得出的。
储备余额检查不会产生事件或转账,类似于从瞬态存储加载,而不是数据库存储,因此使用 `tload` 的成本。

消耗所有燃气的回退与以太坊预编译的行为一致,后者在失败时不会返回剩余燃气,而与 Solidity 函数在回退时返回未使用的燃气相对。

### 返回值编码

返回值的编码与标准 Solidity ABI 编码一致。
这意味着调用者可以通过正常的合约调用来调用预编译。

### 预编译与操作码

之前的设计提议添加一个具有类似语义的新操作码。
由于此 introspection 特性旨在供智能合约开发者直接使用(例如,在捆绑器入口合约中),因此选择了预编译,因为它可以立即调用,而无需更新编译器或工具链。

### 与其他 Monad 预编译的兼容性

上述语义(严格的 calldata 验证、ABI 编码的返回值、拒绝带值的调用、关于 `*CALL` 操作码和 EIP-7702 的约定、回退消息以及消耗所有燃气的回退)是为了与现有的 Monad 质押预编译保持明确的一致性。

接口方法 `dippedIntoReserve()` 故意未声明为 `view`,以便 Solidity 调用站点编译为 `CALL` 而不是 `STATICCALL`。

## 向后兼容性

该提案添加了一个新的预编译,并未修改现有行为。
不使用预编译的合约不受影响。

## 安全考虑

该预编译是只读的,并暴露已经隐式可用的信息(如果违规持续,交易将回退)。
没有引入新的攻击面。

## 参考

- [Monad 初始规范](https://category-labs.github.io/category-research/monad-initial-spec-proposal.pdf)

## 版权

版权及相关权利通过 [CC0](../LICENSE.md) 放弃。

论坛讨论

8 个帖子 · 11 个点赞 · 6个月前
在论坛上阅读更多 ↗
  1. @Bruce Collie #1 2026年1月8日 12:53

    mips.monad.xyz Monad Improvement Proposals (MIPs) Add reserve balance precompile to query reserve balance violation state during transaction execution This MIP adds a new opcode that allows contracts on Monad to detect whether their local execution state is in violation of reserve balance and take action to avoid reverting.

  2. @Pdobacz #4 2026年1月15日 13:10

    Re gas cost Expected to be `O(N)` in the number of warm accounts (i.e., accounts with modified balances). Would it be feasible to: - Keep a cache of reserve balance check results per warm account address (with a count of failed checks for quick lookup) - Run the reserve balance check only on crossing boundaries where it actually might change. I think that is at transaction start (after auth processing) CALL with value!=0 and CREATE and SELFDESTRUCT and call frame reverting. - Run the check and update cache only for affected addresses (1 or 2 max per such op) - Have CHECKRESERVEBALANCE just return the cached result Then it would be possible to price CHECKRESERVEBALANCE at O(1) (and as cheap as an environment opcode)? The cost of checking and updating would be assumed to be included in the (expensive) ops from (2.). Cost of memory to hold the extra execution state included in co...

  3. @Bruce Collie #5 2026年1月19日 11:02

    I think that should be feasible to implement. We’ll try that method once we have our tests nailed down using the eager method as a reference.

  4. @ARitz Cracker #6 2026年1月23日 23:17

    Could the MIP link to the algorithm referenced? We will need to update our revm-based codebase to include this opcode.

  5. @Bruce Collie #7 2026年1月26日 15:13

    Yes, we’ll update the MIP to point to the algorithm in the current version of the Monad client - roughly speaking, the opcode will represent a call to dipped_into_reserve here .