这个简单的解决方案有一个显而易见的问题:部署者必须在部署时将所有余额填充到 contract PreloadedToken is ERC20 { bytes32 merkleRoot; mapping(address=>bool) claimed; function claimableBalanceWithProof(address addr, uint balance, bytes proof) external view returns(uint) { require(verifyProof(keccak256(addr, balance), proof)); if(!claimed[addr]) { return balance; } return 0; } function claimWithProof(address addr, uint balance, bytes proof) external { require(verifyProof(keccak256(addr, balance), proof); if(claimed[addr]) { return; } mint(addr, balance); claimed[addr] = true; } }
(为了简化,我们省略掉了 这个方法非常有效,合约的作者也不再需要花费大量的 eth 来预加载所有余额,一个默克尔根就足够了,而且调用者想申领余额的时候,可以自己支付证明 token 所有权的开销。 不过,现在调用者必须理解生成证明的具体流程,并且知道要到哪儿去获取余额清单来生成自己账户的证明。如果我们可以把第一个方案的接口(方便),与第二个方案的效率结合起来,那就完美了。这就是我们的方案。
首先,我们加入了匹配初始 string gateway; function claimableBalance(address addr) external view returns(bytes prefix, string url) { return (abi.encodeWithSelector(claimableBalanceWithProof.selector, addr), gateway); } function claim(address addr) external view returns(bytes prefix, string url) { return (abi.encodeWithSelector(claimWithProof.selector, addr), gateway); 这些函数的调用者可以得到两个值:第一个值是一个后续 callback 的前缀;第二个值是一个网关服务的 URL。该前缀保证了两件事:callback 会用相关的 proof 函数来响应,并且其第一个参数会是所提供的地址。这防止了网关用给另一个地址的数据来响应请求。
接下来,我们需要实现一个网关服务来,可以满足客户端的查询请求。以 const args = tokenInterface.decodeFunctionData("claim", data); const balance = balances[args.addr]; const proof = merkleTree.getProof(addr, balance); return merkleInterface.encodeFunctionData("claimWithProof", [args.addr, balance, proof]);
(再一次,为了简洁,我们假设已经有了包括 这里的网关服务只需要为客户端所发送的 (责任编辑:admin1) |