智能合约开发
本课程的目的是帮助读者深入了解智能合约和去中心化应用相关知识。您将了解智能合约和去中心化应用的底层技术,学习创建自己的智能合约,并探索这一快速发展的领域所具有的各种可能性。
智能合约简介
什么是智能合约?
智能合约的概念最早是在20世纪90年代由密码学先驱尼克·萨博(Nick Szabo)提出的。他将智能合约描述为使用计算机代码管理和执行各方协议的自动执行合约。萨博的开创性想法最初受到自动售货机无需中介即可接受支付并分发商品这一功能的启发。然而,直到维塔利克·布特林(Vitalik Buterin)推出区块链平台以太坊后,智能合约才在区块链和加密货币社区流行起来。
在以太坊平台上,智能合约是在满足特定条件时自动执行预定义操作的数字合约。这种自动化消除了对中间人的需求,大大提高了此过程的效率和可信度。用户通过gas费用支付交易,一旦满足预定条件,智能合约就会自动执行。作为存储在区块链中的小型计算机程序,智能合约可以自动执行其条款和条件,提高了数字协议的自动化水平和准确度。
智能合约的工作原理
智能合约遵循“if this, then that”的编程逻辑,也就是说,它们会根据预先确定的条件执行特定操作。比如说,当满足特定条件时,智能合约可能会将资金从一方的钱包转移到另一方,如插入加密货币钱包地址和网络。在创建智能合约之前,必须明确所有执行条件,因为智能合约并不具有思考能力,必须经过编程才能在特定情况下做出正确响应。智能合约的代码通常使用特定的编程语言编写,如以太坊的Solidity,Vyper或比特币脚本。但一些区块链技术公司提供了简化的智能合约创建工具,如BlockApps Strato Mercata和Remix Project。代码编写好以后便被部署在区块链上,当条件满足时启动智能合约。随后,智能合约通过多个区块链节点进行复制,具有区块链的安全性和不可变性。
智能合约一经部署便无法修改(可升级的智能合约除外);要更新智能合约,必须创建和部署一个新的合约。由于交易不可逆转且可追踪,智能合约的透明度和安全性都得到了提升。区块链网络的分布式特性意味着没有单个人能够强制合约释放资金。这使得篡改智能合约几乎不可能,从而创造了一个去信任的交易环境。
智能合约可以应用于多个行业和场景,例如众筹。众筹过程中,资金由智能合约持有,在达到筹款目标后,资金会自动转移给项目创建者;否则,资金会返还给支持者。这中方式消除了对Kickstarter等第三方平台的需求,提高了筹款过程的效率和信任度。智能合约的其他应用还包括银行、保险、邮政服务和各种去中心化平台。
智能合约的优势和用例
智能合约的多个主要优势使其成为数字世界中的一个变革性概念:
消除中间人:智能合约消除了对中间人的需求,提供了交易的成本效益。
去中心化:通过在区块链之上构建智能合约,可以创建去中心化平台,减少对单个中心化实体的依赖。
开发过程简单且经济实惠:以太坊等平台为开发人员提供了构建用于交易的应用所需的基础设施,简单方便且经济实惠。
安全性和透明度提升:由于智能合约是用代码编写的,交易不可逆转且可追踪,确保了更高的透明度和安全性。
自动执行:智能合约是自动执行的,创造了一个无需信任的交易环境。
举例来说,在以太坊等区块链上构建的智能合约可以通过在类似Uber的打车服务中消除中间人,从而使多方受益。智能合约不会向中心化实体支付费用,而是执行交易,并且支付给以太坊网络的gas费用会低于支付给Uber的费用。这样就降低了间接成本,因为数据将保存在世界各地运行以太坊网络的个人计算机上。智能合约在各行各业都有许多潜在应用,如银行、保险和邮政服务。随着以太坊和比特币等越来越多的区块链开始支持智能合约,智能合约的使用场景也会不断扩大,为数字世界带来更多创新。
以太坊和Solidity基础知识
以太坊概述
以太坊是一个基于区块链的开源分布式软件平台,是比特币最大的竞争对手。以太坊于2013年由加密货币研究员维塔利克·布特林首次提出,他建议在比特币中添加一种用于编程的脚本语言。2015年7月30日,以太坊正式启动。它的开发由在线进行的众筹支持,这是一种常见的加密货币发行方式。
以太坊的原生加密货币被称为以太币(Ether,ETH),拥有名为Solidity的特色编程语言。区块链是以太坊的底层技术,是一种分布式账本,维护着一个永久的、防篡改的记录列表。这种去中心化架构使开发人员能够创建各种去中心化应用(dApp),从平台强大的生态系统和多样性中受益。
以太坊的核心功能之一是支持智能合约。我们在第1课中已经讲到,智能合约是基于预定义条件自动执行的数字合约,实现了一个无需中间人的去信任环境,可以安全透明地执行交易。以太坊网络上的矿工曾经通过挖矿获取以太币,这些代币不仅是一种货币,还用于支付平台的使用费;在“合并(The Merge)”之后,以太坊过渡到权益证明(PoS)机制,由验证者(非矿工)验证交易并创建新区块。验证者并不挖掘以太币,而是通过参与网络获得以太币的交易费用和区块奖励。以太币仍用于支付平台上的使用费,但其发行和分配都不同于之前的工作量证明(PoW)机制。
以太坊能够托管各类dApp和智能合约,吸引了开发人员和企业的极大关注,使其成为在金融、供应链和游戏等各个行业构建创新解决方案的领先平台。随着以太坊生态系统的不断发展,以太坊仍然处于区块链技术的最前沿,不断推动创新并打造了一个充满活力的开发人员和用户社区。
Solidity编程语言简介
Solidity是一种高级、静态类型的编程语言,专门为在以太坊区块链上编写智能合约而设计。Solidity由以太坊的核心团队开发,其语法受到JavaScript的影响,对广大开发者来说更加熟悉且易于上手。Solidity是图灵完备的,开发人员可以在他们的智能合约中创建复杂的逻辑并实现各种功能。Solidity编译成以太坊虚拟机(EVM)字节码,在以太坊网络上执行。
在Solidity中,开发人员可以自定义数据结构、创建用户定义的函数并实施访问控制机制等功能。通过Solidity可以创建各种去中心化应用,包括去中心化金融(DeFi)平台、非同质化代币(NFT)、去中心化自治组织(DAO)等。
Solidity合约结构示例
Solidity合约是独立的代码片段,由变量、函数、事件和修饰符等元素组成。Solidity合约的基本结构如下:
Pragma编译指令:此行指定智能合约兼容的Solidity编译器版本。例如:
Solidity
pragma solidity ^0.8.0;
合约定义:此行定义合约及其名称。
例如:
Solidity
contract SimpleToken {
状态变量:将合约状态存储在区块链上的变量。比如,您可以存储代币的总供应量:
Solidity
uint256 public totalSupply;
函数:函数定义了合约的行为,可以被外部用户或其他合约调用。以下是一个简单的代币转移函数:
Solidity
function transfer(address recipient, uint256 amount) public {
// ... transfer logic ...
}
事件:事件用于记录合约中的特定操作,可以被外部用户或合约监控。以下是代币合约中的转移事件:
Solidity
event Transfer(address indexed from, address indexed to, uint256 value);
修饰符:修饰符用于修改函数的行为,通常用于访问控制或预设条件检查。以下是用于检查发送者是否拥有足够代币进行转移的修饰符:
JavaScript
modifier hasEnoughTokens(uint256 amount) {
require(balanceOf[msg.sender] >= amount, "Insufficient balance");
_;
}
构造函数:构造函数是一个特殊的函数,用于在部署合约时初始化合约的状态变量。例如,初始化代币的总供应量:
JavaScript
constructor(uint256 _totalSupply) {
totalSupply = _totalSupply;
balanceOf[msg.sender] = _totalSupply;
}
综上所述,一个简单的代币的Solidity合约可能如下:
TypeScript
pragma solidity ^0.8.0;
contract SimpleToken {
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
event Transfer(address indexed from, address indexed to, uint256 value);
modifier hasEnoughTokens(uint256 amount) {
require(balanceOf[msg.sender] >= amount, "Insufficient balance");
_;
}
}
编写基本智能合约
在本节中,我们将讨论如何使用Solidity编写基本的智能合约。课程将涵盖变量、函数和修饰符等基本内容,并以一个简单的代币合约示例来详细介绍实现过程。
变量、函数和修饰符
变量:Solidity包含两种类型的变量:状态变量和局部变量。状态变量永久存储在区块链上,局部变量则是临时的,只存在于函数的作用域内。
函数:函数是智能合约的构建块,用于执行特定任务,如创建交易和执行自定义逻辑。函数可以有输入参数和返回值,从而实现更大的自定义性和灵活性。
修饰符:修饰符是Solidity所独有的,用于修改函数的行为。修饰符可以提高代码的可读性和可管理性,并可用于验证传入值或有条件地执行被调用的函数。
示例1:实现一个简单的代币合约
我们用Solidity来创建一个简单的代币合约,允许用户在多个账户之间转移代币并查看任一账户的代币余额。
TypeScript
pragma solidity ^0.8.0;
contract SimpleToken {
// Declare state variables
address public owner;
mapping(address => uint256) public balances;
// Initialize the token contract
constructor(uint256 initialSupply) {
owner = msg.sender;
balances[owner] = initialSupply;
}
// Function to transfer tokens
function transfer(address to, uint256 amount) public {
require(balances[msg.sender] >= amount, "Insufficient balance");
balances[msg.sender] -= amount;
balances[to] += amount;
}
// Function to check the token balance of an account
function balanceOf(address account) public view returns (uint256) {
return balances[account];
}
}
该合约包含:
状态变量owner 和 balances,分别用于存储合约所有者的地址和所有地址的代币余额。
一个constructor构造函数,用于设置代币的初始供应并将其分配给合约所有者。
一个transfer函数,允许用户将代币转移到其他账户。它使用require语句来确保发送者有足够的代币可以转移。
一个balanceOf函数,用于返回指定帐户的代币余额。
这一基本的代币合约演示了如何在Solidity中使用变量、函数和修饰符来创建一个简单实用的智能合约。
示例2:实现简单的投票系统
TypeScript
pragma solidity ^0.8.0;
contract VotingSystem {
mapping (bytes32 => uint256) public votesReceived;
bytes32[] public candidateList;
constructor(bytes32[] memory candidateNames) {
candidateList = candidateNames;
}
function voteForCandidate(bytes32 candidate) public {
require(validCandidate(candidate), "Invalid candidate.");
votesReceived[candidate] += 1;
}
function totalVotesFor(bytes32 candidate) public view returns (uint256) {
require(validCandidate(candidate), "Invalid candidate.");
return votesReceived[candidate];
}
function validCandidate(bytes32 candidate) public view returns (bool) {
for (uint256 i = 0; i < candidateList.length; i++) {
if (candidateList[i] == candidate) {
return true;
}
}
return false;
}
}
在此示例中,VotingSystem合约允许用户通过调用voteForCandidate函数为候选人投票。合约使用voteForCandidate映射统计每个候选人收到的票数,统计基于候选人的名称,值则是票数。CandateList变量存储有效候选人列表。可以调用totalVotesFor函数来查看特定候选人获得的票数,并通过validCandidate函数检查候选人是否有效。通过这一示例,我们学习了如何使用Solidity构建具有复杂逻辑的去中心化应用。
智能合约的交互与部署
与外部合约交互
智能合约可以与以太坊区块链上的其他合约进行交互,允许它们调用函数、读取变量以及发送以太币或其他代币。促进这种交互的一种方法是使用Web3j(一个用于处理以太坊智能合约的轻量级Java库)。Web3j可以自动生成智能合约包装器代码,从而实现从JVM无缝部署智能合约并与之交互。
要使用Web3j与外部合约交互,首先需要编译智能合约并生成包装器代码。然后,您可以创建和部署智能合约或使用现有合约,从而轻松进行交易并直接调用智能合约。
事件和日志
事件对于跟踪和监控区块链上的智能合约活动至关重要。它们提供了一种发出日志的方法,这些日志可以存储起来,并在链下系统中检索。事件可以更轻松地跟踪特定的合约事件或状态变量的变化,这对于需要实时更新的dApp(去中心化应用)很有帮助。
日志是事件发出的记录,存储在区块链上,可以实现智能合约和链下系统之间的高效通信,是以太坊生态系统的重要组成部分。此外,针对日志建立索引,使应用程序可以轻松过滤和搜索特定的事件或数据点。
示例:使用Remix和MetaMask部署智能合约
第1步:打开Remix IDE
首先,在Web浏览器中打开Remix IDE(https://remix.ethereum.org/)。

第2步:创建新文件
单击IDE左上角的“+”按钮,创建一个新的空白工作区,然后单击“New File”创建一个新文件。

将文件命名为“Auction. sol”。
第3步:定义合约
将以下代码复制并粘贴到新的“Auction. sol”文件中:
TypeScript
// SPDX-License-Identifier: MIT
// Specify the Solidity version
pragma solidity ^0.8.0;
// Define the Auction contract
contract Auction {
// Declare state variables
address payable public owner; // The owner of the contract (can cancel the auction)
uint public startBlock; // The block number at which the auction starts
uint public endBlock; // The block number at which the auction ends
string public ipfsHash; // IPFS hash for the item being auctioned
bool public canceled; // Whether the auction has been canceled
bool public ended; // Whether the auction has ended
uint public highestBid; // The highest bid so far
address payable public highestBidder; // The address of the highest bidder
// Declare events
event AuctionCanceled(); // Event emitted when the auction is canceled
event HighestBidIncreased(address bidder, uint amount); // Event emitted when a new highest bid is set
event AuctionEnded(address winner, uint amount); // Event emitted when the auction ends
// Declare mapping
mapping(address => uint256) public balances;
// Constructor function
constructor() {
owner = payable(msg.sender); // Set the owner to the address that deploys the contract
startBlock = block.number; // Set the start block to the current block number
endBlock = startBlock + 40320; // Set the end block to 1 week (40320 blocks) after the start block
ipfsHash = ""; // Initialize the IPFS hash to an empty string
}
// Function to place a bid
function placeBid() public payable {
require(block.number >= startBlock && block.number <= endBlock, "Auction is not active."); // Check that the auction is active
require(msg.value > highestBid, "There is already a higher bid."); // Check that the new bid is higher than the current highest bid
require(!canceled, "Auction is canceled."); // Check that the auction has not been canceled
// If there was a previous highest bidder, add their bid amount to their balance
if (highestBidder != address(0)) {
balances[highestBidder] += highestBid;
}
// Set the new highest bid and bidder
highestBid = msg.value;
highestBidder = payable(msg.sender);
// Emit an event to signal a new highest bid has been set
emit HighestBidIncreased(msg.sender, msg.value);
}
// Function to cancel the auction
function cancelAuction() public {
require(msg.sender == owner, "Only the owner can cancel the auction."); // Check that the sender is the owner
require(!ended, "Auction has already ended."); // Check that the auction has not already ended
// Set the canceled flag and emit an event to signal the auction has been canceled
canceled = true;
emit AuctionCanceled();
}
// Function to end the auction
function endAuction() public {
require(block.number > endBlock, "Auction is not over yet."); // Check that the auction is over
require(!canceled, "Auction is canceled."); // Check that the auction has not been canceled
require(!ended, "Auction has already ended."); // Check that the auction has not already ended
// Set the ended flag and emit an event to signal the auction has ended
ended = true;
emit AuctionEnded(highestBidder, highestBid);
// Transfer the highest bid amount to the owner
owner.transfer(highestBid);
// Ifthere was a previous highest bidder, add their bid amount to their balance
if (highestBidder != address(0)) {
balances[highestBidder] += highestBid;
}
}
// Function to set the IPFS hash for the item being auctioned
function setIpfsHash(string memory hash) public {
require(msg.sender == owner, "Only the owner can set the IPFS hash."); // Check that the sender is the owner
ipfsHash = hash; // Set the IPFS hash to the provided value
}
}

本段代码是一个拍卖合约,允许用户对物品进行竞标,并在指定期限后结束拍卖。该合约还有一个取消拍卖的函数和为正在拍卖的物品设置IPFS哈希值的函数。
第4步:编译合约

单击左侧菜单中的“Solidity Compiler”选项卡。在“Compile Auction.sol”下,单击“Compile”按钮。合约编译成功后,文件资源管理器(FILE EXPLORER)中可以看到“Auction.sol”旁会显示一个绿色的勾号。
第5步:部署合约

点击左侧菜单中的“Deploy & run transactions”选项卡。在“Environment”下,选择“Injected Web3”。在“Contract”下,选择“Auction”作为要部署的合约。单击“Deploy”按钮。
第6步:与合约进行交互
合约部署完成后,您可以使用合约中定义的各种函数与之交互。例如,您可以调用place eBid()函数对物品进行竞标。
通过使用Remix和MetaMask,您可以轻松地在以太坊网络上部署智能合约并与之交互,从而在用户友好的环境中实现去中心化应用的开发和测试。
安全和实际应用
常见的智能合约漏洞
与任何软件一样,智能合约也存在各种安全风险。一些常见的漏洞包括:
重入攻击:这种攻击发生在外部合约操纵控制流时,允许它在第一次调用完成之前再次调用原始合约。这可能导致意外行为的发生,例如臭名昭著的DAO黑客攻击。
整数溢出/下溢:在数学运算中,当变量的值超过最大值或小于最小值时,导致变量的值回到意外的值范围内,便会出现这些漏洞。
未检查的外部调用:外部调用可能因为gas不足或合约异常等原因而失败。未检查外部调用的返回值可能会导致意外行为发生。
访问控制漏洞:不适当的访问控制机制可能允许未经授权的用户修改状态变量或执行关键函数。
最佳安全实践
为了降低风险并确保智能合约的安全性,请遵循以下要点:
进行详尽的测试和审计:对智能合约进行全面的测试,包括边缘情况和潜在的攻击向量。可以聘请第三方审计人员来审查代码并识别漏洞。
实施适当的访问控制:使用修饰符(如onlyOwner修饰符)来限制对敏感函数和状态变量的访问。
使用成熟的模式和库:使用经过验证的设计模式和库,如OpenZeppelin,以降低发生漏洞的可能性。
保持合约简单和模块化:编写易于理解和维护的简单且模块化的代码,有助于降低出现错误的可能性,且更容易发现漏洞。
监控并响应安全事件:设置监控和警报系统来检测潜在的安全事件,并准备好快速有效地应对任何违规行为。
若想了解更多关于如何提高安全性的知识,请学习我们的课程:数字资产安全:常见手法、预防方式、历史黑客事件。本课程涵盖加密货币安全以及防止欺诈活动和网络钓鱼攻击等相关内容,有助于您更好地了解加密货币历史上的黑客事件。
去中心化金融(DeFi)、非同质化代币(NFT)和去中心化自治组织(DAO)
智能合约为各行各业的创新应用奠定了基础,其中有三种应用尤其需要关注:
去中心化金融(DeFi):DeFi平台利用智能合约创建去中心化金融服务,如借贷、资产管理和去中心化交易所。DeFi平台旨在通过消除中间人和降低准入门槛实现金融民主化。
非同质化代币(NFT):NFT是代表数字或实物资产的独特且不可分割的代币,如艺术品、收藏品和虚拟房地产。智能合约支持在区块链网络上创建、转让和管理NFT,提供了所有权证明、出处和安全交易。
去中心化自治组织(DAO):DAO是由智能合约而非传统管理机构管理的组织。DAO成员可以对组织的规则和运营提议、投票和实施变更,从而实现去中心化决策和更高的透明度。
在了解最佳安全实践并探索实际应用后,您便可以利用智能合约来创建创新且安全的去中心化应用。
要点
智能合约漏洞:重入攻击、整数溢出和下溢、未经检查的外部调用以及访问控制漏洞是开发智能合约时需要注意的常见风险。
最佳安全实践:要降低智能合约风险并确保其安全性,我们需要进行全面的测试和审计、正确实施访问控制、使用成熟的设计模式和库、保持合约简单和模块化,以及监控和响应安全事件等。
实际应用:智能合约为去中心化金融(DeFi)、非同质化代币(NFT)和去中心化自治组织(DAO)等创新应用打下了坚实基础,革新了各个行业,并实现了去中心化的决策和安全交易。
结语:
关键概念回顾
Solidity编程:我们探讨了Solidity的基础知识,它是一种在以太坊平台上编写智能合约的常用编程语言。我们针对其语法、数据类型、函数和修饰符进行了介绍。
编写基本的智能合约:我们研究了简单智能合约的结构和组成部分,包括变量、函数和修饰符,并演示了如何实现基本的代币合约。
智能合约的交互和部署:课程介绍了如何与外部合约交互,如何使用事件和日志,以及如何使用Remix和MetaMask部署智能合约。
安全性和实际应用:我们深入探讨了安全在智能合约中的重要性,并介绍了几种常见的漏洞,保护智能合约安全的最佳方法,以及DeFi、NFT和DAO等实际应用。
如何成为一名优秀的智能合约开发人员
获得实践经验:建立您开发智能合约的技能和信心,在测试网络上创建和部署您自己的智能合约,并尝试不同的应用场景和平台。
加入社区:积极参与智能合约开发人员社区,包括参加论坛、参加聚会、网络研讨会和其他相关会议以及在开源项目中进行合作等。
持续关注行业动态:通过关注行业新闻、博客、播客和研究论文等方式,了解区块链和智能合约领域的最新趋势、工具和平台。
保持学习:学习设计良好的智能合约,并汲取其他开发人员的经验教训。分析他们的代码,了解他们的设计理念,并从他们的成功和错误中吸取经验。
扩展知识面:继续进行相关话题的学习,例如其他区块链平台、共识机制、去中心化应用的开发和智能合约的新兴用例。
通过遵循以上实践和持续不断的学习,您一定能成为一名技术娴熟的智能合约开发人员。