在以太坊上编写自己的加密货币:构建 ERC-20 代币
以太坊及整个区块链生态系统,广泛采用 ERC20 代币开展各类商业活动。任何人都有机会创建代币,并将其作为加密货币使用。在本教程中,我们将借助 Python 和 Solidity 来部署 ERC20 代币。即便你对这些语言并不熟悉,也能轻松跟上教程的步伐。
ERC-20 代币标准
什么是代币?
在以太坊的世界里,代币的用途极为广泛,几乎可以代表任何事物:
- 在线平台信誉积分:例如在特定社区中,用户通过完成任务或参与互动积累的积分。
- 游戏角色技能:游戏内角色所具备的独特技能,以代币形式体现其价值。
- 金融资产:类似公司股份的资产,在区块链上实现数字化流转。
- 法定货币:如与美元等法定货币等值的代币。
- 实物资产:像一盎司黄金这样的实物,通过代币化实现便捷交易。
- 更多可能:代币的潜力无限,还可代表其他各种价值。
以太坊的这一强大特性,需要强有力的标准来规范,而 ERC-20 标准正是为此而生。它为开发者构建可与其他产品和服务互操作的代币应用程序提供了规范。此外,ERC-20 标准还为以太币赋予了附加功能。
什么是 ERC-20?
ERC-20 定义了同质化代币的标准。所谓同质化,即每个代币在类型和价值上完全相同。例如,一个 ERC-20 代币就如同以太币一样,任何时候,一个代币的价值与其他代币始终保持一致。
什么是 ERC-20 代币?
以太坊区块链赋予用户创建自有加密货币或代币的能力,这些代币可使用以太坊区块链的原生加密货币——以太币进行购买。ERC-20 仅仅是一个标准,它明确了这些代币的行为规范,确保它们能够与其他平台(如加密货币交易所)实现无缝兼容。
要理解 ERC-20 代币的运作机制,我们首先需要了解以太坊区块链的运作原理。
ERC20 的全称为“以太坊征求意见稿 20”。征求意见稿是社交团体对想法进行同行评审的一种方式。以太坊通过鼓励人们提出想法并征求他人意见,持续优化其生态系统。这与以太坊改进提案(EIP)略有不同,尽管两者存在差异,但有时人们会互换使用这两个术语。
当人们提及 ERC20 时,实际上指的是第 20 个以太坊征求意见稿,它由 Fabian Vogelsteller 和 Vitalik Buterin 共同创建。
此次征求意见稿为在以太坊平台上创建“代币”提供了标准化途径。所有代币都必须遵循这一标准,以便平台和工程师能够轻松使用,而无需进行重复开发。
想象一下,如果每个代币都采用独特的功能来实现代币转移,那么围绕这些代币构建协议将变得极为复杂。而 ERC20 代币则如同可互换的部件,通过 Solidity 实现的函数实现统一操作。
这些 ERC20 代币遵循相同的模式,具备相同的功能列表,这些功能在本质上是一致的。其中一些关键功能包括:
- transfer:实现代币在所有者之间的转移。
- balanceOf:用于查看特定地址持有的代币数量。
- transferFrom:允许非所有者将代币从一个地址转移到另一个地址。
- approve:批准合约调用 transferFrom 函数。
在最简单的形式上,ERC20 代币是一种合约,用于追踪区块链上每个地址所拥有的某个价值单位的数量。它们通过 Solidity 中的 mappings 来实现这一功能。
简单来说,ERC20 是代表代币的智能合约。
ERC20 代币的典型示例
ERC20 代币的一些典型示例包括 LINK、AAVE、USDT 和 DAI。你会发现,某些代币(如 LINK 代币)实际上是 ERC20 的更高级形式。特别是 LINK,它是一种 ERC677 代币,增加了与预言机配合使用的额外功能,但它仍然向后兼容 ERC20 代币,并具备 ERC20 的所有功能。
另一个流行的代币标准是 ERC777,它相较于 ERC20 有一些显著的质量改进,并且同样向后兼容 ERC20。
制作 ERC20 代币的用途
ERC20 代币的用途多种多样,以下列举一些最常见的用途:
1. 治理
治理代币允许用户对协议的未来发展方向进行投票,并持有相应的权益。例如,UNI、AAVE 和 CRV 就是典型的治理代币。这些代币可以“质押”到平台中,用于投票并提出新的发展方向,供协议参考和决策。
2. 确保网络安全
网络或协议通常会跨多条区块链运行,而整个协议的安全性不应仅仅依赖于单一的底层区块链。协议通常需要与对协议更有意义的底层资产进行交易,以将资产与网络无关的其他资产的市场波动相隔离。
如果协议依赖于底层链,那么当底层市场波动性恶化时,网络的安全性可能会受到威胁。
3. 合成资产和稳定币
如果你拥有某种喂价机制,就可以轻松地创建带有抵押品支持的合成资产。这是一种接触和交易区块链生态系统之外资产的有效方式。这正是 Synthetix 协议的工作原理,它将 Chainlink 喂价机制与 SNX 质押的抵押品相结合,让 DeFi(去中心化金融)投资者能够接触到传统金融世界。
同样地,稳定币也是一种合成资产,只不过它们代表的是像美元这样的“稳定”资产。Tether、USDC 和 Dai 就是稳定币的典型例子。
4. 其他用途
在思考为什么要创建 ERC20 代币时,创造力是唯一的限制。我们已经看到一些协议使用 ERC20 代币作为底层抵押资产进行质押,为使用协议的用户提供奖励(参见收益耕作),围绕代币创建经济体系,以及实现更多创新应用。
ERC-20 规范深度解析:智能合约接口与函数实现
ERC-20 规范作为以太坊生态中最广泛采用的代币标准,其核心在于定义了智能合约必须遵循的接口规范。这一标准不仅明确了智能合约的基本结构,还详细规定了代币合约必须实现的功能类型,同时提供了一系列虽非强制但极具实用价值的推荐功能。尤为关键的是,ERC-20 规范强制要求代币合约实现特定事件,如 transfer 事件,这些事件如同智能合约的“广播系统”,允许外部应用订阅并响应代币交易等关键行为。通过这一机制,开发者可以轻松构建出能够实时感知代币交易的应用,极大提升了区块链应用的交互性和实用性。
ERC-20 核心接口与事件机制
ERC-20 规范的核心在于其定义的接口和事件。智能合约必须实现这些接口以确保与钱包、交易所等外部系统的兼容性。同时,规范要求合约在特定操作发生时触发相应事件,如 transfer 事件在代币转移时触发。这种设计模式不仅提高了系统的透明度,还为外部监控和审计提供了便利。
方法
function name() public view returns (string)
function symbol() public view returns (string)
function decimals() public view returns (uint8)
function totalSupply() public view returns (uint256)
function balanceOf(address _owner) public view returns (uint256 balance)
function transfer(address _to, uint256 _value) public returns (bool success)
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)
function approve(address _spender, uint256 _value) public returns (bool success)
function allowance(address _owner, address _spender) public view returns (uint256 remaining)
事件
event Transfer(address indexed _from, address indexed _to, uint256 _value)
event Approval(address indexed _owner, address indexed _spender, uint256 _value)
transfer 函数:代币转移的核心实现
transfer 函数是 ERC-20 代币合约中最为关键的函数之一,它负责管理用户之间的代币转移。以下是一个符合 ERC-20 标准的 transfer 函数示例实现,该函数详细展示了代币转移的全过程:
contract ERC20Token {
// 存储每个地址的代币余额
mapping(address => uint256) public balanceOf;
// Transfer 事件定义
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev 将代币从调用者的账户转移到指定地址
* @param _to 接收代币的地址
* @param _value 要转移的代币数量
* @return 成功返回 true
*/
function transfer(address _to, uint256 _value) public returns (bool success) {
// 检查发送者是否有足够的余额
require(balanceOf[msg.sender] >= _value, "Insufficient balance");
// 更新余额:从发送者账户扣除,向接收者账户增加
balanceOf[msg.sender] -= _value;
balanceOf[_to] += _value;
// 触发 Transfer 事件,通知外部系统代币已转移
emit Transfer(msg.sender, _to, _value);
return true;
}
}
transfer 函数实现要点解析
- 存在性验证:函数必须存在于合约中,这是 ERC-20 标准的基本要求。
- 参数正确性:函数接受两个参数——接收者地址
_to和转移数量_value,这两个参数的类型和顺序必须严格符合规范。 - 余额检查:在执行转移前,函数会检查发送者的余额是否足够。如果余额不足,交易将被回滚,并返回错误信息“Insufficient balance”。
- 余额更新:函数通过修改
balanceOf映射来更新发送者和接收者的余额,确保账本的准确性。 - 事件触发:函数在完成余额更新后,会触发
Transfer事件。这一事件可以被外部系统(如钱包、交易所)订阅,从而实现实时监控和通知功能。 - 返回值:函数返回
true表示转移成功,这是 ERC-20 标准规定的返回值。
通过这一精心设计的函数实现,ERC-20 代币合约能够确保代币转移的安全、可靠和透明,为以太坊生态中的代币经济提供了坚实的基础。
如何创建 ERC20
我们只需要合约的应用程序二进制接口 (ABI) 来创造一个 ERC-20 代币界面。 下面我们将使用一个简化的应用程序二进制接口,让例子变得更为简单。
首先,请确保你已安装 Web3.py Python 库:
pip install web3
然后用anvil创建一个本地以太坊节点用来开发测试。
安装 foundryup:
curl -L https://foundry.paradigm.xyz | bash
运行anvil:
anvil
from web3 import Web3
anvil_url = "http://127.0.0.1:8545"
w3 = Web3(Web3.HTTPProvider(anvil_url))
w3.is_connected()
True
创建两个测试账户
from eth_account import Account
# 生成两个本地账户(随机私钥)
ac1 = Account.create()
ac2 = Account.create()
# 用默认账户给新账户转账一些以太币,以便支付部署合约的燃气费。
funder = w3.eth.accounts[0]
tx = {
'from': funder,
'to': ac1.address,
'value': w3.to_wei(1, 'ether'),
'gas': 21000,
'nonce': w3.eth.get_transaction_count(funder),
}
tx_hash = w3.eth.send_transaction(tx)
w3.eth.wait_for_transaction_receipt(tx_hash)
w3.eth.get_balance(ac1.address)
1000000000000000000
部署合约
先写一个简单的ERC20合约,然后部署它。
创建一个MyToken.sol文件,内容如下:
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MyToken is ERC20 {
constructor() public ERC20("MyToken", "MT") {
_mint(msg.sender, 1000000000000000000000000);
}
}
openzeppelin-contracts仓库中包含ERC20合约。从github上克隆该仓库。
用python的solcx库构建智能合约
首先先引入solcx库,并编译合约文件
from solcx import install_solc, set_solc_version
install_solc(version='0.8.30')
set_solc_version('0.8.30')
from solcx import compile_files
compiled_sol = compile_files([
"contracts/MyToken.sol"
], import_remappings=["@openzeppelin=~/code/web3/openzeppelin-contracts"])
contract_id, contract_interface = compiled_sol.popitem()
# get bytecode / bin
bytecode = contract_interface['bin']
# get abi
abi = contract_interface['abi']
编译完成后,我们可以使用Web3.py来部署合约。用ac1作为我们的代币的创建者。
他会拥有_mint函数中写的1000000000000000000000000
MyTokenContract = w3.eth.contract(abi=abi, bytecode=bytecode)
# 构造部署交易
construct_txn = MyTokenContract.constructor().build_transaction({
'from': ac1.address,
'nonce': w3.eth.get_transaction_count(ac1.address),
'gasPrice': w3.to_wei('2', 'gwei'),
'chainId': w3.eth.chain_id,
})
# 估算 gas 并补上
construct_txn['gas'] = w3.eth.estimate_gas(construct_txn)
# 签名 + 发送
signed_txn = w3.eth.account.sign_transaction(construct_txn, private_key=ac1.key)
tx_hash = w3.eth.send_raw_transaction(signed_txn.raw_transaction)
tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
这样合约就部署到链上了,我们可以使用合约地址和ABI与之交互。
my_token = w3.eth.contract(
address=tx_receipt.contractAddress,
abi=abi
)
# 查询我们的代币名称
my_token.functions.name().call()
'MyToken'
# 查询我们的发行量
my_token.functions.totalSupply().call()
1000000000000000000000000
print(f"账户1的余额: {my_token.functions.balanceOf(ac1.address).call()}")
print(f"账户2的余额: {my_token.functions.balanceOf(ac2.address).call()}")
账户1的余额: 1000000000000000000000000
账户2的余额: 0
用账户1给账户2转1个代币
# 1. 构建交易
tx = my_token.functions.transfer(ac2.address, 1).build_transaction({
'from': ac1.address,
'nonce': w3.eth.get_transaction_count(ac1.address),
'gasPrice': w3.to_wei('2', 'gwei'),
'gas': 100000, # 足够高的 gas limit
})
2. 签名交易
signed_tx = w3.eth.account.sign_transaction(tx, private_key=ac1.key)
3. 发送交易
tx_hash = w3.eth.send_raw_transaction(signed_tx.raw_transaction)
4. 等待交易确认
tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
tx_receipt
AttributeDict({'type': 0,
'status': 1,
'cumulativeGasUsed': 52117,
'logs': [AttributeDict({'address': '0x11416eE8d5D8Fb1dC1d3a1DfD719935AA638Dd9c',
'topics': [HexBytes('0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'),
HexBytes('0x000000000000000000000000948ff0e70a5f90db5b8bbc4815c1fc089add01b7'),
HexBytes('0x0000000000000000000000001e062370e60f8fe748180e3a838f454e66496e3e')],
'data': HexBytes('0x0000000000000000000000000000000000000000000000000000000000000001'),
'blockHash': HexBytes('0xbe1c47de397b7a1ca0e57dfaf66ccc60643aea85544ff7bf8a64963857708a05'),
'blockNumber': 9,
'blockTimestamp': '0x68ff2dfd',
'transactionHash': HexBytes('0x8c418450045ca3f5147b76b9dd402eb70245f0da13b00e0a50a86bc55ddc3596'),
'transactionIndex': 0,
'logIndex': 0,
'removed': False})],
'logsBloom': HexBytes('0x00000000000000000000000000000000000000000000000000000000000000010000000000000000008000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000020000000000000000000000000001000000000000000000000000000000000000000040000000000200000000000000000000000000000000000000001000000000002000000000000000000000000000000000800000000000000000000000000000000000000000000000000000002000000000000000000000000000000'),
'transactionHash': HexBytes('0x8c418450045ca3f5147b76b9dd402eb70245f0da13b00e0a50a86bc55ddc3596'),
'transactionIndex': 0,
'blockHash': HexBytes('0xbe1c47de397b7a1ca0e57dfaf66ccc60643aea85544ff7bf8a64963857708a05'),
'blockNumber': 9,
'gasUsed': 52117,
'effectiveGasPrice': 2000000000,
'blobGasPrice': 1,
'from': '0x948ff0E70a5F90dB5b8BBC4815C1Fc089add01B7',
'to': '0x11416eE8d5D8Fb1dC1d3a1DfD719935AA638Dd9c',
'contractAddress': None})
print(f"账户1的余额: {my_token.functions.balanceOf(ac1.address).call()}")
print(f"账户2的余额: {my_token.functions.balanceOf(ac2.address).call()}")
账户1的余额: 999999999999999999999999
账户2的余额: 1
1894

被折叠的 条评论
为什么被折叠?



