# Delegate Cash 合约分析 **Published by:** [rayjun.eth](https://paragraph.com/@rayjun-2/) **Published on:** 2023-02-20 **URL:** https://paragraph.com/@rayjun-2/delegate-cash ## Content 1. 最近猴子游戏 Dookey Dash 很火,这个游戏可以看成神庙逃亡的下水道版本,跑得越远,分数越高。但是很多人手残(比如我),很难玩出高的分数,于是游戏代打很自然的出现的了。 但是有个难处理的细节,代打的人如何获取到门票的授权?总不能直接把钱包给出去或者把游戏门票(Sewer Pass)转出去,这样太不安全了。在这里的关键就是怎么去证明你拥有这个资产的所有权,但是又不威胁到资产的安全。 在这个过程中,我发现有一个合约很好地解决了这个问题,就是这个 Delegate Cash。这个合约可以解决很多场景下资产所有权的问题:比如上面的代打场景保护资产安全,在加群或者领空投时,不想直接操作钱包Delegate Cash整个流程就是在 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 ## Publication Information - [rayjun.eth](https://paragraph.com/@rayjun-2/): Publication homepage - [All Posts](https://paragraph.com/@rayjun-2/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@rayjun-2): Subscribe to updates - [Twitter](https://twitter.com/rayjun0412): Follow on Twitter