區塊鏈全端工程師第7週課程: 智能合約更新
===
Outline
---
* Fallback及delegatecall說明
* 基本的Proxy運行機制
* ERC721的Proxy
* Openzeppelin Proxy合約說明
* ERC1967
* Transparent Proxy
* UUPS Proxy
Fallback及delegatecall
---
* Fallback:
* 說明:
* 是一個特殊的函數,它沒有名字、不接受任何參數、也不回傳任何值。合約接收直接發送的以太幣(沒有調用任何函數)或調用的函數不存在時被觸發。
* 使用場景:
* 接收以太幣
* 代理合約
* 範例:
```solidity=
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract FallbackExample {
event Log(uint gas);
fallback() external payable {
emit Log(gasleft());
}
function getBalance() public view returns (uint) {
return address(this).balance;
}
}
```
* delegatecall:
* 說明:
* 要用另外一個外部合約時,用當前合約的狀態與該外部合約互動。
* 使用場景:
* 代理合約
* 範例:
* 直接見後方Proxy範例。
基本的Proxy運行機制
---
* 概念圖:

* 範例:
Proxy.sol
```solidity=
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;
contract Proxy{
uint256 public num;
address public logic;
constructor (address _logic){
logic = _logic;
}
function setLogic (address _new_logic) external {
logic = _new_logic;
}
fallback() external payable{
(bool success, bytes memory data) = logic.delegatecall(msg.data);
require(success, "delegatecall error! ");
}
}
```
Logic.sol
```solidity=
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;
contract Logic{
uint256 public num;
function setNum(uint256 _num) external {
num = _num;
}
function getNum() external view returns(uint256) {
return num;
}
}
```
Setup.sol
```solidity=
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;
import "./Proxy.sol";
import "./Logic.sol";
contract Setup{
Proxy public proxy;
Logic public logic;
constructor (){
logic = new Logic();
proxy = new Proxy(address(logic));
}
}
```
ERC721的Proxy
---
Proxy.sol
```solidity=
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;
contract Proxy{
// Token name
string private _name;
// Token symbol
string private _symbol;
mapping(uint256 tokenId => address) private _owners;
mapping(address owner => uint256) private _balances;
mapping(uint256 tokenId => address) private _tokenApprovals;
mapping(address owner => mapping(address operator => bool)) private _operatorApprovals;
address public logic;
constructor (address _logic){
logic = _logic;
}
function setLogic (address _new_logic) external {
logic = _new_logic;
}
fallback() external payable{
(bool success, bytes memory data) = logic.delegatecall(msg.data);
require(success, "delegatecall error! ");
}
}
```
ERC721Logic.sol
```solidity=
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.20;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
contract ERC721Logic is ERC721{
constructor (string memory _name, string memory _symbol) ERC721 (_name, _symbol){
}
function mint (uint256 tokenId) external {
_mint(msg.sender, tokenId);
}
}
```
SetupERC721Proxy.sol
```solidity=
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;
import "./Proxy.sol";
import "./ERC721Logic.sol";
contract SetupERC721Proxy{
Proxy public proxy;
ERC721Logic public logic;
constructor (){
logic = new ERC721Logic("PAUL","P");
proxy = new Proxy(address(logic));
}
}
```
* 上述有個問題, delegatecall無法取得回傳值!
Openzeppelin Proxy合約說明
---
* Proxy.sol
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/Proxy.sol
* 範例:
NewProxy.sol
```solidity=
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;
import "@openzeppelin/contracts/proxy/Proxy.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract NewProxy is Proxy, Ownable{
uint256 public num;
address public logic;
constructor (address _logic) Ownable (msg.sender){
logic = _logic;
}
function setImplementation(address _new_address) external onlyOwner {
logic = _new_address;
}
function _implementation() internal view override returns (address){
return logic;
}
}
```
Logic.sol
```solidity=
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;
contract Logic{
uint256 public num;
function setNum(uint256 _num) external {
num = _num+10;
}
function getNum() external view returns(uint256) {
return num;
}
}
```
SetupNewProxy.sol
```solidity=
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;
import "./NewProxy.sol";
import "./Logic.sol";
contract Setup{
NewProxy public proxy;
Logic public logic;
constructor (){
logic = new Logic();
proxy = new NewProxy(address(logic));
}
}
```
* Slot 衝突發生!!!
- owner地址被更改了!
ERC1967
---
此標準出來就是來解決Slot衝突問題的!
https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/proxy/ERC1967
bytes32 to uint256 工具:
https://neptunemutual.com/web3-tools/bytes32-to-number-converter/
ERC1967ProxyDemo.sol
```solidity=
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.20;
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
contract ERC1967ProxyDemo is ERC1967Proxy{
constructor (address _logic, bytes memory _data) ERC1967Proxy(_logic, _data){
}
}
```
Logic.sol
```solidity=
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;
contract Logic{
uint256 public num;
function setNum(uint256 _num) external {
num = _num+10;
}
function getNum() external view returns(uint256) {
return num;
}
}
```
Transparent Proxy
---
* Openzeppelin合約:
https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/proxy/transparent
* Proxy合約管理者為```ProxyAdmin```合約,當ProxyAdmin合約的管理者,透過ProxyAdmin合約間接呼叫到Proxy合約時,會去檢查是否為```更新```操作,如我是,則更新。
* 如果呼叫者為一般用戶,則直接丟給邏輯合約。
* 範例:
TransparentProxyDemo.sol
```solidity=
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.20;
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
contract TransparentProxyDemo is TransparentUpgradeableProxy{
constructor (address _logic, address _owner, bytes memory _data) TransparentUpgradeableProxy(_logic, _owner, _data){
}
function admin() external returns(address){
return _proxyAdmin();
}
}
```
ERC721Logic.sol
```solidity=
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
contract ERC721Logic is ERC721{
uint256 public num;
uint256 count = 0;
constructor (string memory _name, string memory _symbol ) ERC721 (_name, _symbol){
}
function setNum(uint256 _num) external {
num = _num+10;
}
function getNum() external view returns(uint256) {
return num;
}
function burn(uint256 _tokenId) external {
_burn (_tokenId);
}
function mint (uint256 amount) external {
for (uint256 i = 0 ; i < amount; i++){
_mint(msg.sender, count);
count ++ ;
}
}
}
```
SetuoTransparentProxy.sol
```solidity=
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;
import "./TransparentProxyDemo.sol";
import "./ERC721Logic.sol";
contract SetupTransparentProxy{
TransparentProxyDemo public proxy;
ERC721Logic public logic;
constructor (bytes memory _data){
logic = new ERC721Logic("Test", "T");
proxy = new TransparentProxyDemo(address(logic), msg.sender, _data);
}
}
```
* Admin從ProxyAdmin更新合約!
* 一般用戶直接互動,都會走到邏輯合約。
UUPS Proxy
---
* UUPS Proxy全名叫做:Universal Upgradeable Proxy Standard。
* 和Transparent Proxy的運作差異在於,合約更新的操作Transparent Proxy運作機制在Proxy 這層,而UUPS Proxy的機制在Logic這層。
* Openzeppelin的UUPS:
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/UUPSUpgradeable.sol
* 範例:
NewERC1967Proxy.sol
```solidity=
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.20;
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
contract NewERC1967Proxy is ERC1967Proxy{
constructor (address _logic, bytes memory _data) ERC1967Proxy(_logic, _data){
}
}
```
UUPSLogic.sol
```solidity=
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
contract UUPSLogic is Initializable, UUPSUpgradeable, ERC721Upgradeable, OwnableUpgradeable{
function initialize(string memory _name, string memory _symbol) external initializer{
__UUPSUpgradeable_init();
__ERC721_init(_name, _symbol);
__Ownable_init(tx.origin);
}
function mint (uint256 _tokenId) external {
_mint(msg.sender, _tokenId);
}
function _authorizeUpgrade(address newImplementation) internal override{
}
}
```
SetupUUPS.sol
```solidity=
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;
import "./NewERC1967Proxy.sol";
import "./UUPSLogic.sol";
contract SetupTransparentProxy{
NewERC1967Proxy public proxy;
UUPSLogic public logic;
constructor (){
logic = new UUPSLogic();
proxy = new NewERC1967Proxy(address(logic), abi.encodeWithSelector(logic.initialize.selector, "Paul","P"));
}
}
```