Wyvern 代理注册协议


OpenSea 使用了 Wyvern 协议。协议内容,主要包括了代理注册部分和交换部分。本文主要展开分析 OpenSea 所使用的 代理注册部分

1. 概述

从技术层面讲,整个代理注册过程,关键在于通过 代理模式 实现 可更新的授权实现者
所谓 授权实现者,即,在 NFT 合约中,用户授权其可以转移自己的 NFT。授权实现者 被用户 approve 后,通过 delegatecall 或者 call 调用可以代替用户处理 NFT。
所谓 可更新性,即,通过重新部署新合约的形式,借助代理模式,将 授权实现者 指向最新的合约。这种方式,将大大增强合约的灵活性。

接下来,就具体合约内容做分析。
继承关系如下所示:

2. Proxy

代理过程,通过 fallback 函数实现,fallback 中,通过内联汇编,实现可以接收返回值的 delegatecall
汇编代码中,最终调用 implementation 做实现。而该变量,在 Proxy 构造时,被构造参数赋值为 AuthenticatedProxy
当然,implementation 是可以被“升级”的。即,允许重新上链一个合约作为 implementation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function () payable public {
address _impl = implementation();
require(_impl != address(0));

assembly {
let ptr := mload(0x40)
// 将参数存储到从 ptr 开始的数据槽,需要的槽位:calldatasize
calldatacopy(ptr, 0, calldatasize)
// 注意,这里的 delegatecall 并非 Solidity 中的 delegatecall
// 此处是 EVM 中的操作符
let result := delegatecall(gas, _impl, ptr, calldatasize, 0, 0)

let size := returndatasize
// 将返回值存储到从 ptr 开始的数据槽,需要的槽位:size
returndatacopy(ptr, 0, size)

// 检查调用是否成功
switch result
case 0 { revert(ptr, size) }
// 如果成功,则返回数据
default { return(ptr, size) }
}
}

3. AuthenticatedProxy

AuthenticatedProxy 是事实上的授权实现者。虽然,用户将交易权授权给了 OwnableDelegateProxy,但是,其 通过 delegatecall 最终调用了 AuthenticatedProxy 中的 proxy 方法。delegatecall 调用机制,从某种程度上就像 OwnableDelegateProxy 继承AuthenticatedProxy

1
2
3
4
5
6
7
8
9
10
11
12
13
function proxy(address dest, HowToCall howToCall, bytes calldata) public returns (bool result)
{
// user, 发起 `registerProxy` 注册的用户。
// registry, 即 `ProxyRegistry`
// 因此,调用该函数,需要调用者是注册的发起者,或者,是调用者授权的第三方
require(msg.sender == user || (!revoked && registry.contracts(msg.sender)));
if (howToCall == HowToCall.Call) {
result = dest.call(calldata);
} else if (howToCall == HowToCall.DelegateCall) {
result = dest.delegatecall(calldata);
}
return result;
}

注意,在注册中心 registry,已经对交易协议 Wyvern Exchange v2 进行了授权。因此,当调用者为 Wyvern Exchange v2 时,registry.contracts(msg.sender)) 将返回 trueWyvern Exchange v2 合约地址为 0x7f268357a8c2552623316e2562d90e642bb538e5,可以使用该地址,在 OwnableDelegateProxy 中检查授权。

4. ProxyRegistry

主要包括两个功能:

  • 管理 proxy 方法的调用授权。
  • 为调用者创建代理对象。

4.1 授权管理

需要注意的是,在授权第三方可以请求代理方法执行时,授权过程需要两周才会生效。而取消一个授权,是可以直接取消的。
另外,授权管理方法,只能由 ProxyRegistry 的创建者调用。

1
2
3
4
5
function startGrantAuthentication (address addr) public onlyOwner{
require(!contracts[addr] && pending[addr] == 0);
// 两周后,再调用 endGrantAuthentication 可以完成授权
pending[addr] = now;
}
1
2
3
4
5
// 取消授权
function revokeAuthentication (address addr) public onlyOwner
{
contracts[addr] = false;
}

在授权时,只有一种不需要 两周时延 的方法,但是只能调用一次。

1
2
3
4
5
6
function grantInitialAuthentication (address authAddress) onlyOwner public
{
require(!initialAddressSet);
initialAddressSet = true;
contracts[authAddress] = true;
}

授权的本质,是 registry.contracts(msg.sender) 布尔值的赋值。根据该值,将决定是否被允许可以进行 AuthenticatedProxyproxy 调用。proxy 函数中,将对被代理对象执行 delegatecall,通过该方法,完成 NFT 所属权转移。

4.2 创建代理

ProxyRegistry 会为用户创建 OwnableDelegateProxy 合约。OwnableDelegateProxy 合约是用户授权的对象,它有权限转移用户的 NFT。

1
2
3
4
5
6
7
8
9
function registerProxy() public returns (OwnableDelegateProxy proxy)
{
require(proxies[msg.sender] == address(0));
// 为 `msg.sender` 创建一个代理合约
// `initialize` 实际上,是 `AuthenticatedProxy` 中的方法
proxy = new OwnableDelegateProxy(msg.sender, delegateProxyImplementation, abi.encodeWithSignature("initialize(address,address)", msg.sender, address(this)));
proxies[msg.sender] = proxy;
return proxy;
}

5. 代理对象 OwnableDelegateProxy

最终,为用户注册的合约为 OwnableDelegateProxy.
其构造方法为:

1
2
3
4
5
6
7
8
9
constructor(address owner, address initialImplementation, bytes calldata)
public
{
// 被代理者,可以更换 代理实现,即 AuthenticatedProxy
setUpgradeabilityOwner(owner);
_upgradeTo(initialImplementation);
// 注意,这里通过 `delegatecall` 调用了 AuthenticatedProxy 中的 `initialize` 方法
require(initialImplementation.delegatecall(calldata));
}
1
2
3
4
5
6
7
8
9
// 调用了 AuthenticatedProxy 中的 `initialize`
function initialize (address addrUser, ProxyRegistry addrRegistry) public
{
require(!initialized);
// 以下状态变量,都将在 OwnableDelegateProxy 空间中。
initialized = true;
user = addrUser;
registry = addrRegistry;
}

6. WyvernProxyRegistry

注意,其继承自 ProxyRegistry,在单独的 WyvernProxyRegistry 层面,完成了两件事:

  • 在构造函数中,创建 AuthenticatedProxy,该对象将传递给各个由 WyvernProxyRegistry 创建的 OwnableDelegateProxy

    1
    2
    3
    4
    constructor () public
    {
    delegateProxyImplementation = new AuthenticatedProxy();
    }
  • 可以进行一次没有延时的合约授权

    1
    2
    3
    4
    5
    6
    function grantInitialAuthentication (address authAddress) onlyOwner public
    {
    require(!initialAddressSet);
    initialAddressSet = true;
    contracts[authAddress] = true;
    }

7. OwnedUpgradeabilityProxy

最后,说明下管理代理更新的合约。
主要包含两个功能:谁有权限可以更新实现;当前的实现是谁。

1
2
3
4
5
function transferProxyOwnership(address newOwner) public onlyProxyOwner {
require(newOwner != address(0));
emit ProxyOwnershipTransferred(proxyOwner(), newOwner);
setUpgradeabilityOwner(newOwner);
}
1
2
3
4
5
function _upgradeTo(address implementation) internal {
require(_implementation != implementation);
_implementation = implementation;
emit Upgraded(implementation);
}

© 2024 YueGS