跳到主要内容

使用地址类型

在前面的开发工具 - MetaMask 钱包 一节中,我们创建了自己的钱包账号

账户与地址

Solidity 合约程序里,使用地址类型来表示我们的账号,如下在合约中,获取了用户地址,保存在地址类型(address)中:

contract testAddr { 
address public user;
function getUserAddress() public {
user = msg.sender;
}
}

地址类型有两种:

  • address:保存一个20字节的值(以太坊地址的大小)。

  • address payable:表示可支付地址(可接受以太币的地址),在地址格式上,其实与address 完全相同,也是20字节。

备注

那为什么要使用 addressaddress payable 两种类型呢?

如果不做区分,当我们把 ETH 转到一个地址上时,恰巧如果后者是一个合约地址(即合约账户)又没有处理ETH的逻辑,那么 ETH 将永远锁死在该合约地址上,任何人都无法提取和使用它。

因此,需要做此区分,显示的表达,一个地址可以接受ETH, 表示其有处理ETH的逻辑(EOA 账户本身可转账ETH)。

addressaddress payable 两种类型尽管格式一样,但address payable拥有的两个成员函数transfersendaddress 没有这两个方法),transfersend 的作用是向该地址转账,下文会进一步介绍。

在编写合约时,大部分时候,使用address就好,当需要向地址转账时,可以使用以下代码把address 转换为address payable

address payable ap = payable(addr);
备注

上面的转换方法是在 Solidity 0.6 加入,如果使用的 Solidity 0.5 版本的编译器,则使用 address payable ap = address(uint160(addr));

若被转换的地址是一个是合约地址时,则合约需要实现了接收(receive)函数或payable回退函数(参考合约如何接收 ETH)。

如果转换的合约地址上没有接收或 payable 回退函数,可以使用这个魔法payable(address(addr)) , 即先转为普通地址类型,在转换为address payable类型 。

地址类型上支持哪些操作

地址比较

地址类型支持的类似整型的比较运算:==(两个地址相同)、!=(两个地址不相同), 例如:

    function _onlyOwner() internal view {
require(owner() == msg.sender, "调用者不是 Owner");
_;
}

function transferOwnership(address newOwner) public onlyOwner {
require(newOwner != address(0), "新的 Owner 不可以是 零地址");
/// ....
}

地址类型还支持其他介个运算: <=、<、>= 以及 > 。

对地址转账及获取地址余额

地址类型还有一些成员函数属性及函数,因此地址类型在表现上还类似面向对象语言的中的类(内置类), 最常使用的是余额属性与转账函数:

  1. addr.balance 属性 : 返回地址的余额, 余额以wei为单位 (uint256)。

  2. addr_payable.transfer(uint256 amount) : 用来向地址发送amount数量以太币(wei),transfer 函数只使用固定的 2300 gas , 发送失败时抛出异常。

  3. addr_payable.send(uint256 amount) returns (bool): send 功能上和transfer 函数一样,同样使用固定的 2300 gas , 但是在发送失败时不抛出异常,而是返回false

备注

你也许发现了 addr.transfer(y)require(addr.send(y)) 是等价的, 对的。

send是transfer的低级版本。如果执行失败,当前的合约不会因为异常而终止, 而在使用 send 的时候,如果不检查返回值,就会有额外风险, 编写智能合约风险真是无处不在呀。

使用示例

pragma solidity ^0.8.0;

contract testAddr {

// 如果合约的余额大于等于10,而x小于10,则给x转10 wei
function testTrasfer(address payable x) public {
address myAddress = address(this);
if (x.balance < 10 && myAddress.balance >= 10) {
x.transfer(10);
}
}
}

上面代码的 address myAddress = address(this); 就是把合约转换为地址类型,然后用.balance获取余额, 再使用 .transfer 向 x 转账。

账户中,在 EVM 层面是,外部用户账户和合约账户是一样的,因此可以把合约类似转换为地址类型。

sendtransfer 函数只使用 2300 gas,在对合约地址转账时,会调用合约上的函数,很容易因 gas 不足而失败,一个推荐的转账方法是:

function safeTransferETH(address to, uint256 value) internal {
(bool success, ) = to.call{value: value}(new bytes(0));
require(success, 'TransferHelper::safeTransferETH: ETH transfer failed');
}

safeTransferETH 函数涉及两个新的知识点:合约接收以太币和地址类型上成员函数 call 的用法,本节不展开。

地址还有3个底层方法,将在地址底层调用 一节中介绍一下。

小结

提炼本节的重点:Solidity 合约程序里,使用地址类型address来表示的账号, 合约和普通地址,都可以用address 类型表示。

在地址类型上用.balance获取该地址的余额, 使用 .transfer / .send向该地址转账。

学习 Solidity 不要忘了翻看 Solidity 文档手册

------

DeCert.me 码一个未来,DeCert 让每一位开发者轻松构建自己的可信履历。

DeCert.me 由登链社区 @UpchainDAO 孵化,欢迎 Discord 频道 一起交流。

本教程来自贡献者 @Tiny熊