---
# System prepended metadata

title: 撰寫可升級合約

---

# 撰寫可升級合約

**注意事項**

1. 不寫 `constructor` 寫 init function，並確保此 function 只能被執行一次
```solidity=
contract MyContract {
    uint256 public x;
    bool private initialized;

    function initialize(uint256 _x) public {
        require(!initialized, "Contract instance has already been initialized");
        initialized = true;
        x = _x;
    }
}
```

**補充**
> 在 Solidity 中，構造函數內部的代碼或全局變量聲明的一部分不是已部署合約的運行時字節碼的一部分。此代碼僅在部署合約實例時執行一次。因此，邏輯合約構造函數中的代碼將永遠不會在代理狀態的上下文中執行。換句話說，代理完全不知道構造函數的存在。就好像他們沒有代理一樣。


2. 所有調用的父合約都不能有 constructor
```solidity=
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

contract BaseContract is Initializable {
    uint256 public y;

    function initialize() public initializer {
        y = 42;
    }
}

contract MyContract is BaseContract {
    uint256 public x;

    function initialize(uint256 _x) public initializer {
        BaseContract.initialize(); // Do not forget this call!
        x = _x;
    }
}
```

2. 使用可升級的智能合約庫
``@openzeppelin/contracts-upgradeable`
```solidity=
// @openzeppelin/contracts-upgradeable/contracts/token/ERC20/ERC20Upgradeable.sol
pragma solidity ^0.6.0;
  ...
contract ERC20Upgradeable is Initializable, ContextUpgradeable, IERC20Upgradeable {
  ...
    string private _name;
    string private _symbol;
    uint8 private _decimals;

    function __ERC20_init(string memory name, string memory symbol) internal initializer {
        __Context_init_unchained();
        __ERC20_init_unchained(name, symbol);
    }

    function __ERC20_init_unchained(string memory name, string memory symbol) internal initializer {
        _name = name;
        _symbol = symbol;
        _decimals = 18;
    }
  ...
}
```

4. 避免直接設定初始值

```solidity=
contract xxx {
    string test = 13; // X
    
    function initialize() public initializer {
        test = 42; // O
    }
}
```
5. 避免建立實例並賦值，可以使用傳參的方式

```solidity=
contract wrong is Initializabl{
    ERC20 public token;

    function initialize() public initializer {
        token = new ERC20("Test", "TST"); // X
    }
}

contract right is Initializabl {
    IERC20Upgradeable public token;

    function initialize(IERC20Upgradeable _token) public initializer {
        token = _token;
    }
}
```

6. 避免在合約中使用 `selfdestruct`, `delegatecall`
7. 調整注意事項
- 不能改變變量的類型
- 不能變更宣告順序
- 不能在現有變量之前引入一個新變量
- 不能刪除現有變量
- 如需要引入一個新變量，擺在最後


## 以代理模式升級合約
可升級合約就是利用代理合約去實現升級的效果
![](https://i.imgur.com/YWYISKh.png)


![](https://hackmd.io/_uploads/BkSaZcAhY.png)

### 按照影片教學實踐可更新合約
https://www.youtube.com/watch?v=bdXJmWajZRY
部屬合約到 reinkeby
1. **Box** 0x784b54b1FD682A43f7aA4013Be66e3713B37406b
2. **ProxyAdmin** 0xF95b8469593D1C79811e1B4F07d33bEB2d21F3C3
3. **TransparentUpgradeableProxy** 0xC97dC95955957C66E88E275F5A9AD887A2E07E30
5. **BoxV2** 0x6E5A21931Ae722F978186E5A1829B1aB37B7a203
6. **GnosisSafeProxy** 0xdf3b6E8b4a51d696479d636872CfFbA39cC41094

影片中是直接使用 upgrade.js/
實務上需要用 https://gnosis-safe.io/ 來更新，因為更新不是一個人說了算

contract address 填寫 TransparentUpgradeableProxy address
New implementation address 填寫 BoxV2 的地址
![](https://i.imgur.com/TqKpikx.png)
![](https://i.imgur.com/qMrqEHH.png)

### 詳細步驟
https://forum.openzeppelin.com/t/openzeppelin-upgrades-step-by-step-tutorial-for-hardhat/3580




### Reference
https://www.youtube.com/watch?v=bdXJmWajZRY
https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable
https://blog.openzeppelin.com/proxy-patterns/
https://fengbro.medium.com/%E5%BB%BA%E7%AB%8B%E5%8F%AF%E5%8D%87%E7%B4%9A%E7%9A%84%E4%BB%A3%E7%90%86%E5%90%88%E7%B4%84-%E4%BB%A5erc20%E7%82%BA%E4%BE%8B-upgrable-contract-proxy-contract-88769cf92be7
https://cloud.tencent.com/developer/article/1922772