
Uniswap V3交易预计算技巧
Uniswap上做swap交易时,比如用usdt购买btc,会根据界面上输入的usdt数量,实时计算出可以swap到多少个btc,v2版本因为是应用了xy=k的公式,可以方便的计算出来。代码里通过getAmountOut和getAmountIn得到,这两个都是view函数,不需要消耗gas。而到了...
Meebits mint随机算法
先上代码function randomIndex() internal returns (uint) { uint totalSize = TOKEN_LIMIT - numTokens; uint index = uint(keccak256(abi.encodePacked(nonce, ms...

nftx闪电贷领取apecoin空投
已经是两个多月前的事件了,这几天才对这个tx进行了分析和fork重现,记录下来加深一下理解。apecoin的空投并没有限制调用方不能是合约地址,也不是用链下签名再到合约里验签的方式来领取空投,而是直接在合约里校验调用方的地址里有没有bayc/mayc,再加上apecoin当时的价格在8u左右,总体...
<100 subscribers

Uniswap V3交易预计算技巧
Uniswap上做swap交易时,比如用usdt购买btc,会根据界面上输入的usdt数量,实时计算出可以swap到多少个btc,v2版本因为是应用了xy=k的公式,可以方便的计算出来。代码里通过getAmountOut和getAmountIn得到,这两个都是view函数,不需要消耗gas。而到了...
Meebits mint随机算法
先上代码function randomIndex() internal returns (uint) { uint totalSize = TOKEN_LIMIT - numTokens; uint index = uint(keccak256(abi.encodePacked(nonce, ms...

nftx闪电贷领取apecoin空投
已经是两个多月前的事件了,这几天才对这个tx进行了分析和fork重现,记录下来加深一下理解。apecoin的空投并没有限制调用方不能是合约地址,也不是用链下签名再到合约里验签的方式来领取空投,而是直接在合约里校验调用方的地址里有没有bayc/mayc,再加上apecoin当时的价格在8u左右,总体...
Share Dialog
Share Dialog
ENS的注册分为两步,先commit预提交,再registerWithConfig注册。
先看下commit的代码,客户端会先调用makeCommitmentWithConfig获得commitment参数,再调用commit进行预提交。
//存储预提交记录的时间戳 mapping(bytes32=>uint) public commitments; //最小预提交间隔时间,单位:秒 uint public minCommitmentAge; //最大预提交间隔时间,单位:秒 uint public maxCommitmentAge; /** * @dev 生成commitment参数 * @param name ens名 * @param owner 注册者 * @param secret 32随机字节 * @param resolver 正向解析器地睛 * @param addr 正向解析目标地址 */ function makeCommitmentWithConfig(string memory name, address owner, bytes32 secret, address resolver, address addr) pure public returns(bytes32) { bytes32 label = keccak256(bytes(name)); if (resolver == address(0) && addr == address(0)) { return keccak256(abi.encodePacked(label, owner, secret)); } require(resolver != address(0)); return keccak256(abi.encodePacked(label, owner, resolver, addr, secret)); } /** * @dev 预提交 * @param commitment 提交参数 */ function commit(bytes32 commitment) public { require(commitments[commitment] + maxCommitmentAge < block.timestamp);//当重复预提交时,如果时间还未超过最大时间间隔,就不用再重新更新时间戳 commitments[commitment] = block.timestamp;//记录本此提交的时间戳 }
import crypto from 'crypto' function randomSecret() { return '0x' + crypto.randomBytes(32).toString('hex') }//生成makeCommitmentWithConfig的secret参数,实际是32随机字节
此步预提交的目的是为了防止抢跑,如果没有这一步的话,直接一步就是注册域名成功,那么在mev里可以监听注册域名的tx,然后用gasPrice去抢跑,那么一些抢手的域名注册时就可能会被机器人抢走。加上这个预提交步骤后,机器人如果要抢跑域名注册也要监听commit方法,但这个方法的参数的bytes32,解读不出tx是要注册哪个域名,机器人自然就抢不了;而如果机器人直接监听第二步registerWithConfig,但没有先进行第一步预提交tx,自然也成功不了。
下面来看下registerWithConfig方法
contract ETHRegistrarController is Ownable { BaseRegistrarImplementation base; PriceOracle prices; uint public minCommitmentAge; uint public maxCommitmentAge; mapping(bytes32=>uint) public commitments; /** * @dev 注册ens并配置 * @param name ens名 * @param owner 注册者 * @param duration 域名有效时间 * @param secret 32随机字节 * @param resolver 正向解析器地睛 * @param addr 正向解析目标地址 */ function registerWithConfig(string memory name, address owner, uint duration, bytes32 secret, address resolver, address addr) public payable { bytes32 commitment = makeCommitmentWithConfig(name, owner, secret, resolver, addr); uint cost = _consumeCommitment(name, duration, commitment); bytes32 label = keccak256(bytes(name)); uint256 tokenId = uint256(label); uint expires; //存在正向解析器 if(resolver != address(0)) { //mint NFT,设置ens的注册者为address(this),这里先临街把注册者设为当前合约地址, //因为后面要setResolver时,要求msg.sender是合约注册者,如果直接把注册者给到owner,那在setResolver时就会fail expires = base.register(tokenId, address(this), duration); //计算nodehash bytes32 nodehash = keccak256(abi.encodePacked(base.baseNode(), label)); //设置正向解析器地址 base.ens().setResolver(nodehash, resolver); //如果正向解析地址不为0,就设置正向解析地址 if (addr != address(0)) { Resolver(resolver).setAddr(nodehash, addr); } //用owner重新认领这个ens base.reclaim(tokenId, owner); //转移erc721 NFT给owner base.transferFrom(address(this), owner, tokenId); } else {//不存在正向解析器,直接把注册者设为owner就可以了 require(addr == address(0)); expires = base.register(tokenId, owner, duration); } //emit注册事件 emit NameRegistered(name, label, owner, cost, expires); //发送过来的eth超过了费用,就退还 if(msg.value > cost) { payable(msg.sender).transfer(msg.value - cost); } } /** * @dev 消费commitment * @param name ens名 * @param duration 域名有效时间 * @param commitment 参数 */ function _consumeCommitment(string memory name, uint duration, bytes32 commitment) internal returns (uint256) { //commit之后必须等待最小间隔时间后才能注册,当前是60秒 require(commitments[commitment] + minCommitmentAge <= block.timestamp); //不能超过commit之后的最大间隔境,当前是7天 require(commitments[commitment] + maxCommitmentAge > block.timestamp); require(available(name));//这个ens要可用 delete(commitments[commitment]);//删除预提交信息,gas返还 uint cost = rentPrice(name, duration);//根据域名购买的有效时间,计算费用 require(duration >= MIN_REGISTRATION_DURATION);//有效时间不能小于最小时间,当前是28天 require(msg.value >= cost);//传入的eth要>=费用 return cost; } /** * @dev 价格计算 * @param name ens名 * @param duration 域名有效时间 */ function rentPrice(string memory name, uint duration) view public returns(uint) { bytes32 hash = keccak256(bytes(name)); return prices.price(name, base.nameExpires(uint256(hash)), duration); } }
contract StablePriceOracle is Ownable, PriceOracle { //索引2,3,4分别存储域名3,4,5位及以上的每秒的usd费用 //现在是3位每年640美金,4位每年160美金,5位及以上每年5美金 uint[] public rentPrices; function price(string calldata name, uint expires, uint duration) external view override returns(uint) { uint len = name.strlen(); if(len > rentPrices.length) { len = rentPrices.length; } require(len > 0); uint basePrice = rentPrices[len - 1].mul(duration);//每秒费用*有效时间 basePrice = basePrice.add(_premium(name, expires, duration));//这里_premium固定是0 return attoUSDToWei(basePrice);//根据预言机转成wei价格,用的是MakerDAO的medianizer预言机 } }
contract BaseRegistrarImplementation is ERC721, BaseRegistrar { // A map of expiry times mapping(uint256=>uint) expiries; modifier live { require(ens.owner(baseNode) == address(this));//基结点的所有者必须是本合约 _; } modifier onlyController { require(controllers[msg.sender]);//调用方地址必须是可控制这个合约的地址,这个地址需要本合约owner事先用addController添加进来 _; } //返回域名是否可以注册 function available(uint256 id) public view override returns(bool) { return expiries[id] + GRACE_PERIOD < block.timestamp;//过期时间戳+90天的保护期<当前时间戳,就表示过期且过了保护期了,可以注册 } function _register(uint256 id, address owner, uint duration, bool updateRegistry) internal live onlyController returns(uint) { require(available(id)); require(block.timestamp + duration + GRACE_PERIOD > block.timestamp + GRACE_PERIOD); //先计算一下当前时间戳+有效时间+保护期,如果溢出就会直接fail,防止这次注册成功后,后面都不可renew了 expiries[id] = block.timestamp + duration; if(_exists(id)) { //之前存在这个id的nft,则燃烧,说明这个nft是过期又注册的 _burn(id); } _mint(owner, id); if(updateRegistry) {//更新ens的owner ens.setSubnodeOwner(baseNode, bytes32(id), owner); } emit NameRegistered(id, owner, block.timestamp + duration); return block.timestamp + duration; } /** * @dev 重新认领ens */ function reclaim(uint256 id, address owner) external override live { require(_isApprovedOrOwner(msg.sender, id)); ens.setSubnodeOwner(baseNode, bytes32(id), owner); } }
讲到这里,需要说明一下nodehash的生成过程,以btc.eth为例,
keccak256(“btc”)得到0x4bac7d8baf3f4f429951de9baff555c2f70564c6a43361e09971ef219908703d,再keccak256(abi.encodePacked(base.baseNode(), “0x4bac7d8baf3f4f429951de9baff555c2f70564c6a43361e09971ef219908703d”))得到0x9b530388C920f6b1dD3d05AEFb9B4650Fe388B2F,就是实际btc.eth在ENS系统中的node,而baseNode=keccak256(abi.encodePacked(“0x00000000000000000000000000000000”, keccak256(“eth”))。如果是btc.eth的子域名就再递归下去。
contract ENSRegistry is ENS { //解析器结构体 struct Record { address owner;//注册者 address resolver;//解析器合约地址 uint64 ttl; } /** * @dev Sets the resolver address for the specified node. * @param node The node to update. * @param resolver The address of the resolver. */ function setResolver(bytes32 node, address resolver) public virtual override authorised(node) { emit NewResolver(node, resolver); records[node].resolver = resolver;//设置node的解析器地址,这里的node就是上面讲到的nodehash生成的node } }
abstract contract AddrResolver is ResolverBase { uint constant private COIN_TYPE_ETH = 60;//eth地址默认60,还可以解析btc,ltc,dego地址,分别对应0,2,3 mapping(bytes32=>mapping(uint=>bytes)) _addresses;//此map存储node=>60=>address,就表示某个eth域名对应的地址 /** * Sets the address associated with an ENS node. * May only be called by the owner of that node in the ENS registry. * @param node The node to update. * @param a The address to set. */ function setAddr(bytes32 node, address a) external authorised(node) { setAddr(node, COIN_TYPE_ETH, addressToBytes(a)); } function setAddr(bytes32 node, uint coinType, bytes memory a) public authorised(node) { emit AddressChanged(node, coinType, a); if(coinType == COIN_TYPE_ETH) { emit AddrChanged(node, bytesToAddress(a)); } _addresses[node][coinType] = a; } }
ENS域名也是ERC721的NFT,可以以opensea等平台进行交易,但是它没有metadata,且在交易成功后,新的owner需要调用一下BaseRegistrarImplementation合约的reclaim方法,重新认领一下,才可以管理自己的ENS域名。
在域名过期后,设有90天的保护期,在保护期内owner是可以直接renew的,也就是续期,过了保护期后就要重新commit去注册了,这时候其他人也就可以注册这个域名了。
上面讲的是用ens域名解析出用户的钱包地址,下面来讲下怎么反向解析,也就是用钱包地址反向解析出ens域名。
contract ReverseRegistrar { // namehash('addr.reverse') bytes32 public constant ADDR_REVERSE_NODE = 0x91d1777781884d03a6757a803996e38de2a42967fb37eeaca72729271025a9e2; function claimWithResolver(address owner, address resolver) public returns (bytes32) { bytes32 label = sha3HexAddress(msg.sender);//将钱包地址转成字符串再做keccak256 bytes32 node = keccak256(abi.encodePacked(ADDR_REVERSE_NODE, label)); address currentOwner = ens.owner(node); //需要更新解析器地址 if (resolver != address(0x0) && resolver != ens.resolver(node)) { // Transfer the name to us first if it's not already if (currentOwner != address(this)) {//之前的owner不是当前合约地址 ens.setSubnodeOwner(ADDR_REVERSE_NODE, label, address(this));//设置node的owner为当前合约地址 currentOwner = address(this); } ens.setResolver(node, resolver);//设置解析器地址 } //如果用了不同的ReverseRegistar时,就会出现之前的owner和不是当前合约地址,需要更新 if (currentOwner != owner) { ens.setSubnodeOwner(ADDR_REVERSE_NODE, label, owner); } return node; } function setName(string memory name) public returns (bytes32) { bytes32 node = claimWithResolver(address(this), address(defaultResolver)); defaultResolver.setName(node, name);//设置node对应的ens name return node; } }
contract DefaultReverseResolver { mapping (bytes32 => string) public name; function setName(bytes32 node, string memory _name) public onlyOwner(node) { name[node] = _name; } }
从代码出可以看出,任何地址都可以设置反向解析到指定ens域名,没有做ens域名的所有者限制。与正向解析用eth做根不同的时,反向解析用addr.reverse做根。
ENS的owner可以将ens授权给其它地址, 这些地址可以理解为管理地址,可以对这个域名设置各自解析器等操作,但不能转让这个nft。
contract ENSRegistry is ENS { function setApprovalForAll(address operator, bool approved) external virtual override { operators[msg.sender][operator] = approved; emit ApprovalForAll(msg.sender, operator, approved); } }
可以看到BaseRegistrarImplementation继承了ERC721,最终还是用了erc721的owner体系去实现nft的归属,在ENSRegistry里维护的是ens业务上的所有者、解析器等信息。因此,如果在opensea上交易后,只完成的nft的转让,还需要再reclaim一次,才能自己设置地址解析。
contract BaseRegistrarImplementation is ERC721, BaseRegistrar { /** * @dev 重新认领ens */ function reclaim(uint256 id, address owner) external override live { require(_isApprovedOrOwner(msg.sender, id)); ens.setSubnodeOwner(baseNode, bytes32(id), owner); } }
ENS有提案允许保存ABI信息到链上,并规定了几种contentType,如下:
1 JSON
2 zlib-compressed JSON
4 CBOR
8 URI
要求contentType是2的指数,在代码里用了位运算来实现这个判断。
例如:contentType=8,二进制是1000,将contentType-1=7,二进制是0111,将0111与1000做与运算,得到0,也就是说-1后再&运算将永远是0,以此来判断2的指数。
function setABI(bytes32 node, uint256 contentType, bytes calldata data) external authorised(node) { // Content types must be powers of 2 require(((contentType - 1) & contentType) == 0); abis[node][contentType] = data; emit ABIChanged(node, contentType); }
ENS的注册分为两步,先commit预提交,再registerWithConfig注册。
先看下commit的代码,客户端会先调用makeCommitmentWithConfig获得commitment参数,再调用commit进行预提交。
//存储预提交记录的时间戳 mapping(bytes32=>uint) public commitments; //最小预提交间隔时间,单位:秒 uint public minCommitmentAge; //最大预提交间隔时间,单位:秒 uint public maxCommitmentAge; /** * @dev 生成commitment参数 * @param name ens名 * @param owner 注册者 * @param secret 32随机字节 * @param resolver 正向解析器地睛 * @param addr 正向解析目标地址 */ function makeCommitmentWithConfig(string memory name, address owner, bytes32 secret, address resolver, address addr) pure public returns(bytes32) { bytes32 label = keccak256(bytes(name)); if (resolver == address(0) && addr == address(0)) { return keccak256(abi.encodePacked(label, owner, secret)); } require(resolver != address(0)); return keccak256(abi.encodePacked(label, owner, resolver, addr, secret)); } /** * @dev 预提交 * @param commitment 提交参数 */ function commit(bytes32 commitment) public { require(commitments[commitment] + maxCommitmentAge < block.timestamp);//当重复预提交时,如果时间还未超过最大时间间隔,就不用再重新更新时间戳 commitments[commitment] = block.timestamp;//记录本此提交的时间戳 }
import crypto from 'crypto' function randomSecret() { return '0x' + crypto.randomBytes(32).toString('hex') }//生成makeCommitmentWithConfig的secret参数,实际是32随机字节
此步预提交的目的是为了防止抢跑,如果没有这一步的话,直接一步就是注册域名成功,那么在mev里可以监听注册域名的tx,然后用gasPrice去抢跑,那么一些抢手的域名注册时就可能会被机器人抢走。加上这个预提交步骤后,机器人如果要抢跑域名注册也要监听commit方法,但这个方法的参数的bytes32,解读不出tx是要注册哪个域名,机器人自然就抢不了;而如果机器人直接监听第二步registerWithConfig,但没有先进行第一步预提交tx,自然也成功不了。
下面来看下registerWithConfig方法
contract ETHRegistrarController is Ownable { BaseRegistrarImplementation base; PriceOracle prices; uint public minCommitmentAge; uint public maxCommitmentAge; mapping(bytes32=>uint) public commitments; /** * @dev 注册ens并配置 * @param name ens名 * @param owner 注册者 * @param duration 域名有效时间 * @param secret 32随机字节 * @param resolver 正向解析器地睛 * @param addr 正向解析目标地址 */ function registerWithConfig(string memory name, address owner, uint duration, bytes32 secret, address resolver, address addr) public payable { bytes32 commitment = makeCommitmentWithConfig(name, owner, secret, resolver, addr); uint cost = _consumeCommitment(name, duration, commitment); bytes32 label = keccak256(bytes(name)); uint256 tokenId = uint256(label); uint expires; //存在正向解析器 if(resolver != address(0)) { //mint NFT,设置ens的注册者为address(this),这里先临街把注册者设为当前合约地址, //因为后面要setResolver时,要求msg.sender是合约注册者,如果直接把注册者给到owner,那在setResolver时就会fail expires = base.register(tokenId, address(this), duration); //计算nodehash bytes32 nodehash = keccak256(abi.encodePacked(base.baseNode(), label)); //设置正向解析器地址 base.ens().setResolver(nodehash, resolver); //如果正向解析地址不为0,就设置正向解析地址 if (addr != address(0)) { Resolver(resolver).setAddr(nodehash, addr); } //用owner重新认领这个ens base.reclaim(tokenId, owner); //转移erc721 NFT给owner base.transferFrom(address(this), owner, tokenId); } else {//不存在正向解析器,直接把注册者设为owner就可以了 require(addr == address(0)); expires = base.register(tokenId, owner, duration); } //emit注册事件 emit NameRegistered(name, label, owner, cost, expires); //发送过来的eth超过了费用,就退还 if(msg.value > cost) { payable(msg.sender).transfer(msg.value - cost); } } /** * @dev 消费commitment * @param name ens名 * @param duration 域名有效时间 * @param commitment 参数 */ function _consumeCommitment(string memory name, uint duration, bytes32 commitment) internal returns (uint256) { //commit之后必须等待最小间隔时间后才能注册,当前是60秒 require(commitments[commitment] + minCommitmentAge <= block.timestamp); //不能超过commit之后的最大间隔境,当前是7天 require(commitments[commitment] + maxCommitmentAge > block.timestamp); require(available(name));//这个ens要可用 delete(commitments[commitment]);//删除预提交信息,gas返还 uint cost = rentPrice(name, duration);//根据域名购买的有效时间,计算费用 require(duration >= MIN_REGISTRATION_DURATION);//有效时间不能小于最小时间,当前是28天 require(msg.value >= cost);//传入的eth要>=费用 return cost; } /** * @dev 价格计算 * @param name ens名 * @param duration 域名有效时间 */ function rentPrice(string memory name, uint duration) view public returns(uint) { bytes32 hash = keccak256(bytes(name)); return prices.price(name, base.nameExpires(uint256(hash)), duration); } }
contract StablePriceOracle is Ownable, PriceOracle { //索引2,3,4分别存储域名3,4,5位及以上的每秒的usd费用 //现在是3位每年640美金,4位每年160美金,5位及以上每年5美金 uint[] public rentPrices; function price(string calldata name, uint expires, uint duration) external view override returns(uint) { uint len = name.strlen(); if(len > rentPrices.length) { len = rentPrices.length; } require(len > 0); uint basePrice = rentPrices[len - 1].mul(duration);//每秒费用*有效时间 basePrice = basePrice.add(_premium(name, expires, duration));//这里_premium固定是0 return attoUSDToWei(basePrice);//根据预言机转成wei价格,用的是MakerDAO的medianizer预言机 } }
contract BaseRegistrarImplementation is ERC721, BaseRegistrar { // A map of expiry times mapping(uint256=>uint) expiries; modifier live { require(ens.owner(baseNode) == address(this));//基结点的所有者必须是本合约 _; } modifier onlyController { require(controllers[msg.sender]);//调用方地址必须是可控制这个合约的地址,这个地址需要本合约owner事先用addController添加进来 _; } //返回域名是否可以注册 function available(uint256 id) public view override returns(bool) { return expiries[id] + GRACE_PERIOD < block.timestamp;//过期时间戳+90天的保护期<当前时间戳,就表示过期且过了保护期了,可以注册 } function _register(uint256 id, address owner, uint duration, bool updateRegistry) internal live onlyController returns(uint) { require(available(id)); require(block.timestamp + duration + GRACE_PERIOD > block.timestamp + GRACE_PERIOD); //先计算一下当前时间戳+有效时间+保护期,如果溢出就会直接fail,防止这次注册成功后,后面都不可renew了 expiries[id] = block.timestamp + duration; if(_exists(id)) { //之前存在这个id的nft,则燃烧,说明这个nft是过期又注册的 _burn(id); } _mint(owner, id); if(updateRegistry) {//更新ens的owner ens.setSubnodeOwner(baseNode, bytes32(id), owner); } emit NameRegistered(id, owner, block.timestamp + duration); return block.timestamp + duration; } /** * @dev 重新认领ens */ function reclaim(uint256 id, address owner) external override live { require(_isApprovedOrOwner(msg.sender, id)); ens.setSubnodeOwner(baseNode, bytes32(id), owner); } }
讲到这里,需要说明一下nodehash的生成过程,以btc.eth为例,
keccak256(“btc”)得到0x4bac7d8baf3f4f429951de9baff555c2f70564c6a43361e09971ef219908703d,再keccak256(abi.encodePacked(base.baseNode(), “0x4bac7d8baf3f4f429951de9baff555c2f70564c6a43361e09971ef219908703d”))得到0x9b530388C920f6b1dD3d05AEFb9B4650Fe388B2F,就是实际btc.eth在ENS系统中的node,而baseNode=keccak256(abi.encodePacked(“0x00000000000000000000000000000000”, keccak256(“eth”))。如果是btc.eth的子域名就再递归下去。
contract ENSRegistry is ENS { //解析器结构体 struct Record { address owner;//注册者 address resolver;//解析器合约地址 uint64 ttl; } /** * @dev Sets the resolver address for the specified node. * @param node The node to update. * @param resolver The address of the resolver. */ function setResolver(bytes32 node, address resolver) public virtual override authorised(node) { emit NewResolver(node, resolver); records[node].resolver = resolver;//设置node的解析器地址,这里的node就是上面讲到的nodehash生成的node } }
abstract contract AddrResolver is ResolverBase { uint constant private COIN_TYPE_ETH = 60;//eth地址默认60,还可以解析btc,ltc,dego地址,分别对应0,2,3 mapping(bytes32=>mapping(uint=>bytes)) _addresses;//此map存储node=>60=>address,就表示某个eth域名对应的地址 /** * Sets the address associated with an ENS node. * May only be called by the owner of that node in the ENS registry. * @param node The node to update. * @param a The address to set. */ function setAddr(bytes32 node, address a) external authorised(node) { setAddr(node, COIN_TYPE_ETH, addressToBytes(a)); } function setAddr(bytes32 node, uint coinType, bytes memory a) public authorised(node) { emit AddressChanged(node, coinType, a); if(coinType == COIN_TYPE_ETH) { emit AddrChanged(node, bytesToAddress(a)); } _addresses[node][coinType] = a; } }
ENS域名也是ERC721的NFT,可以以opensea等平台进行交易,但是它没有metadata,且在交易成功后,新的owner需要调用一下BaseRegistrarImplementation合约的reclaim方法,重新认领一下,才可以管理自己的ENS域名。
在域名过期后,设有90天的保护期,在保护期内owner是可以直接renew的,也就是续期,过了保护期后就要重新commit去注册了,这时候其他人也就可以注册这个域名了。
上面讲的是用ens域名解析出用户的钱包地址,下面来讲下怎么反向解析,也就是用钱包地址反向解析出ens域名。
contract ReverseRegistrar { // namehash('addr.reverse') bytes32 public constant ADDR_REVERSE_NODE = 0x91d1777781884d03a6757a803996e38de2a42967fb37eeaca72729271025a9e2; function claimWithResolver(address owner, address resolver) public returns (bytes32) { bytes32 label = sha3HexAddress(msg.sender);//将钱包地址转成字符串再做keccak256 bytes32 node = keccak256(abi.encodePacked(ADDR_REVERSE_NODE, label)); address currentOwner = ens.owner(node); //需要更新解析器地址 if (resolver != address(0x0) && resolver != ens.resolver(node)) { // Transfer the name to us first if it's not already if (currentOwner != address(this)) {//之前的owner不是当前合约地址 ens.setSubnodeOwner(ADDR_REVERSE_NODE, label, address(this));//设置node的owner为当前合约地址 currentOwner = address(this); } ens.setResolver(node, resolver);//设置解析器地址 } //如果用了不同的ReverseRegistar时,就会出现之前的owner和不是当前合约地址,需要更新 if (currentOwner != owner) { ens.setSubnodeOwner(ADDR_REVERSE_NODE, label, owner); } return node; } function setName(string memory name) public returns (bytes32) { bytes32 node = claimWithResolver(address(this), address(defaultResolver)); defaultResolver.setName(node, name);//设置node对应的ens name return node; } }
contract DefaultReverseResolver { mapping (bytes32 => string) public name; function setName(bytes32 node, string memory _name) public onlyOwner(node) { name[node] = _name; } }
从代码出可以看出,任何地址都可以设置反向解析到指定ens域名,没有做ens域名的所有者限制。与正向解析用eth做根不同的时,反向解析用addr.reverse做根。
ENS的owner可以将ens授权给其它地址, 这些地址可以理解为管理地址,可以对这个域名设置各自解析器等操作,但不能转让这个nft。
contract ENSRegistry is ENS { function setApprovalForAll(address operator, bool approved) external virtual override { operators[msg.sender][operator] = approved; emit ApprovalForAll(msg.sender, operator, approved); } }
可以看到BaseRegistrarImplementation继承了ERC721,最终还是用了erc721的owner体系去实现nft的归属,在ENSRegistry里维护的是ens业务上的所有者、解析器等信息。因此,如果在opensea上交易后,只完成的nft的转让,还需要再reclaim一次,才能自己设置地址解析。
contract BaseRegistrarImplementation is ERC721, BaseRegistrar { /** * @dev 重新认领ens */ function reclaim(uint256 id, address owner) external override live { require(_isApprovedOrOwner(msg.sender, id)); ens.setSubnodeOwner(baseNode, bytes32(id), owner); } }
ENS有提案允许保存ABI信息到链上,并规定了几种contentType,如下:
1 JSON
2 zlib-compressed JSON
4 CBOR
8 URI
要求contentType是2的指数,在代码里用了位运算来实现这个判断。
例如:contentType=8,二进制是1000,将contentType-1=7,二进制是0111,将0111与1000做与运算,得到0,也就是说-1后再&运算将永远是0,以此来判断2的指数。
function setABI(bytes32 node, uint256 contentType, bytes calldata data) external authorised(node) { // Content types must be powers of 2 require(((contentType - 1) & contentType) == 0); abis[node][contentType] = data; emit ABIChanged(node, contentType); }
No comments yet