3

WETH10 - 更高效的 WETH

 3 years ago
source link: https://learnblockchain.cn/article/1646
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

本文介绍的代币化的以太币 WETH10 ,实现在更少的 gas 下,支持更多的特性,如:离线授权、交易链、闪电铸币。 基于WETH10的 DEFI 生态一定会生出有趣的组合交易。

玩过DEFI的应该都知道,很多项目是 通过WETH把以太币代币化 ,再接入到ERC20为主的DEFI生态中。

当前使用最广泛的WETH实现是[WETH9](https://etherscan.io/address/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 #code),有兴趣的可以点击链接查看实现。

WETH9上线至今快有3年了,现在社区小伙伴实现了升级版本: WETH10 ,已经部署在 Kovan 测试网

WETH10 新特性

WETH10和 WETH9 一样实现和以太币的包裹,实现WETH与以太币的1:1互换

存款:Ether -> WETH

具体是调用 deposit 存入以太币(或发送到合约),可接收到WETH:

receive() external payable {
        require(msg.sender != address(this));
        _balanceOf[msg.sender] += msg.value;
        emit Transfer(address(0), msg.sender, msg.value);
    }

    function deposit() external payable {
        _balanceOf[msg.sender] += msg.value;
        emit Transfer(address(0), msg.sender, msg.value);
    }

以上实现和WETH9基本一致,WETH10 还可以为指定的账号存款,调用 depositTo

function depositTo(address to) external payable {
        require(to != address(this), "!recipient");
        _balanceOf[to] += msg.value;
        emit Transfer(address(0), to, msg.value);
    }

WETH10 还实现了存款加调用 depositToAndCall ,通过 depositToAndCall 可以把资金存入合约,并立即调用合约自定义的实现 onTokenTransfer ,以往使用WETH9可能需要多笔交易才能完成的工作现在只需要一笔交易完成。

depositToAndCall 实现如下:

function depositToAndCall(address to, bytes calldata data) external payable returns (bool success) {
        require(to != address(this), "!recipient");
        _balanceOf[to] += msg.value;
        emit Transfer(address(0), to, msg.value);

        ERC677Receiver(to).onTokenTransfer(msg.sender, msg.value, data);
        return true;
    }

depositToAndCall 的第 2 个参数 data 是传递给目标函数的参数。 目标函数的调用使用 ERC677 标准

取款 WETH -> Ether

取款通过调用 withdraw 来完成, withdraw 方法如下:

function withdraw(uint256 value) external {
        require(_balanceOf[msg.sender] >= value, "!balance");
        
        _balanceOf[msg.sender] -= value;

        (bool success, ) = msg.sender.call{value: value}("");
        require(success, "!withdraw");

        emit Transfer(msg.sender, address(0), value);
    }

以上实现和WETH9基本一致,WETH10 还可以取款到指定的账号,调用 withdrawTo

function withdrawTo(address to, uint256 value) external {
        require(_balanceOf[msg.sender] >= value, "!balance");
        
        _balanceOf[msg.sender] -= value;

        (bool success, ) = to.call{value: value}("");
        require(success, "!withdraw");

        emit Transfer(msg.sender, address(0), value);
    }

另外还有 withdrawFrom 函数,用来从其他的账号以取款到指定的账号, 当然需要资金的所有者提前 approve , withdrawFrom 实现如下:

function withdrawFrom(address from, address to, uint256 value) external {
        require(_balanceOf[from] >= value, "!balance");
        
        if (from != msg.sender) {
            uint256 allow = allowance[from][msg.sender];
            if (allow != type(uint256).max) {
                require(allow >= value, "!allowance");
                allowance[from][msg.sender] -= value;
                emit Approval(from, msg.sender, allow - value);
            }
        }

        _balanceOf[from] -= value;
        (bool success, ) = to.call{value: value}("");
        require(success, "!withdraw");

        emit Transfer(from, address(0), value);
    }

仔细查看以上代码, 你还可以看到授权量的优化,如果授权量是最大值 type(uint256).max 时,将不再减少授权量,从而降低 gas 成本。这个优化也同样应用到了 transferFrom 中。

离线授权

升级的WETH10, 实现了离线授权功能(实现 EIP2612 ),可以参考文章: 通过链下签名授权实现更少 Gas 的 ERC20代币 ,方便合约合并授权与转账交易,减少 gas 消耗。

调用链

在前面 depositAndCall 函数已经提到过,在用户执行存款时,可以立即在用户定义的合约中执行一笔调用, 这样存款和合约调用发生在一次交易中。

升级的WETH10 也支持在转账时(使用 transferAndCall 转账),同样支持在接收者地址上调用onTokenTransfer, transferAndCall 实现如下:

function transferAndCall(address to, uint value, bytes calldata data) external returns (bool success) {
        require(_balanceOf[msg.sender] >= value, "!balance");
        require(_balanceOf[to] + value >= value, "overflow");

        _balanceOf[msg.sender] -= value;
        _balanceOf[to] += value;

        emit Transfer(msg.sender, to, value);

        ERC677Receiver(to).onTokenTransfer(msg.sender, value, data);
        return true;
    }

闪电铸币

WETH10合约允许通过 flashMint 来增发任意数量的WETH,而不用存入真实的以太币,只需要在交易结束前将其销毁掉。

flashMint 函数会在调用者地址上调用 executeOnFlashMintflashMint 还支持接收一个额外的 bytes 参数传递给 executeOnFlashMintflashMint 的实现如下:

function flashMint(uint256 value, bytes calldata data) external {
        _balanceOf[msg.sender] += value;
        require(_balanceOf[msg.sender] >= value, "overflow");

        emit Transfer(address(0), msg.sender, value);

        FlashMinterLike(msg.sender).executeOnFlashMint(value, data);

        require(_balanceOf[msg.sender] >= value, "!balance");
        _balanceOf[msg.sender] -= value;
        emit Transfer(msg.sender, address(0), value);
    }

预感 flashMint 会产生出很多闪电代新玩法。

玩过DEFI的应该都知道,很多项目是 通过WETH把以太币代币化 ,再接入到ERC20为主的DEFI生态中。

当前使用最广泛的WETH实现是[WETH9]( https://etherscan.io/address/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 #code),有兴趣的可以点击链接查看实现。

WETH9上线至今快有3年了,现在社区小伙伴实现了升级版本: WETH10 ,已经部署在 Kovan 测试网

WETH10 新特性

WETH10和 WETH9 一样实现和以太币的包裹,实现WETH与以太币的1:1互换

存款:Ether -> WETH

具体是调用 deposit 存入以太币(或发送到合约),可接收到WETH:

receive() external payable {
        require(msg.sender != address(this));
        _balanceOf[msg.sender] += msg.value;
        emit Transfer(address(0), msg.sender, msg.value);
    }

    function deposit() external payable {
        _balanceOf[msg.sender] += msg.value;
        emit Transfer(address(0), msg.sender, msg.value);
    }

以上实现和WETH9基本一致,WETH10 还可以为指定的账号存款,调用 depositTo

function depositTo(address to) external payable {
        require(to != address(this), "!recipient");
        _balanceOf[to] += msg.value;
        emit Transfer(address(0), to, msg.value);
    }

WETH10 还实现了存款加调用 depositToAndCall ,通过 depositToAndCall 可以把资金存入合约,并立即调用合约自定义的实现 onTokenTransfer ,以往使用WETH9可能需要多笔交易才能完成的工作现在只需要一笔交易完成。

depositToAndCall 实现如下:

function depositToAndCall(address to, bytes calldata data) external payable returns (bool success) {
        require(to != address(this), "!recipient");
        _balanceOf[to] += msg.value;
        emit Transfer(address(0), to, msg.value);

        ERC677Receiver(to).onTokenTransfer(msg.sender, msg.value, data);
        return true;
    }

depositToAndCall 的第 2 个参数 data 是传递给目标函数的参数。 目标函数的调用使用 ERC677 标准

取款 WETH -> Ether

取款通过调用 withdraw 来完成, withdraw 方法如下:

function withdraw(uint256 value) external {
        require(_balanceOf[msg.sender] >= value, "!balance");

        _balanceOf[msg.sender] -= value;

        (bool success, ) = msg.sender.call{value: value}("");
        require(success, "!withdraw");

        emit Transfer(msg.sender, address(0), value);
    }

以上实现和WETH9基本一致,WETH10 还可以取款到指定的账号,调用 withdrawTo

function withdrawTo(address to, uint256 value) external {
        require(_balanceOf[msg.sender] >= value, "!balance");

        _balanceOf[msg.sender] -= value;

        (bool success, ) = to.call{value: value}("");
        require(success, "!withdraw");

        emit Transfer(msg.sender, address(0), value);
    }

另外还有 withdrawFrom 函数,用来从其他的账号以取款到指定的账号, 当然需要资金的所有者提前 approve , withdrawFrom 实现如下:

function withdrawFrom(address from, address to, uint256 value) external {
        require(_balanceOf[from] >= value, "!balance");

        if (from != msg.sender) {
            uint256 allow = allowance[from][msg.sender];
            if (allow != type(uint256).max) {
                require(allow >= value, "!allowance");
                allowance[from][msg.sender] -= value;
                emit Approval(from, msg.sender, allow - value);
            }
        }

        _balanceOf[from] -= value;
        (bool success, ) = to.call{value: value}("");
        require(success, "!withdraw");

        emit Transfer(from, address(0), value);
    }

仔细查看以上代码, 你还可以看到授权量的优化,如果授权量是最大值 type(uint256).max 时,将不再减少授权量,从而降低 gas 成本。这个优化也同样应用到了 transferFrom 中。

离线授权

升级的WETH10, 实现了离线授权功能(实现 EIP2612 ),可以参考文章: 通过链下签名授权实现更少 Gas 的 ERC20代币 ,方便合约合并授权与转账交易,减少 gas 消耗。

调用链

在前面 depositAndCall 函数已经提到过,在用户执行存款时,可以立即在用户定义的合约中执行一笔调用, 这样存款和合约调用发生在一次交易中。

升级的WETH10 也支持在转账时(使用 transferAndCall 转账),同样支持在接收者地址上调用onTokenTransfer, transferAndCall 实现如下:

function transferAndCall(address to, uint value, bytes calldata data) external returns (bool success) {
        require(_balanceOf[msg.sender] >= value, "!balance");
        require(_balanceOf[to] + value >= value, "overflow");

        _balanceOf[msg.sender] -= value;
        _balanceOf[to] += value;

        emit Transfer(msg.sender, to, value);

        ERC677Receiver(to).onTokenTransfer(msg.sender, value, data);
        return true;
    }

闪电铸币

WETH10合约允许通过 flashMint 来增发任意数量的WETH,而不用存入真实的以太币,只需要在交易结束前将其销毁掉。

flashMint 函数会在调用者地址上调用 executeOnFlashMintflashMint 还支持接收一个额外的 bytes 参数传递给 executeOnFlashMintflashMint 的实现如下:

function flashMint(uint256 value, bytes calldata data) external {
        _balanceOf[msg.sender] += value;
        require(_balanceOf[msg.sender] >= value, "overflow");

        emit Transfer(address(0), msg.sender, value);

        FlashMinterLike(msg.sender).executeOnFlashMint(value, data);

        require(_balanceOf[msg.sender] >= value, "!balance");
        _balanceOf[msg.sender] -= value;
        emit Transfer(msg.sender, address(0), value);
    }

预感 flashMint 会产生出很多闪电代新玩法。

本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

  • 发表于 9分钟前
  • 阅读 ( 7 )
  • 学分 ( 0 )
  • 分类:以太坊

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK