0. 背景
上一篇文章我们从合约编写、编译、部署、交互等几个方面介绍了truffle
的大致用法。
本篇主要继续深入地介绍truffle
的高级用法 + 合约源码分析
1. 将合约部署到测试网Ropsten
1.1 注册infura
获取API-KEY
infura是基于ETH的chain公共API服务,为开发者提供链上数据查询、交易广播等功能。
1.2 准备助记词
可从Web钱包MetaMask 上进行创建并导出助记词
注 一个助记词可以对应多个网络环境的地址,请勿向助记词对应的地址转入主网的ETH
,否则有一定的安全风险
1.3 安装 truffle-hdwallet-provider
由于需要将合约部署到测试网Ropsten
,此过程需要部署者私钥进行签名再将数据进行广播,truffle-hdwallet-provider
是truffle
上提供交易离线签名的模块
注:必须在truffle
框架的代码目录下安装,否则不生效
✘ wujinquan@wujinquandeMacBook-Pro ~/workspace/eth/truffle/defi npm install truffle-hdwallet-provider
npm WARN deprecated [email protected]: WARNING: This package has been renamed to @truffle/hdwallet-provider.
npm WARN deprecated [email protected]: request has been deprecated, see https://github.com/request/request/issues/3142
> [email protected] install /Users/wujinquan/workspace/eth/truffle/defi/node_modules/web3-providers-ws/node_modules/websocket
> (node-gyp rebuild 2> builderror.log) || (exit 0)
CXX(target) Release/obj.target/bufferutil/src/bufferutil.o
> [email protected] install /Users/wujinquan/workspace/eth/truffle/defi/node_modules/websocket
> (node-gyp rebuild 2> builderror.log) || (exit 0)
CXX(target) Release/obj.target/bufferutil/src/bufferutil.o
SOLINK_MODULE(target) Release/bufferutil.node
CXX(target) Release/obj.target/validation/src/validation.o
SOLINK_MODULE(target) Release/validation.node
npm WARN saveError ENOENT: no such file or directory, open '/Users/wujinquan/workspace/eth/truffle/defi/package.json'
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN enoent ENOENT: no such file or directory, open '/Users/wujinquan/workspace/eth/truffle/defi/package.json'
npm WARN defi No description
npm WARN defi No repository field.
npm WARN defi No README data
npm WARN defi No license field.
+ [email protected]
added 326 packages from 212 contributors and audited 20646 packages in 114.122s
found 2 vulnerabilities (1 low, 1 high)
run `npm audit fix` to fix them, or `npm audit` for details
╭────────────────────────────────────────────────────────────────╮
│ │
│ New minor version of npm available! 6.13.4 → 6.14.2 │
│ Changelog: https://github.com/npm/cli/releases/tag/v6.14.2 │
│ Run npm install -g npm to update! │
│ │
╰────────────────────────────────────────────────────────────────╯
wujinquan@wujinquandeMacBook-Pro ~/workspace/eth/truffle/defi ls
build defi node_modules test
contracts migrations package-lock.json truffle-config.js
wujinquan@wujinquandeMacBook-Pro ~/workspace/eth/truffle/defi
1.4 修改truffle
配置
主要修改ropsten
网络配置,使truffle
能顺利与ropsten
交互
/**
* Use this file to configure your truffle project. It's seeded with some
* common settings for different networks and features like migrations,
* compilation and testing. Uncomment the ones you need or modify
* them to suit your project as necessary.
*
* More information about configuration can be found at:
*
* truffleframework.com/docs/advanced/configuration
*
* To deploy via Infura you'll need a wallet provider (like @truffle/hdwallet-provider)
* to sign your transactions before they're sent to a remote public node. Infura accounts
* are available for free at: infura.io/register.
*
* You'll also need a mnemonic - the twelve word phrase the wallet uses to generate
* public/private key pairs. If you're publishing your code to GitHub make sure you load this
* phrase from a file you've .gitignored so it doesn't accidentally become public.
*
*/
const HDWalletProvider = require("truffle-hdwallet-provider");
// const infuraKey = "fj4jll3k.....";
//
// const fs = require('fs');
// const mnemonic = fs.readFileSync(".secret").toString().trim();
const mnemonic = "grant boat distance require trap pair theme together spray blush glory simple"
//0x843ACfB41E5c0F1E0587C5B765d897cCDeA8c4DD
module.exports = {
/**
* Networks define how you connect to your ethereum client and let you set the
* defaults web3 uses to send transactions. If you don't specify one truffle
* will spin up a development blockchain for you on port 9545 when you
* run `develop` or `test`. You can ask a truffle command to use a specific
* network from the command line, e.g
*
* $ truffle test --network <network-name>
*/
networks: {
// Useful for testing. The `development` name is special - truffle uses it by default
// if it's defined here and no other network is specified at the command line.
// You should run a client (like ganache-cli, geth or parity) in a separate terminal
// tab if you use this network and you must also set the `host`, `port` and `network_id`
// options below to some value.
//
// development: {
// host: "127.0.0.1", // Localhost (default: none)
// port: 8545, // Standard Ethereum port (default: none)
// network_id: "*", // Any network (default: none)
// },
// Another network with more advanced options...
// advanced: {
// port: 8777, // Custom port
// network_id: 1342, // Custom network
// gas: 8500000, // Gas sent with each transaction (default: ~6700000)
// gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei)
// from: <address>, // Account to send txs from (default: accounts[0])
// websockets: true // Enable EventEmitter interface for web3 (default: false)
// },
// Useful for deploying to a public network.
// NB: It's important to wrap the provider as a function.
ropsten: {
provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/v3/5c93axxx`),//示例无效
network_id: 3, // Ropsten's id
gas: 5500000, // Ropsten has a lower block limit than mainnet
confirmations: 2, // # of confs to wait between deployments. (default: 0)
timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50)
skipDryRun: true // Skip dry run before migrations? (default: false for public nets )
}
// Useful for private networks
// private: {
// provider: () => new HDWalletProvider(mnemonic, `https://network.io`),
// network_id: 2111, // This network is yours, in the cloud.
// production: true // Treats this network as if it was a public net. (default: false)
// }
},
// Set default mocha options here, use special reporters etc.
mocha: {
// timeout: 100000
},
// Configure your compilers
compilers: {
solc: {
// version: "0.5.1", // Fetch exact version from solc-bin (default: truffle's version)
// docker: true, // Use "0.5.1" you've installed locally with docker (default: false)
// settings: { // See the solidity docs for advice about optimization and evmVersion
// optimizer: {
// enabled: false,
// runs: 200
// },
// evmVersion: "byzantium"
// }
}
}
}
1.5 部署至Ropsten
执行truffle migrate --network ropsten --reset
即可部署至Ropsten
如需要重新编译所有合约,可执行truffle migrate --network ropsten --reset --compile-all
wujinquan@wujinquandeMacBook-Pro ~/workspace/eth/truffle/defi truffle migrate --network ropsten --reset
Compiling your contracts...
===========================
> Everything is up to date, there is nothing to compile.
Starting migrations...
======================
> Network name: 'ropsten'
> Network id: 3
> Block gas limit: 0x7a121d
1_initial_migration.js
======================
Replacing 'Migrations'
----------------------
> transaction hash: 0x349ca58d8ed2176265efc58bf232878d930c71436a9b39796062489a638de396
> Blocks: 2 Seconds: 92
> contract address: 0x619DB4dda0cd2b3fF6351F52828Af5Cc44E6cA55
> block number: 7531302
> block timestamp: 1584376631
> account: 0x843ACfB41E5c0F1E0587C5B765d897cCDeA8c4DD
> balance: 0.47030684
> gas used: 164175
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.0032835 ETH
Pausing for 2 confirmations...
------------------------------
> confirmation number: 1 (block: 7531303)
> confirmation number: 3 (block: 7531305)
> Saving migration to chain.
> Saving artifacts
-------------------------------------
> Total cost: 0.0032835 ETH
2_deploy_contracts.js
=====================
Replacing 'DeFi'
----------------
> transaction hash: 0x4be50b4e7a560080de6587078f937a553f29ac7b04ccd2e6b3802dc75dc8fe61
> Blocks: 1 Seconds: 63
> contract address: 0xB378420cde84c7A73EecfAeE2fC91ED364F45E9F
> block number: 7531309
> block timestamp: 1584376852
> account: 0x843ACfB41E5c0F1E0587C5B765d897cCDeA8c4DD
> balance: 0.4474762
> gas used: 1099191
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.02198382 ETH
Pausing for 2 confirmations...
------------------------------
> confirmation number: 1 (block: 7531310)
> confirmation number: 2 (block: 7531311)
> Saving migration to chain.
> Saving artifacts
-------------------------------------
> Total cost: 0.02198382 ETH
Summary
=======
> Total deployments: 2
> Final cost: 0.02526732 ETH
wujinquan@wujinquandeMacBook-Pro ~/workspace/eth/truffle/defi
1.6 从区块浏览器上查看已部署的合约
从log输出可见DeFi
合约地址为0xB378420cde84c7A73EecfAeE2fC91ED364F45E9F
2. 合约源码分析
项目源码可见Github仓库,主要分析DeFi.sol
,过程以下代码中的详细中英文注释
pragma solidity >=0.4.25 <0.7.0;
contract Token{
mapping (address => uint256) balances;
string public name;
string public symbol;
uint8 public decimals;
uint256 public totalSupply;
mapping (address => mapping (address => uint256)) allowed;
//ERC20 Token Standard: https://eips.ethereum.org/EIPS/eip-20
constructor() public {
name = "Token"; // Set the name
symbol = "TK"; // Set the symbol
decimals = 18; // Amount of decimals for display purposes
// totalSupply = 1000000000000000000000; // Not set total supply
}
//Returns the account balance of another account with address _owner.
function balanceOf(address _owner) public view returns (uint256 balance) {
return balances[_owner];
}
/* Transfers _value amount of tokens to address _to, and MUST fire the Transfer event.
The function SHOULD throw if the message caller’s account balance does not have enough tokens to spend.
Note Transfers of 0 values MUST be treated as normal transfers and fire the Transfer event.*/
function transfer(address _to, uint256 _value) public returns (bool success) {
require(_value > 0 ); // Check if token's value to be send > 0
require(balances[msg.sender] >= _value); // Check if the sender has enough token
require(balances[_to] + _value > balances[_to]); // Check for overflows
balances[msg.sender] -= _value; // Subtract token from the sender
balances[_to] += _value; // Add the same amount to the receiver
emit Transfer(msg.sender, _to, _value); // Notify anyone listening that this transaction happen.
return true;
}
/* The transferFrom method is used for a withdraw workflow,
allowing contracts to transfer tokens on your behalf.
This can be used for example to allow a contract to transfer tokens on your behalf and/or to charge fees in sub-currencies.
The function SHOULD throw unless the _from account has deliberately authorized the sender of the message via some mechanism.
Note Transfers of 0 values MUST be treated as normal transfers and fire the Transfer event.*/
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
require(balances[_from] >= _value); // Check if the sender has enough token
require(balances[_to] + _value >= balances[_to]); // Check for overflows
require(_value <= allowed[_from][msg.sender]); // Check allowance
balances[_from] -= _value; // Subtract from the sender
balances[_to] += _value; // Add the same amount to the receiver
allowed[_from][msg.sender] -= _value;
emit Transfer(_from, _to, _value);
return true;
}
/* Allows _spender to withdraw from your account multiple times,
up to the _value amount. If this function is called again it overwrites the current allowance with _value.
NOTE: To prevent attack vectors like the one described here and discussed here,
clients SHOULD make sure to create user interfaces in such a way that they set the allowance first to 0 before setting it to another value for the same spender.
THOUGH The contract itself shouldn’t enforce it, to allow backwards compatibility with contracts deployed before */
function approve(address _spender, uint256 _value) public returns (bool success) {
require(balances[msg.sender] >= _value);
allowed[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value);
return true;
}
//Returns the amount which _spender is still allowed to withdraw from _owner.
function allowance(address _owner, address _spender) public view returns (uint256 remaining) {
return allowed[_owner][_spender];
}
//The event for tranfer and approve
event Transfer(address indexed _from, address indexed _to, uint256 _value);
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
}
contract DeFi is Token{
//声明owner,暂时没作用
address public owner ;
//声明 用户-抵押ETH数量 mapping
mapping (address => uint) pledgeETHAmount;
//声明 抵押/赎回时的event
event Pledge(address user, uint256 amount);
event Redeem(address user, uint256 amount);
//构造函数,只在合约被部署的时候执行一次
constructor() public {
owner = msg.sender ;
}
//抵押功能
function pledge() public payable returns(bool success){
//ETH抵押金额必须大于0
require(msg.value > 0, "Not enough ETH to pledge.");
//抵押操作
// 1. 1:1贷出ERC20 Token
Token.balances[msg.sender] += msg.value;
// 2. 写入抵押信息map,记录用户抵押ETH的数量:单位wei
pledgeETHAmount[msg.sender] += msg.value;
// 3. 更新Token总量
Token.totalSupply += msg.value;
//记录抵押事件
emit Pledge(msg.sender,msg.value);
return true;
}
//赎回功能
function redeem(uint256 value) public returns(bool success){
//要求赎回ETH的数量必须 <= Token余额
require(value <= Token.balances[msg.sender],"Not enough ETH to redeem.");
//赎回操作
// 1. 在合约转出ETH到用户地址之前将待发金额清零,更新用户Token余额和Token总量,来防止重入(re-entrancy)攻击
Token.balances[msg.sender] -= value;
Token.totalSupply -= value;
// 2. 从合约里转ETH到对应用户
msg.sender.transfer(value);
//记录赎回事件
emit Redeem(msg.sender,value);
return true;
}
}
3. 与合约交互
在项目目录中执行truffle console --network ropsten
连接ropsten
并进入console
模式
3.0 定义常用的抽象合约对象
truffle(ropsten)> let defi = await DeFi.deployed() //合约对象定义为defi
undefined
truffle(ropsten)> let contractaddr = await defi.address ////合约地址定义为contractaddr
undefined
truffle(ropsten)>
3.1 调用合约抵押ETH
贷出ERC20
代币TK
可见区块浏览器https://ropsten.etherscan.io/tx/0x6dcb3790976b0d3a6f0715739a3c771f192f39b3f1d2995a88f718d6df30c6d9
truffle(ropsten)> defi.pledge.sendTransaction({from:accounts[0],value:101}) //指定sender和抵押ETH数量,回显需要一点时间
{
tx: '0x6dcb3790976b0d3a6f0715739a3c771f192f39b3f1d2995a88f718d6df30c6d9',
receipt: {
blockHash: '0x15b79d45581d83e7f5aaa40a9fac51c13d1824f188869207948c050fc4ded8ae',
blockNumber: 7531333,
contractAddress: null,
cumulativeGasUsed: 85311,
from: '0x843acfb41e5c0f1e0587c5b765d897ccdea8c4dd',
gasUsed: 85311,
logs: [ [Object] ],
logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000200000000000000000000000000000000000040000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
status: true,
to: '0xb378420cde84c7a73eecfaee2fc91ed364f45e9f',
transactionHash: '0x6dcb3790976b0d3a6f0715739a3c771f192f39b3f1d2995a88f718d6df30c6d9',
transactionIndex: 0,
rawLogs: [ [Object] ]
},
logs: [
{
address: '0xB378420cde84c7A73EecfAeE2fC91ED364F45E9F',
blockHash: '0x15b79d45581d83e7f5aaa40a9fac51c13d1824f188869207948c050fc4ded8ae',
blockNumber: 7531333,
logIndex: 0,
removed: false,
transactionHash: '0x6dcb3790976b0d3a6f0715739a3c771f192f39b3f1d2995a88f718d6df30c6d9',
transactionIndex: 0,
id: 'log_818726e7',
event: 'Pledge',
args: [Result]
}
]
}
truffle(ropsten)>
3.2 查询合约余额和用户的Token余额是否达到预期
truffle(ropsten)> web3.eth.getBalance(contractaddr) //合约的ETH余额
'101'
truffle(ropsten)> defi.balanceOf(accounts[0]) //用户的Token余额
BN {
negative: 0,
words: [ 101, <1 empty item> ],
length: 1,
red: null
}
truffle(ropsten)>
3.3 调用合约归还ERC20
代币TK
,赎回ETH
可见区块浏览器https://ropsten.etherscan.io/tx/0xa720d3da5eda30b9a816e68aaf41ddde0a536c2307734317f6b15243fb9b1829
truffle(ropsten)> defi.redeem.sendTransaction(99,{from:accounts[0]}) //归还99`TK`,1比1 赎回99`wei`的`ETH`
Error: Invalid JSON RPC response: ""
at Object.InvalidResponse (/Users/wujinquan/workspace/eth/truffle/defi/node_modules/truffle-hdwallet-provider/dist/webpack:/truffle-hdwallet-provider/Users/gnidan/src/work/truffle/node_modules/web3/node_modules/web3-core-helpers/src/errors.js:42:1)
at t.InvalidResponse [as onreadystatechange] (/Users/wujinquan/workspace/eth/truffle/defi/node_modules/truffle-hdwallet-provider/dist/webpack:/truffle-hdwallet-provider/Users/gnidan/src/work/truffle/node_modules/web3/node_modules/web3-providers-http/src/index.js:92:1)
at t._a [as dispatchEvent] (/Users/wujinquan/workspace/eth/truffle/defi/node_modules/truffle-hdwallet-provider/dist/webpack:/truffle-hdwallet-provider/Users/gnidan/src/work/truffle/node_modules/xhr2-cookies/dist/xml-http-request-event-target.js:27:61)
at t.dispatchEvent [as _setReadyState] (/Users/wujinquan/workspace/eth/truffle/defi/node_modules/truffle-hdwallet-provider/dist/webpack:/truffle-hdwallet-provider/Users/gnidan/src/work/truffle/node_modules/xhr2-cookies/dist/xml-http-request.js:208:1)
at t._setReadyState [as _onHttpRequestError] (/Users/wujinquan/workspace/eth/truffle/defi/node_modules/truffle-hdwallet-provider/dist/webpack:/truffle-hdwallet-provider/Users/gnidan/src/work/truffle/node_modules/xhr2-cookies/dist/xml-http-request.js:349:1)
at ClientRequest._onHttpRequestError (/Users/wujinquan/workspace/eth/truffle/defi/node_modules/truffle-hdwallet-provider/dist/webpack:/truffle-hdwallet-provider/Users/gnidan/src/work/truffle/node_modules/xhr2-cookies/dist/xml-http-request.js:252:47)
at ClientRequest.emit (events.js:305:20)
at ClientRequest.EventEmitter.emit (domain.js:483:12)
at TLSSocket.socketErrorListener (_http_client.js:423:9)
at TLSSocket.emit (events.js:305:20)
at TLSSocket.EventEmitter.emit (domain.js:483:12)
at emitErrorNT (internal/streams/destroy.js:84:8)
at processTicksAndRejections (internal/process/task_queues.js:84:21)
{
tx: '0xa720d3da5eda30b9a816e68aaf41ddde0a536c2307734317f6b15243fb9b1829',
receipt: {
blockHash: '0x0a08ef737e0729f31a2bd428eb50273833f669ebfc95b622cdf405c67996ceef',
blockNumber: 7531341,
contractAddress: null,
cumulativeGasUsed: 756933,
from: '0x843acfb41e5c0f1e0587c5b765d897ccdea8c4dd',
gasUsed: 43116,
logs: [ [Object] ],
logsBloom: '0x00000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000040000000000000000000000000000000040000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000',
status: true,
to: '0xb378420cde84c7a73eecfaee2fc91ed364f45e9f',
transactionHash: '0xa720d3da5eda30b9a816e68aaf41ddde0a536c2307734317f6b15243fb9b1829',
transactionIndex: 1,
rawLogs: [ [Object] ]
},
logs: [
{
address: '0xB378420cde84c7A73EecfAeE2fC91ED364F45E9F',
blockHash: '0x0a08ef737e0729f31a2bd428eb50273833f669ebfc95b622cdf405c67996ceef',
blockNumber: 7531341,
logIndex: 1,
removed: false,
transactionHash: '0xa720d3da5eda30b9a816e68aaf41ddde0a536c2307734317f6b15243fb9b1829',
transactionIndex: 1,
id: 'log_2be7cb64',
event: 'Redeem',
args: [Result]
}
]
}
truffle(ropsten)>
3.4 查询赎回后合约余额和用户的Token余额是否达到预期
truffle(ropsten)> web3.eth.getBalance(contractaddr) //合约的ETH余额
'2'
truffle(ropsten)> defi.balanceOf(accounts[0]) //用户的Token余额
BN { negative: 0, words: [ 2, <1 empty item> ], length: 1, red: null }
truffle(ropsten)>
4.其它部署调试工具
对于开发者而言,可使用可视化工具Remix
进行合约调试、部署
同一例子,详细参考以太坊ETH-智能合约开发-Remix使用