You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
/** @dev Creates `amount` tokens and assigns them to `account`, increasing * the total supply. * * Emits a {Transfer} event with `from` set to the zero address. * * Requirements: * * - `account` cannot be the zero address. */function _mint(addressaccount, uint256amount) internalvirtual {
require(account !=address(0), "ERC20: mint to the zero address");
_beforeTokenTransfer(address(0), account, amount);
_totalSupply += amount;
_balances[account] += amount;
emitTransfer(address(0), account, amount);
_afterTokenTransfer(address(0), account, amount);
}
// You might need the previously deployed yourToken:constyourToken=awaitethers.getContract('YourToken',deployer);// 部署承销商合约awaitdeploy('Vendor',{// Learn more about args here: https://www.npmjs.com/package/hardhat-deploy#deploymentsdeployfrom: deployer,args: [yourToken.address],log: true,});// 获取部署的合约constvendor=awaitethers.getContract('Vendor',deployer);// 发送 1000 个代币给承销商console.log('\n 🏵 Sending all 1000 tokens to the vendor...\n');awaityourToken.transfer(vendor.address,ethers.utils.parseEther('1000'));// 转移所有权awaitvendor.transferOwnership('0x169841AA3024cfa570024Eb7Dd6Bf5f774092088');
mapping(address=>mapping(address=>uint256)) private _allowances;
/** * @dev See {IERC20-approve}. * * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on * `transferFrom`. This is semantically equivalent to an infinite approval. * * Requirements: * * - `spender` cannot be the zero address. */function approve(addressspender, uint256amount) publicvirtualoverridereturns (bool) {
address owner =_msgSender();
_approve(owner, spender, amount);
returntrue;
}
/** * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. * * This internal function is equivalent to `approve`, and can be used to * e.g. set automatic allowances for certain subsystems, etc. * * Emits an {Approval} event. * * Requirements: * * - `owner` cannot be the zero address. * - `spender` cannot be the zero address. */function _approve(
addressowner,
addressspender,
uint256amount
) internalvirtual {
require(owner !=address(0), "ERC20: approve from the zero address");
require(spender !=address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount;
emitApproval(owner, spender, amount);
}
/** * @dev See {IERC20-allowance}. */function allowance(addressowner, addressspender) publicviewvirtualoverridereturns (uint256) {
return _allowances[owner][spender];
}
/** * @dev Updates `owner` s allowance for `spender` based on spent `amount`. * * Does not update the allowance amount in case of infinite allowance. * Revert if not enough allowance is available. * * Might emit an {Approval} event. */function _spendAllowance(
addressowner,
addressspender,
uint256amount
) internalvirtual {
uint256 currentAllowance =allowance(owner, spender);
if (currentAllowance !=type(uint256).max) {
require(currentAllowance >= amount, "ERC20: insufficient allowance");
unchecked {
_approve(owner, spender, currentAllowance - amount);
}
}
}
/** * @dev Moves `amount` of tokens from `sender` to `recipient`. * * This internal function is equivalent to {transfer}, and can be used to * e.g. implement automatic token fees, slashing mechanisms, etc. * * Emits a {Transfer} event. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `from` must have a balance of at least `amount`. */function _transfer(
addressfrom,
addressto,
uint256amount
) internalvirtual {
require(from !=address(0), "ERC20: transfer from the zero address");
require(to !=address(0), "ERC20: transfer to the zero address");
_beforeTokenTransfer(from, to, amount);
uint256 fromBalance = _balances[from];
require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
unchecked {
_balances[from] = fromBalance - amount;
}
_balances[to] += amount;
emitTransfer(from, to, amount);
_afterTokenTransfer(from, to, amount);
}
/** * @dev See {IERC20-transferFrom}. * * Emits an {Approval} event indicating the updated allowance. This is not * required by the EIP. See the note at the beginning of {ERC20}. * * NOTE: Does not update the allowance if the current allowance * is the maximum `uint256`. * * Requirements: * * - `from` and `to` cannot be the zero address. * - `from` must have a balance of at least `amount`. * - the caller must have allowance for ``from``'s tokens of at least * `amount`. */function transferFrom(
addressfrom,
addressto,
uint256amount
) publicvirtualoverridereturns (bool) {
address spender =_msgSender();
_spendAllowance(from, spender, amount);
_transfer(from, to, amount);
returntrue;
}
...
eventSellTokens(addressseller,uint256amountOfTokens,uint256amountOfETH);
...
// 允许用户使用代币换回 ETHfunctionsellTokens(uint256amountToSell)public{// 价差是否合理require(amountToSell>0,"Amount to sell must be greater than 0");// 检查用户是否有足够的代币uint256userBalance=yourToken.balanceOf(msg.sender));require(userBalance>=amountToSell,"Not enought tokens");// 检查承销商是否有足够的 ETHuint256amountOfEthNeeded=amountToSell/tokensPerEth;uint256venderBalance=address(this).balance;require(amountOfEthNeeded<=venderBalance,"Not enought ether");// 用户发送代币给承销商boolsent=yourToken.transferFrom(msg.sender,address(this),amountToSell);require(sent,"Failed to transfer tokens from seller to vender");// 承销商发送 ETH 给用户(boolsent,)=msg.sender.call{value: amountOfEthNeeded}("");require(sent,"Failed to send ether from vender to seller");emitSellTokens(msg.sender,amountToSell,amountOfEthNeeded);}
$ yarn deploy
Nothing to compile
No need to generate any newer typings.
deploying "YourToken" (tx: 0xa7a89a2917cfa355d1305643dc89f54d776186c0059977b0a237737fa37dff62)...: deployed at 0x0F0D10eF3589cE896E9E54E09568cB7a5371e398 with 639137 gas
deploying "Vendor" (tx: 0x3a1f02b77de29704a16599067c8e10abb0da78e547ea0eea8200761da5d45715)...: deployed at 0xb335Fc61D759C041503dC17266575229E593DE17 with 694098 gas
🏵 Sending all 1000 tokens to the vendor...
这篇教程我们来完成 scaffold-eth 项目的第二个挑战:代币承销商,我们可以在网站 speedrunethereum.com 中查看或者直接查看对应的 Github 连接:scaffold-eth/scaffold-eth-typescript-challenges。
这个挑战的目的是创建一个自己的ERC20代币,并编写承销商合约,实现用户对代币的购买和卖出。下面,我们一步步完成这个过程。
一、安装并设置环境
首先,我们下载项目,并初始化环境。
git clone https://github.com/scaffold-eth/scaffold-eth-typescript-challenges.git challenge-2-token-vendor cd challenge-2-token-vendor git checkout challenge-2-token-vendor yarn install
安装好依赖包之后,我们可以看到项目的主要目录为
packages
,包含一下子目录其中:
hardhat-ts
是项目合约代码,包含合约文件以及合约的部署等;services
The Graph 协议的 graph-node 配置;subgraph
The Graph 协议相应的处理设置,包括 mappings,数据结构等;vite-app-ts
前端项目,主要负责用户与合约交互。The Graph 协议是去中心化的区块链数据索引协议,本片教程中暂时不涉及。我们需要启动三个命令终端,分别用于运行以下命令:
yarn chain
使用 hardhat 运行本地区块链,作为合约部署的本地测试链;yarn deploy
编译、部署和发布合约;yarn start
启动 react 应用的前端;按顺序分别运行上述命令之后,此时我们就可以在
http://localhost:3000
中访问我们的应用。如果需要重新部署合约,运行yarn deploy --reset
即可。二、编写 ERC20 代币合约
现在我们进入合约编写部分。我们的目标是编写一个 ERC20 代币合约,并为创建者铸造 1000 个代币。
什么是 ERC20 合约标准
代币可以在以太坊中表示任何东西,比如信誉积分,黄金等,而 ERC-20 提供了一个同质化代币的标准,每个代币与另一个代币(在类型和价值上)完全相同。
ERC20是各个代币的标准接口,包含以下方法:
其中,合约必需设置
totalSupply
、balanceOf
、transfer
、transferFrom
、approve
以及allowance
这六个函数,其他如name
、symbol
和decimalsze
则是可选实现。使用 OpenZeppelin 库
如果从上述的合约标准开始,我们需要实现这六个函数的方法,幸运的是,OpenZeppelin 库是一个成熟的合约开发库,为我们实现了 ERC20 代币基本功能,我们可以基于这个库开发我们的 ERC20 代币,这将大大减少我们的工作量。我们可以在 ERC20 标准 页面查到相关的使用方法。
除了 ERC20,OpenZeppelin 库还提供了其他合约标准的实现,比如 ERC721,ERC777等,以及大量的经过安全审计的库,这些对于我们快速开发和实现安全的合约代码提供了支持。
编写代码
我们使用 ERC20.sol 来实现我们的合约,创见一个名为
GOLD
的代币,代币符号为GLD
,并为创建者铸造 1000 个代币:其中,
_mint
方法是 ERC20 提供的方法,该方法创建相应数量的代币,并将代币发送给账户:代码地址:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol#L248
部署脚本
接着我们使用脚本进行部署,并向地址发送 1000 代币,地址可以在
http://localhost:3000
中连接我们的 Metamask 得到。部署脚本地址:packages/hardhat-ts/deploy/00_deploy_your_token.ts
。然后我们运行
yarn deploy --reset
部署合约。验证
使用 Debug 页面功能进行检查,查看用户账户中的代币余额,可以看到账户中有 1000 个代币;
使用
transfer()
将代币转给另一个账户;在 Debug 中,使用
transfer
功能,输入目标钱包地址0xc12ae5Ba30Da6eB11978939379D383beb5Df9b33
,以及发送的数量1000000000000000000000
(1000*1E18,1后边有21个0),点击发送。等交易完成之后,可以分别查看原来账户和目标账户的代币数量,可以看到原来的变成了 0,目标账户是 1000。注意:
00_deploy_your_token.ts
中的 transfer 代码注释了,不然会影响之后的步骤。三、承销商合约 — 购买
接下来,我们创建一个承销商合约,这个合约允许用户通过以太购买代币。
为了完成这个功能,我们需要:
tokensPerEth=100
,也就是 1个以太可以兑换 100 GLD;buyTokens
函数,这个函数必须是payable
,可以接受发送的以太,计算对应的GLD
数量,然后使用transfer
将相应的GLD
代币发送给购买者msg.sender
;BuyTokens
事件,记录购买者,使用的 ETH 数量以及购买的 GLD 数量;withdraw
,用来将合约中的 ETH 全部提取到合约的所有者(owner)地址。我们可以使用两种方式设置合约的所有者:在这个教程中,我们使用第二个方式,这样我们可以不用将我们控制的地址的私钥添加到项目配置中,降低暴露。
其中,
Ownable
可以进行权限控制,合约提供的onlyOwner
修改器可以用来限制某些特定合约函数的访问权限。在这里,我们的withdraw
函数必需限制合约的所有这才能提取所有的资金。同时,这个合约提供了transferOwnership
函数,可以用来转移合约的所有者,这个将在我们的脚本部分中使用。对于部署脚本,我们需要完成以下功能:
vendor.address
,而不是我们之前的地址;ownership
转移到我们能控制的地址,比如我们在前端使用的地址。脚本位置:
packages/hardhat-ts/deploy/01_deploy_vendor.ts
部署合约
完成上述代码之后,我们重新部署我们的合约:
对应的输出结果为:
可以从命令行输出中看到合约部署的地址为:
0x9A676e781A523b5d0C0e43731313A708CB607508
0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82
验证
我们通过以下步骤进行验证:
通过 Debug 页面查看承销商 (Vendor)合约地址初始时是否有 1000 个代币;
使用 0.1 ETH 购买 10 个 GLD:我们使用 Buy Tokens 功能购买 10 个代币,可以看到此时的价格约为 0.1 ETH(ETH 价格为 2766.7 美元)。
将购买的代币发送给另一个账户:同样使用页面 Transfer Tokens 功能完成;
使用所有者账户,查看是否能全部取出合约中的 ETH:在 Debug 页面,我们使用
withdraw
功能,尝试将承销商合约中的 ETH 全部取出,可以看到,当交易完成以后,合约的余额变为了0:变为:
四、承销商合约 — 回购
接下来我们添加承销商合约的回购代币功能,也就是允许用户通过发送代币给承销商合约,承销商合约将对应的ETH发给用户账户。但是在以太坊中,合约只能通过 payable 接受 ETH,无法接受直接发送代币,如果直接向合约发送代币,代币将会永久消失。所以在 ERC20 标准中,我们需要使用
approve
和tranferFrom
者两个函数来完成这个过程。首先,用户通过调用
approve
函数授权承销商合约(spender
)处理amount
数量的代币,然后,调用transferFrom
函数将代币从用户账户(from
)转移amount
数量的代币给承销商合约(to
)。这其中的难点在于approve
和transferFrom
函数。我们来看一下这两个函数在 OpenZeppelin 中具体实现,首先是approve
:从上面可以看出,
approve
函数调用了_approve
,_approve
中用_allowances
这个哈希记录了owner
和spender
之间的授权数量amount
。因此可以推断,transferFrom
函数以及其他需要授权情况的函数都使用了_allowances
这个变量,比如allowance
函数。在
transferFrom
函数中,先使用_spendAllowance
进行授权数量检查并更新授权数量,然后再使用_transfer
进行代币划转,而_spendAllowance
中正是调用了allowance
这个函数。合约实现
合约的函数实现如下:
部署合约
我们再次部署新的合约:
此时,合约地址变为:
0x3Aa5ebB10DC797CAC828524e59A333d0A371443c
0x68B1D87F95878fE05B998F19b66F4baba5De1aed
验证
验证过程需要包含两步:
先在 Debug 页面使用代币的
approve
允许承销商合约处理 10 个代币:在
编辑权限
中,我们可以查看到授权的代币数量:使用承销商的
sellTokens
将 10 个代币换成 ETH。如果上一步没有使用approve
的话,程序会报错。到这一步,我们就完成了合约的编写。
五、部署到测试网络
我们将部署合约到测试网络中,使用的测试网络是
rinkeby
:rinkeby
:packages/hardhat-ts/hardhat.config.ts
的defaultNetwork
变量,packages/vite-app-ts/src/config/providersConfig.ts
中的targetNetworkInfo
变量yarn account
,如果没有找到可用账户,则使用yarn generate
生成;yarn deploy
进行合约部署:可以看到,合约部署成功,此时我们可以在线上测试网络查看到具体的合约部署情况:
并且部署完成了初始化代币分发和所有权转换。详情可以查看部署账户信息: https://rinkeby.etherscan.io/address/0xccb20d43f62f31dd94436f04a1e90d7d08569e57。
六、发布
接下来,我们将发布我们的前端项目到 Surge (或者使用 s3, ipfs 上)。Surge.sh 提供了免费的网站的部署,对于我们的测试网站来时再合适不过。
yarn build
yarn surge
Surge 在运行命令的过程中就设置了账户名称,以及可以自定义域名:qiwihui-scaffold-2.surge.sh,当完成部署之后,我们就可以在浏览器中访问这个页面,和我们本地运行的结果是一致的。
七、合约验证
当我们向测试网络部署合约时,部署的是合约编译之后的字节码,合约源码不会发布。实际生产中,有时我们需要发布我们的源代码,以保证我们的代码真实可信。此时,我们就可以借助 etherscan 提供的功能进行验证。
首先,我们获取 etherscan 的 API key,地址为 https://etherscan.io/myapikey,比如
PSW8C433Q667DVEX5BCRMGNAH9FSGFZ7Q8
;更新
packages/hardhat-ts/package.json
中对应的 api-key 参数:由于项目中的一个 bug,需要在根目录下的
packages.json
中添加以下命令才能直接使用之后的命令:运行
yarn verify --network rinkeby
,这个命令将通过 etherscan 接口进行合约验证,输出结果为:验证完成后,我们可以看到 etherscan 中的合约页面已经加上了一个蓝色小钩,在合约中,也可以看到我们合约的源代码:
至此,我们就完成了合约的验证。
八、提交结果
最后,当我们完成上述的所有步骤之后,我们可以将我们的结果提交到 speedrunethereum.com 上,选择对应的挑战,并提交部署的前端地址和承销商合约的链接即可:
Congratulations! 你已经完成了这个教程
总结
通过篇教程,我们可以学习到如下内容:
approve
和transferFrom
的使用;The text was updated successfully, but these errors were encountered: