前言
参考:
《区块链技术与实践》—— 机械工业出版社
https://solidity-cn.readthedocs.io/zh/develop/introduction-to-smart-contracts.html
https://github.com/chaseSpace/learn_blockchain/blob/main/smart_contract.md
简介
一种在区块链上存储的、无须中介、自我验证、自动执行合约条款的计算机交易协议。一旦协议参与方达到执行协议的条件,计算机或计算机网络便会自动执行协议并输出相应的结果
例如:打麻将,这种活动无需参与方之外的力量参与就能按照规则自动进行,什么时候判定胜利,什么时候结束牌局,这些都是规则自己需要考虑的(
架构
合约模型(自底向上):基础设施层、合约层、运维层、智能层、表现层和应用层
- 基础设施层:封装了分布式账本、共识机制、激励机制、开发环境等基础设施
- 合约层:封装了静态的合约数据,包括合约各方达成一致的合约文本、合约代码、符合情景的响应规则和合约创建者指定的合约与外界以及合约之间的交互准则
- 运维层:封装了一系列对合约层中静态合约数据的动态操作
- 智能层:封装各类智能算法
- 表现层:封装了智能合约在实际运用中的各类具体表现形式
- 应用层:封装了智能合约及其表现形式的具体应用领域
运行机制
分类
脚本型智能合约:比特币
图灵完备型智能合约:以太坊和 Hyperledger Fabric 中的智能合约,图灵完备是指能用编程语言模拟任何图灵机,以太坊主要使用 Solidity 和 Serpent 两种智能合约开发语言
可验证合约型智能合约:Kadena中的智能合约
技术特征
去信任化
自动性
防篡改
可追溯
开发
以 Solidity 为例
pragma solidity ^0.4.0;
contract SimpleStorage {
uint storedData;
function set(uint x) public {
storedData = x;
}
function get() public view returns (uint) {
return storedData;
}
}
第一行代码:告诉大家源代码使用Solidity版本0.4.0写的(运行版本 < 0.5.0)。这是为了确保合约不会在新的编译器版本中突然行为异常。
pragma
:告知编译器如何处理源代码的指令的(例如,C/C++中的 pragma once ,让所在的文件在一个单独的编译中只被包含一次,类似于php中的include_once
)
Solidity中合约的含义就是一组代码(它的 函数 )和数据(它的 状态 ),它们位于以太坊区块链的一个特定地址上。
uint storedData
:声明一个类型为 uint
(256位无符号整数)的状态变量,叫做 storedData
。 可以认为它是数据库里的一个位置,可以通过调用管理数据库代码的函数进行查询和变更。
对于以太坊来说,上述的合约就是拥有合约(owning contract)。在这种情况下,函数 set
和 get
可以用来变更或取出变量的值。
Hello World
参考:https://juejin.cn/post/7046041881913851912
在线IDE:https://remix.ethereum.org
对应的文档:https://remix-ide.readthedocs.io/zh-cn/latest/
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.12 <0.9.0;
contract HelloWorld {
/**
* @dev Prints Hello World string
*/
function print() public pure returns (string memory) {
return "Hello World!";
}
}
编译
然后部署合约,deploy即可
此时就成功部署了
这是对应的选项:
然后我们打开刚才部署成功的合约地址,点击执行print函数
成功输出hello world
子货币
下面的合约实现了一个最简单的加密货币:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract Coin {
// 关键字“public”让这些变量可以从外部读取
address public minter;
mapping (address => uint) public balances;
// 轻客户端可以通过事件针对变化作出高效的反应
event Sent(address from, address to, uint amount);
// 这是构造函数,只有当合约创建时运行
constructor() {
minter = msg.sender;
}
function mint(address receiver, uint amount) public {
if (msg.sender != minter) return;
balances[receiver] += amount;
}
function send(address receiver, uint amount) public {
if (balances[msg.sender] < amount) return;
balances[msg.sender] -= amount;
balances[receiver] += amount;
emit Sent(msg.sender, receiver, amount);
}
}
注意,只有创建合约的人才能使货币无中生有,任何人都可以给其他人转币,不需要注册用户名和密码,所需要的只是以太坊密钥对
address public minter
:这一行声明了一个可以被公开访问的 address
类型的状态变量。 address
类型是一个160位的值,且不允许任何算数操作,这种类型适合存储合约地址或外部人员的密钥对
public
:自动生成一个函数,允许你在这个合约之外访问这个状态变量的当前值,如果没有这个关键字,其他的合约则没有办法访问这个变量
mapping (address => uint) public balances
:创建一个公共状态变量,该类型将address映射为无符号整数
event Sent(address from, address to, uint amount)
:声明了一个所谓的” 事件(event)”,它会在 send
函数的最后一行被发出,其中所有的事件都包含了 from
, to
和 amount
三个参数
特殊函数 Coin
是在创建合约期间运行的构造函数,不能在事后调用。 它永久存储创建合约的人的地址: msg
,这里是全局变量,其中包含一些允许访问区块链的属性。 msg.sender
始终是当前(外部)函数调用的来源地址。
最后,真正被用户或其他合约所调用的,以完成本合约功能的方法是 mint
和 send
。 如果 mint
被合约创建者外的其他人调用则什么也不会发生。 另一方面, send
函数可被任何人用于向他人发送币 (当然,前提是发送者拥有这些币)
测试:
给两个节点设置币
传入参数receiver: 0x1234567890123456789012345678901234567890
和amount: 1
receiver: 0x1234567890123456789012345678901234567891
和amount: 256
现在给0x1234567890123456789012345678901234567891
发送2个币(我们是minter 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
,我这里已经设置了有448个币)
balances 分别看一下0x1234567890123456789012345678901234567891
和0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
可以看到前者多了2个币,后者少了2个币
那么,如果我们往0x1234567890123456789012345678901234567891
节点添加了超过446个币会发生什么?
此时不会有输出,即执行失败