Delegate Cash 合约分析

1.

最近猴子游戏 Dookey Dash 很火,这个游戏可以看成神庙逃亡的下水道版本,跑得越远,分数越高。但是很多人手残(比如我),很难玩出高的分数,于是游戏代打很自然的出现的了。

但是有个难处理的细节,代打的人如何获取到门票的授权?总不能直接把钱包给出去或者把游戏门票(Sewer Pass)转出去,这样太不安全了。在这里的关键就是怎么去证明你拥有这个资产的所有权,但是又不威胁到资产的安全。

在这个过程中,我发现有一个合约很好地解决了这个问题,就是这个 Delegate Cash。这个合约可以解决很多场景下资产所有权的问题:

  • 比如上面的代打场景
  • 保护资产安全,在加群或者领空投时,不想直接操作钱包

0

整个流程就是在 Delegate Cash 合约,你可以把自己的地址 A 代理给另外一个地址 B,B 拿到这个代理之后,能证明这些资产是属于你的,但是同时 B 合约无法操作 A 合约中的任何资产。这点很重要,这样即使 B 合约被钓鱼了或者私钥泄漏了,地址 A 中的资产也是安全的。

2.

Delegate Cash 有三种代理模式:

  • 代理一个 token
  • 代理一个合约地址
  • 代理整个地址
// 代理整个地址
function delegateForAll(address delegate, bool value) external;
// 代理某个合约地址
function delegateForContract(address delegate, address contract_, bool value) external;
// 代理一个 token
function delegateForToken(address delegate, address contract_, uint256 tokenId, bool value) external;

用户所有的代理信息都被存储在下面两个变量中:

mapping(address => EnumerableSet.Bytes32Set) internal delegationHashes;
mapping(bytes32 => IDelegationRegistry.DelegationInfo) internal delegationInfo;

delegationHashes 中存储的是相关代理信息(比如代理地址、被代理地址、被代理的合约或者 token)算出来的 hash, delegationInfo中存储的是这些信息的原文。

绑定代理和解除代理都是通过下面这个方法来实现:

function _setDelegationValues(
        address delegate,
        bytes32 delegateHash,
        bool value,
        IDelegationRegistry.DelegationType type_,
        address vault,
        address contract_,
        uint256 tokenId
    ) internal {
        if (value) {
            delegations[vault][vaultVersion[vault]].add(delegateHash);
            delegationHashes[delegate].add(delegateHash);
            delegationInfo[delegateHash] =
                DelegationInfo({vault: vault, delegate: delegate, type_: type_, contract_: contract_, tokenId: tokenId});
        } else {
            delegations[vault][vaultVersion[vault]].remove(delegateHash);
            delegationHashes[delegate].remove(delegateHash);
            delete delegationInfo[delegateHash];
        }
    }

如果 value 为 true,则是绑定代理,如果 value 为 false,则是解除代理。然后还提供了一系列的查询方法来检查代理关系是否存在。

整体的逻辑就这些,相当简单。但是还存在一个小问题,如果在使用的过程中,绑定了很多代理关系,一个个去解绑就会浪费很多 gas,所以还需要一个一件解绑所有代理的功能,Delegate Cash 的实现相当巧妙。

引入了下面两个 version 的 map,其中 valutVersion 记录的是地址的版本,在当前的合约实现中,这个 map 的值的返回值一直是 0,delegateVersion 中记录的是当前这次代理的版本。

mapping(address => uint256) internal vaultVersion;
mapping(address => mapping(address => uint256)) internal delegateVersion;

这两个 Map 中存储的版本信息在每次算代理信息 hash 的时候会被用到:

function _computeAllDelegationHash(address vault, address delegate) internal view returns (bytes32) {
        uint256 vaultVersion_ = vaultVersion[vault];
        uint256 delegateVersion_ = delegateVersion[vault][delegate];
        return keccak256(abi.encode(delegate, vault, vaultVersion_, delegateVersion_));
}

所以在取消对某个地址的全部代理时,只需要对这个版本号加 1:

function _revokeDelegate(address delegate, address vault) internal {
        ++delegateVersion[vault][delegate];
}

这样就会让后续检查代理时的 hash 对比都会失败,对应的代理关系也全部失效:

 if (delegationHash == _computeAllDelegationHash(vault, delegationInfo_.delegate)) {
    //....
 }

后续在其他的 DAPP 中,如果要检查用户是否拥有某个资产,只需要来检查这个代理关系就可以:

address requester = msg.sender;
  
if (_vault != address(0)) { 
     bool isDelegateValid = dc.checkDelegateForContract(msg.sender, _vault, NFT_CONTRACT);
     require(isDelegateValid, "invalid delegate-vault pairing");
     requester = _vault;
 }

3.

通过这样的一个简单的合约,就可以在安全的情况下来证明用户是否拥有某个资产。这个也符合链上资产使用习惯,在大多数场景中,只需要证明资产是自己的,但很多人的资产就是在这个证明的过程中丢的。

这个合约对 gas 的消耗很大,在使用的过程中,要尽量减少代理和取消代理的次数。

参考链接

[1]https://delegate.cash/
[2]https://etherscan.io/address/0x00000000000076A84feF008CDAbe6409d2FE638B#code

©2024 Rayjun    PowerBy Hexo