【Web3技术分享系列专题】-以太坊智能合约学习笔记1
什么是智能合约?
1.传统合约是双方或者多方共同协议做或不做某事来换取某些东西,前提是互相要信任彼此会履行义务。通常我们遵守的一些规则和法律就是一种传统合约,这种合约的执行一般需要一下中间人进行担保来确定合约的正确执行。
2.智能合约的特点是,同样是彼此协议做或不做某事,但是无须信任彼此。因为智能合约完全是代码定义的,由代码执行的,完全自动且人工无法干预。在智能合约的执行过程中,代码作为中间人来确保合约的正确执行。
什么是以太坊智能合约?
以太坊上面的智能合约是以太坊网络上的一种特殊账户,是运行在区块链上的一段代码。以太坊上面的账户一般分为两种,用户账户和智能合约账户,示例如下。
用户账户:
- 地址(账户的唯一标识)
- 余额(拥有的资金)
智能合约账户:
- 地址(账户的唯一标识)
- 余额(拥有资金和管理资金)
- 状态(所有变量及其当前变量状态)
- 代码(编译后可运行的字节码)
以太坊交易
比特币交易非常简单,它只做一件事,就是进行交易。忽略细节,这一切都归结为TO(谁收钱),FROM(谁汇款)和AMOUNT(多少钱)。 这让比特币网络中的参与者可以传递价值并存储价值。
以太坊很大的不同是其交易还有一个DATA字段。 DATA字段支持三种类型的交易:
价值传递:
- TO:收款地址
- DATA:留空或留言信息
- FROM:发出者
- AMOUNT:发多少
创建合约:
- TO:留空(触发创建合约的原因)
- DATA:包含编译为字节码的智能合约代码
- FROM:创建者
- AMOUNT:可以是零或任意以太,是想给合约的存款
调用合约:
- TO:目标合约账户地址
- DATA:包含函数名称和参数
- FROM:调用者
- AMOUNT:可以是零或任意以太,例如可以支付合约服务费用
智能合约代码
-
Solidity语言是智能合约最常用的语言,语法上与JavaScript很接近。Solidity里面的map不能直接遍历,因此一般配套一个数组来存储map里面的键值。
event
定义一个事件,emit
执行对应的事件,主要用于在执行过程中输出日志。以太坊中所有需要接收转账的合约函数必须定义为payable
,否则无法接收到转账,会导致异常。
-
一个合约如何调用另一个合约中的函数?
-
直接调用
- 如果在执行
a.foo()
过程中抛出错误,则callFooDirectly
也抛出错误,本次调用全部回滚。即A执行出错,A和B都会回滚。 - ua为执行
a.foo("call foo directly")
的返回值 - 可以通过
.gas()
和.value()
调整提供的gas数量或提供一些ETH
- 如果在执行
一个交易只有外部账户才可以发起,一个合约账户不能主动发起一个交易进行合约调用。因此上面的调用是需要一个外部账户调用合约B的callFooDirectly函数,然后callFooDirectly
函数内部调用合约A的foo函数。
-
使用address类型的
call()
函数调用
- 第一个参数被编码成4个字节,表示要调用的函数的签名
- 其他参数为被扩展到32字节,表示要调用函数的参数
- 上面的例子相当于
A(addr).foo("call foo by func call")
- 返回一个布尔值表明了被调用的函数已经执行完毕(true)或者引发了一个EVM异常(false),无法获取函数返回值。即调用失败也不会引起当前C合约的回滚。
- 也可以通过
.gas()
和.value()
调整提供的gas数量或提供一些ETH
-
代理调用
delegatecall()
- 使用方法与
call()
相同,只是不能使用.value()
- 区别在于是否切换上下文
call()
切换到被调用合约上下文中delegatecall()
只使用给定地址的代码,其他属性(存储、余额等)都取自当前合约。delegatecall
的目的是使用存储在另外一个合约中的库代码。
- 使用方法与
- fallback()函数
function() public [payable]{
......
}
fallback()
函数是一个匿名函数,即没有参数也没有返回值- 在两种情况下会被调用:
- 直接向一个合约地址转账而不加任何data,即data值为空
- 被调用的函数不存在
- 如果转账金额不是0,同样需要声明
payable
,否则会抛出异常 fallback
函数不是必须需要被定义的函数,而是作为一个兜底函数存在
- 智能合约的创建和执行
- 智能合约的代码写完后,要编译成bytecode
- 创建合约:外部账户发起一个转账交易到0x0的地址
- 转账的金额是0,但是要支付汽油费
- 合约的代码放在data域里
- 智能合约运行在EVM(Ethereum Virtual Machine,寻址位数256位)上
- 以太坊是一个交易驱动的状态机
- 调用智能合约的交易发布到区块链上后,每个矿工都会执行这个交易,从当前状态确定性地转移到下一个状态
- 智能合约的错误处理
- 智能合约中不存在自定义的try-catch结构
- 一旦遇到异常,除特殊情况外,本次执行操作全部回滚
- 可以抛出错误的语句:
assert()
:如果条件不满足就抛出——用于内部错误require()
:如果条件不满足就抛掉——用户输入或者外部组件引起的错误revert()
:终止运行并回滚状态变动-无条件抛出异常
- 智能合约可以获得的区块信息
block.blockhash(uint blockNumber) return (bytes32)
:给定区块的哈希-仅对最近的256个区块有效而不包括当前区块block.coinbase(address)
:挖出当前区块的矿工地址block.difficulty(uint)
:当前区块难度block.gaslimit(uint)
:当前区块gas限额block.number(uint)
:当前区块号block.timestamp(uint)
:自unix epoch起始当前区块以秒计的时间戳
- 智能合约可以获得的调用信息
msg.data(bytes)
:完整的calldatamsg.gas(uint)
:剩余gasmsg.sender(address)
:消息发送者(当前调用)msg.sig(bytes4)
:calldata的前4个字节(也就是函数标识符)msg.value(uint)
:随消息发送的wei数量now(uint)
:目前区块时间戳tx.gasprice(uint)
:交易的gas价格tx.origin(address)
:交易发起者
- 地址类型-所有智能合约均可显式地转换成地址类型
<address>.balance (uint256)
:以Wei为单位的地址类型的余额<address>.transfer(uint256 amount)
:当前合约向地址类型发送数量为amount的Wei,失败时抛出异常,发送2300gas的矿工费,不可调节,address是转入地址<address>.send(uint256 amount) returns (bool)
:向地址类型发送数量为amount的Wei,失败时返回false
,发送2300gas的矿工费,不可调节<address>.call(...) returns (bool)
:发出底层CALL
,失败时返回false,发送所有可用gas,不可调节<address>.callcode(...) returns (bool)
:发出底层CALLCODE
,失败时返回false,发送所有可用gas,不可调节<address>.delegatecall(...) returns (bool)
:发出底层DELEGATECALL
,失败时返回false,发送所有可用gas,不可调节
- 智能合约例子解析
- 第一版拍卖
红框处语句调用可能会失败异常,导致所有人都收不到钱,钱被锁死在合约账户中
- 第二版拍卖
**黑客合约可以在清零之前递归调用取钱的合约,达到重入攻击。**因此一般写合约需要先判断条件,再修改条件,最后才进行与其他合约的交互。
解决方法:先清零再转账
- TheDAO
重入攻击,注意条件修改的顺序位置
- 美链
参考学习网址
https://learnblockchain.cn/2018/01/04/understanding-smart-contracts
- 点赞
- 收藏
- 关注作者
评论(0)