區塊鏈全端工程師第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運行機制 --- * 概念圖: ![未命名绘图-第 12 页.drawio](https://hackmd.io/_uploads/rJ5Y7MJ26.png) * 範例: 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")); } } ```