无论风暴将我带到什么岸边,我都将以主人的身份上岸
目录
一、Solidity的单位
1. 货币Ether
2. 时间单位Time
二、地址的形成
三、以太坊的账户
1.内部账户(简称CA)
2.外部账户(简称EOA)
3.内部账户和外部账户的比较
4.判断是内部账户还是外部账户的方法
四、消息调用和余额查询
五、交易
1.公钥和私钥的区别
2.交易的小知识
六、Solidity this 和 msg.sender 、msg.value的用法
七、Solidity的转账函数
1.transfer()
2.send()
3.call()
4.transfer、send、call的区别和用法
八、Solidity 接收函数
九、Solidity 回退函数
一、Solidity的单位
1. 货币Ether
Ether的单位关键字有wei, gwei, finney, szabo, ether,换算格式如下:
- 1 ether = 1 * 10^18 wei
- 1 ether = 1 * 10^9 gwei
- 1 ether = 1 * 10^6 szabo
- 1 ether = 1* 10^3 finney
pragma solidity 0.4.20;/** * 对 比特币 Ether 的几个单位进行测试 */contract testEther {// 定义全局变量uint public balance;function testEther() public{balance = 1 ether;//1000000000000000000}function fFinney() public{balance = 1 finney; //1000000000000000}function fSzabo() public{balance = 1 szabo;//1000000000000}function fWei() public{balance = 1 wei; //1}}
2. 时间单位Time
Time的单位关键字有seconds, minutes, hours, days, weeks, years,换算格式如下:
- 1 == 1 seconds
- 1 minutes == 60 seconds
- 1 hours == 60 minutes
- 1 days == 24 hours
- 1 weeks == 7 days
years
已经在 0.5.0 版本去除了,因为闰年的原因。所以没有月和年//他们都会放在uint中,全部转换为秒单位
//变量now将返回当前的unix时间戳(自1970年1月1日以来到现在的秒数)
return now
return 1 days—> unit秒数
如果你需要进行使用这些单位进行日期计算,需要特别小心,因为不是每年都是365天,且并不是每天都有24小时,因为还有闰秒。由于无法预测闰秒,必须由外部的oracle来更新从而得到一个精确的日历库(内部实现一个日期库也是消耗gas的)。
pragma solidity 0.8.0;/** * 对 Time 单位进行测试 */contract TestTime {// 定义全局变量uint time;function testTime() public{time = 100000000;}function fSeconds() public view returns(uint){return time + 1 seconds; //100000001}function fMinutes() public view returns(uint){return time + 1 minutes; //100000060}function fHours() public view returns(uint){return time + 1 hours; //100003600}function fWeeks() public view returns(uint){return time + 1 weeks; //100604800}}
二、地址的形成
1.地址是由公钥Keccak-256单向哈希,取最后20个字节(160位)派生出来的标识符
2.在solidity中,地址类型使用address来表示
3.地址类型在以太坊中非常重要,因为以太坊的账户需要用地址来表示
4.地址占用20个字节,共160位,即以太坊的地址长度是20个字节
5.地址类型的声明 address 地址名=0x十六进制数
地址是所有合约的基础,支持的比较运算符有 = == !
三、以太坊的账户
以太坊中的两类账户:
- 内部账户:由智能合约的代码控制
- 外部账户 :由密钥控制
以太坊中账户不用申请,而实用户根据需要在钱包中生成,然后连接到以太坊
它们共用EVM中同一个地址空间 账户地址空间
无论账户是否存储代码,这两类账户对EVM来说处理方式是一样的
每个账户在EVM中都有一个键值对形式的持久化存储,其中key和value的长度都是256位
账户信息也就是地址信息
1.内部账户(简称CA)
内部账户也就是合约账户,合约地址就代表该内部账户地址
有的以太币余额,有关联代码,可通过交易或者来自其他合约的调用信息来触发代码执行
执行代码时可以操作自己的存储空间,也可以调用合约,没有私钥控制,其codeHash非空
2.外部账户(简称EOA)
外部账户:
外部账户就是非合约账户,是由第三方钱包app所创建的账户,例如metamask
有对应的以太币余额,没有关联代码,可发送交易(转币或促发合约代码),由用户私钥控制,其codeHash为空
3.内部账户和外部账户的比较
比较 | 外部账户 | 合约账户 |
---|---|---|
拥有私钥 | 是 | 否 |
codeHash内容 | 为空 | 非空 |
主动发起交易 | 是 | 否,只能被动发起交易 |
拥有余额 | 是 | 是 |
地址长度 | 20字节 | 20字节 |
4.判断是内部账户还是外部账户的方法
采用extcodesize来判断,它可以获取地址关联代码长度
通过判断账户关联代码长度
合约地址大于0 外部账户地址为0
//extcodesize获取地址关联代码长度 合约地址大于0 外部账户地址为0 contract IsCadd {function isContract(address addr) returns (bool) {uint size;assembly { size := extcodesize(addr) }return size > 0;}}
四、消息调用和余额查询
合约可以通过消息调用的方式来调用其他合约或者发送以太币到非合约账户
balance属性用于查询账户余额
格式:
地址名.balance
五、交易
1.公钥和私钥的区别
公钥和私钥的作用和区别
很多人搞不清楚公钥和私钥的区别,不知道用哪个加密、用哪个加签,本文在这里做一个简单的总结。一、先上结论:
公钥加密、私钥解密。
私钥加签、公钥验签。
二、加解密
加密的目的是保证信息的保密传输,使只有具备资格的一方才能解密。
公钥和私钥是一对密钥,公钥是公开的,私钥是不公开的。假设A有一对公私钥,当B或者其他人用A的公钥对某信息进行加密,那么只有具备私钥的A可以解密该信息。
三、加验签
加签的目的是让收到消息的一方确认该消息是由特定方发送的。
因为私钥只存在于A,也只有A才能对消息进行加签,这个过程就叫签名。你可以理解为老干妈的私章只有老干妈才有,腾讯拿到这个章盖过的合同后,需要先确认这个合同是老干妈发过来的,但为什么腾讯还是被骗了,那是因为腾讯没有老干妈的公钥,没有对加签过的合同进行验签。公钥的作用就是验签,验证了就能知道消息的真伪。
2.交易的小知识
1.签名的数据包,可以包含二进制数据负载和以太币,由外部账户发送到另一个账户的消息
2.既可以是外部账户的交易也可以是内部账户的交易
3.交易一经创建,每笔交易都需要消耗gas,目的是限制执行交易所需的工作量和为交易支付手续费。EVM执行交易时,gas将按特定规则逐渐耗尽
4.gas price是交易发送者设置的一个值,发送者账户需要预支付手续费=gas_price*gas。如果交易后还有剩余,gas会原路返还
六、Solidity this 和 msg.sender 、msg.value的用法
Solidity 中 this 代表合约对象本身,可以通过
address(this)
获取合约地址。合约地址与合约创建者地址、合约调用者地址并不相同。Solidity 中
msg.sender
代表合约调用者地址。一个智能合约既可以被合约创建者调用,也可以被其它人调用。合约创建者,即合约拥有者,也就是指合约部署者,它的地址可以在合约的
constructor()
中,通过msg.sender
获得,因为合约在部署的时候会首先调用constructor()
。Solidity中mas.value代表调用者输入的值,通常用来让调用者自定义转账数量
1. 范例
// SPDX-License-Identifier: MITpragma solidity ^0.8.0;contract SolidityTest {address public owner;event log(address);constructor(){owner = msg.sender;emit log(msg.sender);emit log(address(this));}}
owner 被赋值为合约部署者的地址。
log(msg.sender) 在日志中输出了合约部署者的地址。
log(address(this)) 在日志中输出了合约地址。
查看合约在部署时的日志结果:
[{"from": "0xE3Ca443c9fd7AF40A2B5a95d43207E763e56005F","topic": "0x2c2ecbc2212ac38c2f9ec89aa5fcef7f532a5db24dbf7cad1f48bc82843b7428","event": "log","args": {// 合约部署者的地址"0": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4"}},{"from": "0xE3Ca443c9fd7AF40A2B5a95d43207E763e56005F","topic": "0x2c2ecbc2212ac38c2f9ec89aa5fcef7f532a5db24dbf7cad1f48bc82843b7428","event": "log","args": {// 合约地址"0": "0xE3Ca443c9fd7AF40A2B5a95d43207E763e56005F"}}]
七、Solidity的转账函数
使用 Solidity 智能合约转账可以使用 transfer 函数等其他转账函数。智能合约里面需要有一定的以太,不然合约将无法给调用者发送以太,可以在创建合约时给合约发送一定的以太来测试。
转账双方可以是:
1.外部账户向外部账户转账
2.内部账户向内部账户转账
3.外部向内部,内部向外部账户转账
具有转账功能的智能合约的 constructor 必须显式的指定为 payable。,具有接收和转账功能的函数也需要加上payable
谁给谁转就消耗谁的余额,谁调用函数就消耗谁的gas
注意事项:非payble类型地址不能
1.transfer()
transfer()方法
接收者地址.transfer(数量)
如果当前合约的余额不够大或者 Ether转账被接收账户拒绝,转账功能将失败。接收方智能合约应定义回退函数,否则转账调用将引发错误。transfer函数在失败时恢复。另外它被硬编码以防止重入攻击(这句话不是很能理解)。
// SPDX-License-Identifier: MITpragma solidity ^0.7.0;contractcs{constructor() payable{}function getETH() public{require(address(this).balance>=1 ether,"no money");address payable _owner = msg.sender;_owner.transfer(1 ether);}fallback() external{}receive() payable external{}}
2.send()
格式:
接收者地址.send(数量)
Send是和Transfer具有同等功能的低级api。如果执行失败,当前合约不会因为异常而停止,但会返回false。
function send(address payable _to) public payable {bool isSend = _to.send(msg.value);require(isSend, "Send fail");}
3.call()
格式:
接收者地址.call(数量)
这是将 ETH 发送到智能合约的推荐方式。空参数触发接收地址的回退功能(fallback function)
function calls(address payable _to) public payable { (bool isSuccess, /* memory data */ ) = _to.call{value: msg.value}("");require(isSuccess, "Failure! Ether not send.");}
使用call,还可以触发合约中定义的其他功能,并发送固定数量的gas来执行该功能。交易状态作为布尔值发送,返回值在数据变量(bytes memory data)中发送。
更具体使用的格式如下:
(bool sent, bytes memory data) = _to.call{gas :10000, value: msg.value}(“func_signature(uint256 args)”);
2019年,solidity官方已经弃用了send和transfer,推荐call方法进行转账操作,但还是要小心使用官方给出了的警告
4.transfer、send、call的区别和用法
1. transfer
- 如果异常会转账失败,抛出异常(等价于require(send()))(合约地址转账)
- 有gas限制,最大2300
- 函数原型:.transfer(uint256 amount)
2. send
- 如果异常会转账失败,仅会返回false,不会终止执行(合约地址转账)
- 有gas限制,最大2300
- 函数原型:.send(uint256 amount) returns (bool)
3. call
- 如果异常会转账失败,仅会返回false,不会终止执行(调用合约的方法并转账)
- 没有gas限制
- .call(bytes memory) returns (bool, bytes memory)
共同点
- addr.transfer(1 ether)、addr.send(1 ether)、addr.call.value(1 ether)的接收方都是addr。
- 如果使用addr.transfer(1 ether)、addr.send(1 ether),addr合约中必须增加fallback回退函数!
- 如果使用addr.call.value(1 ether),那么被调用的方法必须添加payable修饰符,否则转账失败!
// SPDX-License-Identifier: MITpragma solidity ^0.7.0;contractcs{constructor() payable{}function getETH() public returns(bool) {address payable _owner = msg.sender;return(_owner.send(1 ether));}# 如果使用transfer或send函数必须添加fallback回退函数fallback() external{}receive() payable external{}}}
八、Solidity 接收函数
solidity 接收函数 receive 没有参数、没有返回值。
solidity 向合约转账,发送 Eth,就会执行 receive 函数。
如果没有定义接收函数 receive,就会执行 fallback 函数。
向合约转账
// SPDX-License-Identifier: MITpragma solidity ^0.8.0;contract Fallback { event eventFallback(string); fallback() external payable {emit eventFallback("fallbak"); } receive() external payable {emit eventFallback("receive"); } //查看合约账户余额 function getBalance() external view returns(uint) {return address(this).balance; }}
我们向合约 Fallback 发送一笔 123 wei 的交易,查看日志:
[{"from": "0xd457540c3f08f7F759206B5eA9a4cBa321dE60DC","topic": "0x39684f4c14ee0aafaa34ed83629676cd0fbe71653659c3353ef0c33f630e7eab","event": "eventFallback","args": {"0": "receive"}}]我们调用合约 Fallback 的 getBalance 方法,查看合约地址的余额为 123 wei。
九、Solidity 回退函数
solidity 回退函数 fallback 没有参数、没有返回值。
solidity 回退函数在两种情况被调用:
- 向合约转账,发送 Eth,就会执行Fallback函数
- 如果请求的合约方法不存在,就会执行Fallback函数
请求的合约方法不存在
// SPDX-License-Identifier: MITpragma solidity ^0.8.0;contract Fallback { event eventFallback(string); fallback() external payable {emit eventFallback("fallbak"); }}contract SoldityTest { // 外部合约 address private fb; constructor(address addr) {fb = addr; } function callFallback() external view returns(string memory) { // 调用合约 Fallback 不存在的方法 echo()bytes4 methodId = bytes4(keccak256("echo()")); // 调用合约(bool success,bytes memory data) = fb.staticcall(abi.encodeWithSelector(methodId));if(success){ return abi.decode(data,(string));} else { return "error";} }}
我们先部署合约 Fallback,再使用 Fallback 的地址来部署 SoldityTest,调用 Fallback 方法 echo 方法,就会触发 Fallback 的 fallback 方法。
十、receive 和 fallback 调用流程
向一个合约发送 Eth,何时调用 receive 或者 fallback 呢?下面是两者的调用流程。
发送 Eth|msg.data 是否为空/\是否 / \ 是否定义了receive fallback/\ 是 否/\ receive fallback