代理合约
- 代理合约底层采用 delegateCall,分离逻辑和数据。
- 由于逻辑和数据分离,后续可以灵活替换逻辑业务
- 代理合约按照逻辑合约代码去更新/读取
EVM
环境中的slot
的状态变量- 因此替换逻辑合约时,应该保持
slot
的状态变量的顺序 - 新增参数只能在末端添加
- 因此替换逻辑合约时,应该保持
代理合约逻辑
- 用户直接对接代理合约,代理合约存储合约数据
- 代理合约
delegateCall
调用逻辑合约,只能更改代理合约内部的状态变量
- 代理合约
- 逻辑合约仅仅负责代理合约数据处理的逻辑
- 逻辑合约的数据不受代理合约的影响
- 因此,需要独立初始化代理合约和逻辑合约的内部参数
代理合约插槽存储
This is the keccak-256 hash of “eip1967.proxy.implementation” subtracted by 1.
bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
This is the keccak-256 hash of “eip1967.proxy.admin” subtracted by 1.
bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
最简代理合约(不可升级clone)
initcode
,拷贝calldata
数据- 指定逻辑合约地址
- 执行
delegateCall
,返回执行结果或者失败revert
examples
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;
import "@openzeppelin/contracts/utils/Create2.sol";
contract Factory {
error AccountCreationFailed();
error InvalidSignerInput();
error InvalidOwnerInput();
error DuplicatedAddress();
event AccountCreated(address indexed _account);
address[] public userwallet;
address public implementation;
address public manager;
address public owner;
mapping(string => address) identiWallet;
function getWalletLength() public view returns (uint256) {
return userwallet.length;
}
constructor(address _manager, address _implement) payable {
owner = msg.sender;
manager = _manager;
implementation = _implement;
}
modifier onlyOwner() {
require(msg.sender == owner, "NOT_OWNER");
_;
}
modifier onlyManager() {
require(msg.sender == manager, "NOT_OWNER");
_;
}
function updateOwner(address newOwner) external onlyOwner {
require(
newOwner != address(0) && newOwner != owner,
"DUP/INVALID_NEWOWNER"
);
owner = newOwner;
}
function updateManager(address newManager) external onlyOwner {
require(
newManager != address(0) && newManager != manager,
"DUP/INVALID_NEWMANAGER"
);
manager = newManager;
}
function updateImpl(address newImpl) external onlyOwner {
require(
newImpl != address(0) && newImpl != implementation,
"DUP/INVALID_NEWIMPL"
);
implementation = newImpl;
}
function getCreationCode(bytes32 _identifier)
internal
view
returns (bytes memory)
{
return
abi.encodePacked(
hex"3d60ad80600a3d3981f3363d3d373d3d3d363d73",
implementation,
hex"5af43d82803e903d91602b57fd5bf3",
abi.encode(_identifier)
);
}
function createAccount(
address _owner,
address _signer,
string memory _identifier
) external onlyManager returns (address _account) {
(bytes memory code, bytes memory salt) = checkAccount(
_owner,
_signer,
_identifier
);
assembly {
_account := create2(0, add(code, 0x20), mload(code), salt)
}
if (_account == address(0)) revert AccountCreationFailed();
emit AccountCreated(_account);
(bool success, bytes memory result) = _account.call(
abi.encodeWithSignature(
"initData(address,address,address)",
_owner,
manager,
_signer
)
);
if (!success) {
assembly {
revert(add(result, 32), mload(result))
}
}
identiWallet[_identifier] = _account;
userwallet.push(_account);
}
function checkAccount(
address _owner,
address _signer,
string memory _identifier
) internal view returns (bytes memory code, bytes memory salt) {
if (_owner == address(0)) revert InvalidOwnerInput();
if (_signer == address(0)) revert InvalidSignerInput();
require(
identiWallet[_identifier] == address(0),
"ONLY_ALLOW_ONE_WALLET"
);
code = getCreationCode(convertStringToByte32(_identifier));
salt = abi.encode(_identifier, _owner, _signer);
address _account = Create2.computeAddress(
bytes32(salt),
keccak256(code)
);
if (_account.code.length != 0) revert DuplicatedAddress();
}
function account(
address _owner,
address _signer,
string memory _identifier
) external view returns (address _account) {
bytes memory code = getCreationCode(convertStringToByte32(_identifier));
bytes memory salt = abi.encode(_identifier, _owner, _signer);
_account = Create2.computeAddress(bytes32(salt), keccak256(code));
return _account;
}
function convertStringToByte32(string memory _texte)
internal
pure
returns (bytes32 result)
{
assembly {
result := mload(add(_texte, 32))
}
}
}
简单可升级代理合约实现
逻辑合约地址
- eip1967 允许函数更新
IMPLEMENTATION_SLOT
数据 (严格的权限控制),
initialize
- 一般情况下,逻辑合约定义
initialize()
函数,按照插槽顺序初始化代理合约数据- 但是需要注意:
initialize()
应该只能调用一次,或者拥有严格的权限控制 - 代理合约的初始化不会影响逻辑合约的状态变量
- 逻辑合约必须单独执行数据的初始化,并确保逻辑合约中的初始化只能调用一次,或者拥有严格的权限控制
- 但是需要注意:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "erc721a/contracts/extensions/ERC721ABurnable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract LogisNFT is Ownable, ERC721ABurnable {
bool public mintable;
uint256 public maxSupply;
uint256 private initilaized;
address private launchcaller;
// Current base URI.
string public currentBaseURI;
// The suffix for the token URL, e.g. ".json".
string private tokenURISuffix;
string private nftname;
string private nftsymbol;
error NotMintable();
constructor() payable ERC721A(nftname, nftsymbol) Ownable(_msgSender()) {
initilaized = 1;
}
function initailize(
string memory _name,
string memory _symbol,
string memory _tokenURISuffix,
string memory _currentBaseURI,
uint256 _maxSupply,
address _caller
) public virtual {
require((initilaized == 0), "ALREADY_INITIALIZED");
nftname = _name;
nftsymbol = _symbol;
tokenURISuffix = _tokenURISuffix;
currentBaseURI = _currentBaseURI;
maxSupply = _maxSupply;
launchcaller = _caller;
initilaized = 1;
}
/**
* @dev Returns the token collection name.
*/
function name()
public
view
virtual
override(ERC721A, IERC721A)
returns (string memory)
{
return nftname;
}
/**
* @dev Returns the token collection symbol.
*/
function symbol()
public
view
virtual
override(ERC721A, IERC721A)
returns (string memory)
{
return nftsymbol;
}
/**
* @dev Returns the address of the current owner.
*/
function caller() public view virtual returns (address) {
return launchcaller;
}
/**
* @dev Sets mintable.
*/
function setMintable(bool _mintable) external onlyOwner {
mintable = _mintable;
}
}
contract ProxyBase is ERC1967Proxy, Ownable {
constructor(
address implementation,
bytes memory _data,
address _owner
) ERC1967Proxy(implementation, _data) Ownable(_owner) {}
function implement() external view returns (address) {
return _implementation();
}
function upgradeImpl(address implementation, bytes memory _data)
public
virtual
onlyOwner
{
ERC1967Utils.upgradeToAndCall(implementation, _data);
}
function renounceOwnership() public view override onlyOwner {
revert("CLOSED_INTERFACE");
}
receive() external payable {}
}
contract Factory is Ownable {
address[] public proxys;
event ProxyCreated(address indexed _account);
constructor() Ownable(_msgSender()) {}
function newproxy(
address implementation,
string memory _name,
string memory _symbol,
string memory _tokenURISuffix,
string memory _currentBaseURI,
uint256 _maxSupply,
address _caller,
address _owner
) external onlyOwner {
bytes memory _data = abi.encodeWithSignature(
"initailize(string,string,string,string,uint256,address)",
_name,
_symbol,
_tokenURISuffix,
_currentBaseURI,
_maxSupply,
_caller
);
ProxyBase proxy = new ProxyBase(implementation, _data, _owner);
proxys.push(address(proxy));
emit ProxyCreated(address(proxy));
}
function renounceOwnership() public view override onlyOwner {
revert("CLOSED_INTERFACE");
}
}
合约升级
- 代理合约直接
delegateCall
逻辑合约 - 按照逻辑合约的代码更新 代理合约
EVM slot
数据
因此,合于升级后不能影响旧的状态变量相对顺序
- 合约升级只能添加新的函数、事件、
error
、结构体、immutable|constant
变量 - 合约升级允许更新函数逻辑
- 合约升级可以在最后添加新的状态变量,确保旧状态变量的顺序
- 合约升级不能更改状态变量顺序、不能继承新的合约(会改变状态变量顺序)
beacon代理合约
- 多个代理合约使用同一个逻辑合约,并且通过单笔交易可以升级多个代理合约的逻辑合约地址
- 适用于一个逻辑产生多个衍生合约的场景(班级学生采用同一个逻辑管理,但是每人拥有各自的状态)
beacon合约
beacon
合约作为灯塔,提供当前implementation
地址- 全部代理合约去
beacon
合约读取逻辑合约地址,发送delegateCall
交易
- 更换新的逻辑合约后,只需要更新
beacon
合约的IMPLEMENTATION_SLOT
数据,就可以实现all proxy
的逻辑地址更新
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "erc721a/contracts/extensions/ERC721ABurnable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol";
import "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract LogisNFT is Ownable, ERC721ABurnable {
bool public mintable;
uint256 public maxSupply;
uint256 private initilaized;
address private launchcaller;
// Current base URI.
string public currentBaseURI;
// The suffix for the token URL, e.g. ".json".
string private tokenURISuffix;
string private nftname;
string private nftsymbol;
error NotMintable();
constructor() payable ERC721A(nftname, nftsymbol) Ownable(_msgSender()) {
initilaized = 1;
}
function initailize(
string memory _name,
string memory _symbol,
string memory _tokenURISuffix,
string memory _currentBaseURI,
uint256 _maxSupply,
address _caller
) public virtual {
require((initilaized == 0), "ALREADY_INITIALIZED");
nftname = _name;
nftsymbol = _symbol;
tokenURISuffix = _tokenURISuffix;
currentBaseURI = _currentBaseURI;
maxSupply = _maxSupply;
launchcaller = _caller;
initilaized = 1;
}
/**
* @dev Returns the token collection name.
*/
function name()
public
view
virtual
override(ERC721A, IERC721A)
returns (string memory)
{
return nftname;
}
/**
* @dev Returns the token collection symbol.
*/
function symbol()
public
view
virtual
override(ERC721A, IERC721A)
returns (string memory)
{
return nftsymbol;
}
/**
* @dev Returns the address of the current owner.
*/
function caller() public view virtual returns (address) {
return launchcaller;
}
/**
* @dev Sets mintable.
*/
function setMintable(bool _mintable) external onlyOwner {
mintable = _mintable;
}
}
// 管理Implementation
contract Beacon is UpgradeableBeacon {
constructor(address implementation_)
UpgradeableBeacon(implementation_, _msgSender())
{}
}
// 管理beacon,用户直接交互的合约
// address private immutable _beacon;
// beacon地址不允许更新,值直接写到合约codes,读取的一直是合约codes中初始化的那个值
// Factory产生的子合约
contract ProxyBase is BeaconProxy, Ownable {
constructor(
address beacon,
bytes memory data, // delegateCall implementation()'s address
address _owner
) BeaconProxy(beacon, data) Ownable(_owner) {}
function implement() external view returns (address) {
return _implementation();
}
function renounceOwnership() public view override onlyOwner {
revert("CLOSED_INTERFACE");
}
function getBeacon() external view returns (address) {
return _getBeacon();
}
receive() external payable {}
}
contract Factory is Ownable {
address[] public proxys;
event ProxyCreated(address indexed _account);
constructor() Ownable(_msgSender()) {}
function newproxy(
address beacon,
string memory _name,
string memory _symbol,
string memory _tokenURISuffix,
string memory _currentBaseURI,
uint256 _maxSupply,
address _caller,
address _owner
) external onlyOwner {
bytes memory _data = abi.encodeWithSignature(
"initailize(string,string,string,string,uint256,address)",
_name,
_symbol,
_tokenURISuffix,
_currentBaseURI,
_maxSupply,
_caller
);
ProxyBase proxy = new ProxyBase(beacon, _data, _owner);
proxys.push(address(proxy));
emit ProxyCreated(address(proxy));
}
function renounceOwnership() public view override onlyOwner {
revert("CLOSED_INTERFACE");
}
}