﻿---
title: "代理儲存衝突：升級如何破壞合約狀態"
description: "代理保留狀態，而實作程式碼可以變更。了解不相容的儲存布局如何覆寫餘額、擁有者和升級控制，以及如何安全驗證升級。"
image: "https://wiki.fcontext.com/og.png"
---

> Documentation Index
> Fetch the complete documentation index at: https://wiki.fcontext.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 代理儲存衝突：升級如何破壞合約狀態

> 僅供教育參考，不構成投資建議；投資可能產生損失。

<a id="answer"></a>

## 直接答案

當實作程式碼以不同於現有狀態建立時布局所賦予的含義，讀取或寫入代理的儲存槽，就會發生代理儲存衝突。升級可能因此把餘額當作位址、清空擁有者、破壞映射的根槽，或覆寫升級控制資料。

使用 `delegatecall` 時，實作位元組碼在代理的上下文中執行：儲存、餘額和 `address(this)` 都屬於代理。變數名稱不會儲存在鏈上，因此 EVM 只按新程式碼計算出的槽位和位元組偏移操作。

因此，升級必須保持已部署布局相容，而不只是成功編譯或公開相同函式。授權升級之前，應將編譯器產生的布局與確切的已部署版本比較。

<a id="mechanism"></a>

## 運作原理

Solidity 通常在繼承關係經過 C3 線性化後，按宣告順序從槽 `0` 起放置狀態變數。小於 32 位元組的值可能共用一個槽；結構和陣列還有額外的放置規則，而映射和動態陣列會根據基礎槽推導資料位置。基礎槽一旦移動，其衍生資料的位置也會改變。

需要稽核以下四類不同的衝突邊界：

- **代理與實作之間：** 實作位址、管理員等代理自有欄位不得占用應用狀態所用的槽。ERC-1967 為實作、信標和管理員資料規定了編譯器常規分配不會觸及的標準槽。
- **舊實作與新實作之間：** 現有應用變數必須保持相容的槽、偏移和型別。在末尾追加變數可能安全，但插入、重排、刪除或改變變數型別會重新解釋已有儲存字。
- **繼承關係：** 向基底合約新增狀態或改變繼承順序，可能移動衍生合約宣告的儲存，即使衍生合約原始碼沒有變化。
- **預留或命名空間儲存：** 正確使用儲存間隙可以為基底合約預留空間，ERC-7201 風格的命名空間可以隔離布局。但兩者都不允許任意修改既有布局內部。

<a id="example"></a>

## 範例

假設版本 1 的布局如下：

```solidity
uint256 totalAssets; // slot 0
address owner;       // slot 1
```

版本 2 錯誤地在開頭插入一個變數：

```solidity
bool paused;         // slot 0, offset 0
uint256 totalAssets; // slot 1
address owner;       // slot 2
```

升級後，`paused` 會讀取舊 `totalAssets` 的低位位元組，新 `totalAssets` 會把舊 `owner` 儲存字當作整數讀取，而 `owner` 會讀取槽 `2` 中原有的內容，通常為零。原始儲存字仍然存在，但新程式碼賦予了它們不同含義。交易可能成功執行，卻以錯誤解釋後的狀態進行授權或記帳。

<a id="risks"></a>

## 風險與升級檢查

- 為兩個實作產生儲存布局輸出，並以實際部署的參考合約為準，比較槽、偏移、型別和繼承資訊。
- 對傳統線性布局，只在末尾追加新變數。除非已證明相容，否則不要重排現有欄位、改變型別、刪除後複用，也不要修改基底合約。
- 使用儲存間隙時，應按實際占用的預留槽數精確縮減間隙。使用命名空間儲存時，須確保命名空間識別碼唯一，並驗證每個現有命名空間內的變更。
- 不要把 ERC-1967 當作完整保護。它將代理中繼資料與編譯器分配的應用槽分開，但不能保證兩個實作布局彼此相容。
- 在分叉網路或狀態快照上測試升級及任何重新初始化函式。執行前後都要核對擁有者、角色、餘額、授權額度、映射項目、暫停狀態、實作槽以及回復或緊急控制。

如果升級已疑似造成衝突，在治理權限允許時，應暫停後續升級和狀態變更呼叫。保留升級前區塊號與實作位元組碼，比較受影響槽位的原始儲存，並由合格的合約工程師設計遷移方案、接受獨立審查。沒有經驗證的儲存映射就反覆升級，可能破壞更多原本可恢復的狀態。

<a id="misconceptions"></a>

## 常見誤解

- **「變數名稱沒變，布局就是安全的。」** 名稱不決定儲存位置，型別、順序、打包、繼承和命名空間規則才決定。
- **「刪除變數就能釋放槽位供複用。」** 代理儲存會持續存在。除非經過嚴格審查的遷移將舊值清除或轉換，否則複用槽位只是給舊儲存字賦予新含義。
- **「一筆測試交易成功就證明升級相容。」** 測試可能只觸及少量槽位。布局驗證和狀態差異測試必須涵蓋特權欄位、打包值、映射、陣列和繼承儲存。

<a id="related"></a>

## 相關主題

- [Delegatecall 儲存風險](/zh-tw/crypto/delegatecall-storage-risk/)
- [初始化器接管](/zh-tw/crypto/initializer-takeover/)
- [代理合约](/zh-tw/crypto/proxy-contract/)
- [代理升級監控](/zh-tw/crypto/proxy-upgrade-monitoring/)
- [可升級合約](/zh-tw/crypto/upgradeable-contract/)

<a id="sources"></a>

## 來源

- [智慧合約簡介](https://docs.soliditylang.org/en/latest/introduction-to-smart-contracts.html) - Solidity Documentation（存取日期：2026-08-21）
- [儲存與暫態儲存中狀態變數的布局](https://docs.soliditylang.org/en/latest/internals/layout_in_storage.html) - Solidity Documentation（存取日期：2026-08-21）
- [ERC-1967：代理儲存槽](https://eips.ethereum.org/EIPS/eip-1967) - Ethereum Improvement Proposals（存取日期：2026-08-21）
- [編寫可升級合約](https://docs.openzeppelin.com/upgrades-plugins/writing-upgradeable) - OpenZeppelin Documentation（存取日期：2026-08-21）

Source: https://wiki.fcontext.com/zh-tw/crypto/proxy-storage-collision/index.mdx
