ENS的注册分为两步,先commit预提交,再registerWithConfig注册。
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;//记录本此提交的时间戳
}
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);
}
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!