智能合约
以太坊的智能合约是运行在 以太坊虚拟机(EVM, Ethereum Virtual Machine) 上的代码,EVM是智能合约的沙盒,合约存储在以太坊的区块链上,并被编译成 EVM字节码 。EVM字节码是一种低级的面向栈的语言,类似于汇编语言,EVM字节码可以通过以太坊虚拟机执行。
Solidity
Solidity 是一种面向合约的、为实现智能合约而创建的高级编程语言,它的语法类似于JavaScript和Python,可以编译成EVM字节码,然后在以太坊虚拟机上执行。
下面的合约是一个最简单的加密货币合约
1 | pragma solidity ^0.4.21; |
接下来对合约里面的一些代码进行解读
1 | address public minter; |
这一行声明了一个可以被公开访问的
address
类型的状态变量。这个类型适合存储合约地址或外部人员的密钥对。关键字
public
自动生成一个函数,可以让你在合约之外读取这个状态变量的当前值。由编译器生成的函数代码大致如下:
1 | function minter() returns (address) { return minter; } |
1 | mapping (address => uint) public balances; |
这一行创建一个公共状态变量,该类型将
address
映射为无符号整数。 Mappings 可以看作是一个哈希表,它会执行虚拟初始化,使所有可能存在的键都映射到一个字节表示全为零的值。但是,这种类比并不太恰当,因为它既不能获得映射的所有键的列表,也不能获得所有值的列表。 因此,要么记住你添加到
mapping
中的数据(使用列表或更高级的数据类型会更好),要么在不需要键列表或值列表的上下文中使用它,就如本例。
public
关键字创建的getter
函数getter function
大致如下,通过该函数可以轻松地查询到账户的余额:
1 | function balances(address _account) public view returns (uint) { |
1 | event Sent(address from, address to, uint amount); |
这一行声明了一个
event
,它会在函数send
中被触发。客户端可以通过监听这些事件针对变化作出高效的反应,一旦它被发出,监听该事件的 lister 都将收到通知。可以使用如下代码监听该事件:
1 | Coin.Sent().watch({}, '', function(error, result) { |
1 | function Coin() public { |
特殊函数
Coin
是合约的构造函数,只有当合约创建时运行。构造函数会在合约创建时执行,且只执行一次。构造函数的函数名必须与合约名相同,且不能有返回值。它永远存储创建合约的人的地址,全局变量
msg
(以及tx
和 **block
**),包含一些允许访问区块链的属性。
msg.sender
始终指向当前函数的调用者的地址,而tx.origin
始终指向事务的发起者。
可见性
Solidity有几种默认的变量或函数访问域关键字: private
、 public
、 external
、 private
。对合约实例方法来讲,默认可见状态为 public
,而合约实例变量的默认可见状态为 private
。
public
:表示该函数或状态变量可以从任何地方调用,包括合同内部、外部合同、外部用户。如果不显式指定函数或状态变量的可见性,它们将被视为public。private
:表示函数或变量只能在本合约内部使用(代码层面)external
:表示函数只能从外部访问,不能被合约里的函数直接调用,但可以使用this.func()
外部调用的方式调用该函数internal
:一般用在合约继承中,父合约中被标记成 internal 状态变量或函数可供子合约进行直接访问和调用(外部无法直接获取和调用)
底层调用方式
Solidity有两种用于与其他合同进行交互的低级函数: call
、 delegatecall
它们的区别如下图所示:
call
方式会调用外部合约B的func()
函数,在外部合约上下文执行完后继续返回本合约A上下文执行。call
返回一个bool值来表明外部调用成功与否delegatecall
方式相当于将外部合约B的func()
代码复制到本合约的上下文空间中执行。delegatecall
返回一个bool值来表明外部调用成功与否
此外还有
callcode
函数,它是delegatecall
之前的一个版本,现在已经弃用。它俩在msg.sender
和msg.value
的指向上有区别
回退函数 fallback()
fallback()
函数是一种特殊的函数,通常用于接收以太币或处理未知函数调用,具有以下特征:
- 三无函数:一个合同只能有一个
fallback()
函数,且没有名字、没有参数、没有返回值 - 替补函数:如果一个合同收到一个未知的函数调用,就会执行
fallback()
函数。这可用于实现一种默认行为或错误处理逻辑 - 收币函数:当合同收到以太币时,会执行
fallback()
函数,以处理接收到的以太币 - 如果合同需要接收以太币,且没有声明
receive()
函数,那么需要将fallback()
函数声明为payable
,否则会抛出异常 - 在 Solidity 0.6.0 及更高版本中,无数据的以太币转账将触发合约的
receive()
函数(如果定义了的话),而不是fallback()
函数。如果没有定义receive()
函数,则会触发fallback()
函数
交易函数
发送Ether主要有以下几个函数: call.value()()
、 send()
、 transfer()
call.value()()
1 | address.to.call.value(amountInWei)(data) |
在合约中直接发起 TX 的函数之一,相当危险
send()
1 | address.send(uint256 amount); |
通过该函数发送Ether失败时直接返回
false
send()
的目标如果是合约账户,则会尝试调用它的fallback()
函数,fallback()
函数执行失败,会直接返回false
,但只提供 2300 Gas 给fallback()
,所以可以防止重入漏洞
transfer()
1 | address.transfer(uint256 amount); |
transfer()
是一个较为安全的转币函数,该函数调用没有返回值,当发送失败时(例如,由于 gas 不足或接收函数抛出异常)会自动回滚状态,抛出异常。如果目标是一个合约账户,它会尝试执行合约的接收函数(fallback()
函数),并且只会传递 2300 Gas 用于fallback()
函数执行,这可以防止因 gas 不足而导致的转账失败。
自定义修饰符
自定义修饰符(Modifiers)用于在函数执行前和/或执行后修改函数的行为
- 定义修饰符:
1
2
3
4
5modifier ModifierName(parameters) {
// 在函数执行前的代码,如 `require(msg.sender == owner);`
_; // 占位符
// 在函数执行后的代码
}
ModifierName
是修饰符的名称,可以自定义
parameters
可选,用于传递参数给修饰符,通常用于指定某些条件
_
是占位符,表示函数的实际逻辑将在此执行。如果修饰符通过了所有条件检查,那么 _ 中的代码将在函数执行之前和之后执行
- 在函数中应用修饰符
1
2
3function functionName(parameters) public ModifierName(parameters) {
// 函数的实际逻辑
}
只需要在函数声明之前加上修饰符名称,就可以将其应用于该函数
Ethererum IDE
在线IDE:Remix
离线版Remix:Remix | github
TODO
- 整理学习Solidity常见漏洞类型
- 学习 Remix-ide