创建并部署一个去中心化金融应用程序
在本教程中,我们将使用 Solidity 构建一个去中心化金融应用程序。用户可以将 ERC20 代币存入智能合约,然后铸造流动性矿池代币并将这些代币转给他们。 用户之后可以通过消耗智能合约上的流动性矿池代币来赎回他们的 ERC20 代币,然后 ERC20 代币将会转回给他们。
安装 Truffle 和 Ganache
如果这是你第一次编写智能合约,你需要搭建你的环境。 我们将使用两个工具:Truffle(opens in a new tab) 和 Ganache(opens in a new tab)。
Truffle 是用于开发以太坊智能合约的开发环境和测试框架。 使用 Truffle 可以很容易地在区块链中创建和部署智能合约。 Ganache 可以帮助我们创建一个本地以太坊区块链,用以测试智能合约。 它模拟真实的网络功能,前 10 个帐户存入了 100 个测试以太币,如此可以随意部署和测试智能合约。 Ganache 可提供桌面应用程序和命令行工具。 在本文中,我们将使用有图形界面的桌面应用程序。
(opens in a new tab)Ganache UI desktop application
要创建项目,请运行以下命令:
mkdir your-project-namecd your-project-nametruffle init
这将为智能合约的开发和部署创建一个空白项目, 创建的项目结构如下:
contracts
:存放 solidity 智能合约的文件夹migrations
:存放部署脚本的文件夹test
:存放智能合约测试文件的文件夹truffle-config.js
:Truffle 配置文件
创建ERC20 代币
首先,我们需要创建 ERC20 代币,用于在智能合约上质押。 要创建同质化代币,我们首先需要安装 OpenZeppelin 库。 该库包含 ERC20 和 ERC721 等标准的实现。 如需安装,请运行以下命令:
npm install @openzeppelin/contracts
使用 OpenZepelin 库,我们可以通过写入 contracts/MyToken.sol
创建我们的 ERC20 代币,需用到以下 solidity 代码:
1pragma solidity ^0.8.0;23import "@openzeppelin/contracts/token/ERC20/ERC20.sol";45contract MyToken is ERC20 {6 constructor() public ERC20("MyToken", "MTKN"){7 _mint(msg.sender, 1000000000000000000000000);8 }9}显示全部复制
在上述代码中:
第 3 行:我们从包含此代币标准实现的 openzepelin 导入 ERC20.sol 合约。
第 5 行:我们继承了 ERC20.sol 合约。
第 6 行:我们要调用 ERC20.sol 构造函数,并将名称和符号参数分别设为
"MyToken"
和"MTKN"
。第 7 行:我们为正在部署智能合约的帐户铸造和转移 100 万代币(我们默认为 ERC20 代币使用 18 位小数)。这意味着,如果我们想要铸造 1 个代币,则需将其表示为 1000000000000000000,即 1 和 18 个零)。
我们可以看到,ERC20.sol 构造函数实现下方,_decimals
字段设置为 18:
1string private _name;2string private _symbol;3uint8 private _decimals;45constructor (string memory name_, string memory symbol_) public {6 _name = name_;7 _symbol = symbol_;8 _decimals = 18;9}显示全部复制
编译 ERC20 代币
要编译我们的智能合约,我们必须首先检查我们的 solidity 编译器版本。 可以通过运行以下命令来检查:
truffle version
默认版本是 Solidity v0.5.16
。 由于我们的代币基于 solidity 版本 0.6.2
编写,如果我们运行命令来编译合约,我们会遇到编译器错误。 要使用指定的 solidity 编译器版本,请转到文件 truffle-config.js
,设为所需的编译器版本,如下所示:
1// Configure your compilers2compilers: {3 solc: {4 version: "^0.8.0", // Fetch exact version from solc-bin (default: truffle's version)5 // docker: true, // Use "0.5.1" you've installed locally with docker (default: false)6 // settings: { // See the solidity docs for advice about optimization and evmVersion7 // optimizer: {8 // enabled: false,9 // runs: 20010 // },11 // evmVersion: "byzantium"12 // }13 }14}显示全部
现在,我们可以通过运行以下命令来编译智能合约:
truffle compile
部署 ERC20 代币
编译后,我们现在便可以部署代币了。
在 migrations
文件夹中,创建一个名为 2_deploy_Tokens.js
的文件。 我们将在该文件中部署 ERC20 代币和 FarmToken 智能合约。 以下代码用于部署我们的 MyToken.sol 合约:
1const MyToken = artifacts.require("MyToken")23module.exports = async function (deployer, network, accounts) {4 // Deploy MyToken5 await deployer.deploy(MyToken)6 const myToken = await MyToken.deployed()7}
打开 Ganache 并选择“快速启动”选项,启动本地以太坊区块链。 要部署合约,请运行:
truffle migrate
用于部署合同的地址是 Ganache 显示的地址列表中的第一个地址。 为核实这一点,我们可以打开 Ganache 桌面应用程序,然后可以核实第一个帐户的以太币余额已经减少,这是我们部署智能合约的以太币成本:
(opens in a new tab)Ganache desktop application
为了验证 100 万 MyToken 代币是否已发送到部署者地址,我们可以使用 Truffle 控制台与我们部署的智能合约进行交互。
要与智能合约交互,请运行以下命令:
truffle console
现在,我们可以在终端写入以下命令:
获取智能合约:
myToken = await MyToken.depolyed()
从 Ganache 获取帐户组:
accounts = await web3.eth.getAccounts()
获取第一个帐户的余额:
balance = await myToken.balance Of(accounts[0])
格式化具有 18 位小数的余额:
web3.utils.fromWei(balance.toString())
通过运行上述命令,我们会看到第一个地址实际上有 100 万 MyToken:
第一个地址有 1000000 MyToken
创建 FarmToken 智能合约
FarmToken 智能合约将包含 3 个函数:
balance()
:获取 FarmToken 智能合约上的 MyToken 余额。deposit(uint256 _amount)
:代表用户将 MyToken 转移到 FarmToken 智能合约,然后铸造 FarmToken 并转账给用户。withdraw(uint256 _amount)
:消耗用户的 FarmToken 并将 MyToken 转到用户的地址。
我们来看看 FarmToken 构造函数:
1pragma solidity ^0.6.2;23import "@openzeppelin/contracts/token/ERC20/IERC20.sol";4import "@openzeppelin/contracts/utils/Address.sol";5import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";6import "@openzeppelin/contracts/token/ERC20/ERC20.sol";78contract FarmToken is ERC20 {9 using Address for address;10 using SafeMath for uint256; // As of Solidity v0.8.0, mathematical operations can be done safely without the need for SafeMath11 using SafeERC20 for IERC20;1213 IERC20 public token;1415 constructor(address _token)16 public17 ERC20("FarmToken", "FRM")18 {19 token = IERC20(_token);20 }显示全部复制
第 3-6 行:我们要从 openzepelin 导入下列合约:IERC20.sol、Address.sol、SafeERC20.sol 和ERC20.sol。
第 8 行:FarmToken 将继承 ERC20 合约。
14-19 行:FarmToken 构造函数将接收 MyToken 合约地址作为参数,我们将把此地址分配给我们名为
token
的公共变量。
我们来实现 balance()
函数。 它将不会收到任何参数,将返回此智能合约上的 MyToken 余额。 可按如下所示实现:
1function balance() public view returns (uint256) {2 return token.balanceOf(address(this));3}复制
对于 deposit(uint256 _amount)
函数,将接收用户想要存入的金额作为参数,铸造 FarmTokens 并转给用户:
1function deposit(uint256 _amount) public {2 // Amount must be greater than zero3 require(_amount > 0, "amount cannot be 0");45 // Transfer MyToken to smart contract6 token.safeTransferFrom(msg.sender, address(this), _amount);78 // Mint FarmToken to msg sender9 _mint(msg.sender, _amount);10}显示全部复制
对于 withdraw(uint256 _amount)
函数,将接收用户想要消耗的 FarmTokens 数额作为参数,然后将相同数额的 MyToken 返还给用户:
1function withdraw(uint256 _amount) public {2 // Burn FarmTokens from msg sender3 _burn(msg.sender, _amount);45 // Transfer MyTokens from this smart contract to msg sender6 token.safeTransfer(msg.sender, _amount);7}复制
现在我们将部署我们的智能合约。 为此,我们将返回文件 2_deploy_Tokens.js
并添加要部署的新合约:
1const MyToken = artifacts.require("MyToken")2const FarmToken = artifacts.require("FarmToken")34module.exports = async function (deployer, network, accounts) {5 // Deploy MyToken6 await deployer.deploy(MyToken)7 const myToken = await MyToken.deployed()89 // Deploy Farm Token10 await deployer.deploy(FarmToken, myToken.address)11 const farmToken = await FarmToken.deployed()12}显示全部
请注意,在部署 FarmToken 时,我们将已部署的 MyToken 合约的地址作为参数传递。
现在,运行 truffle compile
和 truffle migrate
来部署我们的合约。
我们来测试一下我们的智能合约。 不要使用 truffle console
与我们的智能合约交互,我们将创建一个脚本来自动执行此过程。 创建一个名为 scripts
的文件夹,并添加以下文件 getMyTokenBalance.js
。 该文件将核查 FarmToken 智能合约中的 MyToken 余额:
1const MyToken = artifacts.require("MyToken")2const FarmToken = artifacts.require("FarmToken")34module.exports = async function (callback) {5 myToken = await MyToken.deployed()6 farmToken = await FarmToken.deployed()7 balance = await myToken.balanceOf(farmToken.address)8 console.log(web3.utils.fromWei(balance.toString()))9 callback()10}显示全部
要执行此脚本,请运行以下 cli 命令:
truffle exec .\scripts\getMyTokenBalance.js
我们将得到预期结果 0。 如果你收到尚未部署 FarmToken 的错误,则表明 truffle 网络尚未收到最新版本的合约代码。 只需关闭 ganache,然后再快速启动,并确保运行 truffle migrate
。
现在,让我们把 MyToken 押在智能合约上。 由于 deposit(uint256 _amount)
函数调用了来自 ERC20 的 safeTransferFrom
函数,用户必须首先批准智能合约,才能以用户的名义转账 MyToken。 因此,在下面的脚本中,我们将首先批准此步骤,然后调用函数:
1const MyToken = artifacts.require("MyToken")2const FarmToken = artifacts.require("FarmToken")34module.exports = async function (callback) {5 const accounts = await new web3.eth.getAccounts()6 const myToken = await MyToken.deployed()7 const farmToken = await FarmToken.deployed()89 // Returns the remaining number of tokens that spender will be allowed to spend on behalf of owner through transferFrom.10 // This is zero by default.11 const allowanceBefore = await myToken.allowance(12 accounts[0],13 farmToken.address14 )15 console.log(16 "Amount of MyToken FarmToken is allowed to transfer on our behalf Before: " +17 allowanceBefore.toString()18 )1920 // In order to allow the Smart Contract to transfer to MyToken (ERC-20) on the accounts[0] behalf,21 // we must explicitly allow it.22 // We allow farmToken to transfer x amount of MyToken on our behalf23 await myToken.approve(farmToken.address, web3.utils.toWei("100", "ether"))2425 // Validate that the farmToken can now move x amount of MyToken on our behalf26 const allowanceAfter = await myToken.allowance(accounts[0], farmToken.address)27 console.log(28 "Amount of MyToken FarmToken is allowed to transfer on our behalf After: " +29 allowanceAfter.toString()30 )3132 // Verify accounts[0] and farmToken balance of MyToken before and after the transfer33 balanceMyTokenBeforeAccounts0 = await myToken.balanceOf(accounts[0])34 balanceMyTokenBeforeFarmToken = await myToken.balanceOf(farmToken.address)35 console.log("*** My Token ***")36 console.log(37 "Balance MyToken Before accounts[0] " +38 web3.utils.fromWei(balanceMyTokenBeforeAccounts0.toString())39 )40 console.log(41 "Balance MyToken Before TokenFarm " +42 web3.utils.fromWei(balanceMyTokenBeforeFarmToken.toString())43 )4445 console.log("*** Farm Token ***")46 balanceFarmTokenBeforeAccounts0 = await farmToken.balanceOf(accounts[0])47 balanceFarmTokenBeforeFarmToken = await farmToken.balanceOf(farmToken.address)48 console.log(49 "Balance FarmToken Before accounts[0] " +50 web3.utils.fromWei(balanceFarmTokenBeforeAccounts0.toString())51 )52 console.log(53 "Balance FarmToken Before TokenFarm " +54 web3.utils.fromWei(balanceFarmTokenBeforeFarmToken.toString())55 )56 // Call Deposit function from FarmToken57 console.log("Call Deposit Function")58 await farmToken.deposit(web3.utils.toWei("100", "ether"))59 console.log("*** My Token ***")60 balanceMyTokenAfterAccounts0 = await myToken.balanceOf(accounts[0])61 balanceMyTokenAfterFarmToken = await myToken.balanceOf(farmToken.address)62 console.log(63 "Balance MyToken After accounts[0] " +64 web3.utils.fromWei(balanceMyTokenAfterAccounts0.toString())65 )66 console.log(67 "Balance MyToken After TokenFarm " +68 web3.utils.fromWei(balanceMyTokenAfterFarmToken.toString())69 )7071 console.log("*** Farm Token ***")72 balanceFarmTokenAfterAccounts0 = await farmToken.balanceOf(accounts[0])73 balanceFarmTokenAfterFarmToken = await farmToken.balanceOf(farmToken.address)74 console.log(75 "Balance FarmToken After accounts[0] " +76 web3.utils.fromWei(balanceFarmTokenAfterAccounts0.toString())77 )78 console.log(79 "Balance FarmToken After TokenFarm " +80 web3.utils.fromWei(balanceFarmTokenAfterFarmToken.toString())81 )8283 // End function84 callback()85}显示全部
要运行此脚本:truffle exec .\script\transferMyTokenToFarmToken.js
。 你将在控制台上看到如下输出:
transferMyTokenToFarmToken.js 的输出
正如我们所见,我们已成功将 MyToken 存入智能合约,因为第一个帐户现在已经有了 FarmToken。
要取款:
1const MyToken = artifacts.require("MyToken")2const FarmToken = artifacts.require("FarmToken")34module.exports = async function (callback) {5 const accounts = await new web3.eth.getAccounts()6 const myToken = await MyToken.deployed()7 const farmToken = await FarmToken.deployed()89 // Verify accounts[0] and farmToken balance of MyToken before and after the transfer10 balanceMyTokenBeforeAccounts0 = await myToken.balanceOf(accounts[0])11 balanceMyTokenBeforeFarmToken = await myToken.balanceOf(farmToken.address)12 console.log("*** My Token ***")13 console.log(14 "Balance MyToken Before accounts[0] " +15 web3.utils.fromWei(balanceMyTokenBeforeAccounts0.toString())16 )17 console.log(18 "Balance MyToken Before TokenFarm " +19 web3.utils.fromWei(balanceMyTokenBeforeFarmToken.toString())20 )2122 console.log("*** Farm Token ***")23 balanceFarmTokenBeforeAccounts0 = await farmToken.balanceOf(accounts[0])24 balanceFarmTokenBeforeFarmToken = await farmToken.balanceOf(farmToken.address)25 console.log(26 "Balance FarmToken Before accounts[0] " +27 web3.utils.fromWei(balanceFarmTokenBeforeAccounts0.toString())28 )29 console.log(30 "Balance FarmToken Before TokenFarm " +31 web3.utils.fromWei(balanceFarmTokenBeforeFarmToken.toString())32 )3334 // Call Deposit function from FarmToken35 console.log("Call Withdraw Function")36 await farmToken.withdraw(web3.utils.toWei("100", "ether"))3738 console.log("*** My Token ***")39 balanceMyTokenAfterAccounts0 = await myToken.balanceOf(accounts[0])40 balanceMyTokenAfterFarmToken = await myToken.balanceOf(farmToken.address)41 console.log(42 "Balance MyToken After accounts[0] " +43 web3.utils.fromWei(balanceMyTokenAfterAccounts0.toString())44 )45 console.log(46 "Balance MyToken After TokenFarm " +47 web3.utils.fromWei(balanceMyTokenAfterFarmToken.toString())48 )4950 console.log("*** Farm Token ***")51 balanceFarmTokenAfterAccounts0 = await farmToken.balanceOf(accounts[0])52 balanceFarmTokenAfterFarmToken = await farmToken.balanceOf(farmToken.address)53 console.log(54 "Balance FarmToken After accounts[0] " +55 web3.utils.fromWei(balanceFarmTokenAfterAccounts0.toString())56 )57 console.log(58 "Balance FarmToken After TokenFarm " +59 web3.utils.fromWei(balanceFarmTokenAfterFarmToken.toString())60 )6162 // End function63 callback()64}显示全部
要运行此脚本:truffle exec .\scripts\withdrawMyTokenFromTokenFarm.js
。 正如我们在下面的输出中所看到的,我们已经成功地取回了 MyToken,并消耗掉了 FarmToken:
withdrawMyTokenFromTokenFarm.js 的输出
参考文献
合约 - OpenZepelin 文档(opens in a new tab)
智能合约的适用工具 | Truffle 套装(opens in a new tab)
Ganache | Truffle 套装(opens in a new tab)
什么是去中心化金融? 初学者指南(2021 年更新)(99bitcoins.com)(opens in a new tab)
去中心化金融 - DeFi Llama 的去中心化金融排行榜(opens in a new tab)
上次修改时间: @nhsz(opens in a new tab), 2023年8月15日