一、核心概念
(1)基本概念
地址(Address):用20字节来表示它的地址;
状态(State)
(2)账户分类
外部账户(EOA):外部拥有的账户,是通过私钥来控制,没有相关联的代码;
合约账户:当我们把合约的字节码部署到区块链之后,就会有一个特定的地址来标识这个合约,这个地址就是表现为一个合约的账户;
如果合约里写有构造函数
contract Hello {
function Hello() public {
}
}
如果是使用了新的版本编译器。可能会报错
Warning: Defining constructors as functions with the same name as the contract is deprecated. Use “constructor(…) { … }” instead.
最新版语言编译需要这么写:
contract Hello {
constructors() public {
}
}
最小单位:1 Wei
10^9 Wei = 1 Gwei
10^12 Wei = 1 szabo(萨博)
10^15 Wei = 1 finey(芬尼)
10^18 Wei = 1 Ether
pragma solidity ^0.4.0;
//标准代币的接口
contract ERC20Interface{
string public name;//代币的名字
string public symbol;//代币的简写
uint8 public decimals;//代币的小数点后位数,一个代币最多可以分成多少份,如果一个代币可以切分为10份,那么小数点后位数就是1位(即0.1)
uint256 public totalSupply;//代币的供应量(总共发行了多少代币)
//获取拥有的账户余额
function balanceOf(address _owner) public view returns (uint256 balance);
//交易代币:向地址(_to)发送数量为_value的代币,发送成功则返回true
function transfer(address _to,uint256 _value) public returns(bool success);
//将数量为_value的代币从地址_from交易到地址_to,而且该函数需要启用Transfer事件;该函数一般用于取款(提款)的流程,允许合约代表你进行交易;
function transferFrom(address _from,address _to,uint256 _value) public returns(bool success);
// 允许_spender这个地址的账户从你的账户提款,直到达到你设置的限额_value(说白了就是允许别人操作你的账户,但是操作的金额是有限的)
function approve(address _spender,uint256 _value) public returns(bool success);
//返回_spender这个地址的账户仍允许从_owner这个地址的账户提款多少(即别人还允许从你的账户上花多少钱)
function allowance(address _owner,address _spender) public view returns(uint256 remaining);
//交易事件:在代币交易的时候必须触发该事件,且包括交易数量为0个代币的情况(如在调用transfer和transferFrom函数就必须触发该事件)
event Transfer(address indexed _from, address indexed _to,uint256 _value);
//委托事件:在调用approve函数的时候必须触发Approval事件
event Approval(address indexed _owner,address indexed _spender,uint256 _value);
// 触发事件了就能对事件进行监听,类似于回调
}
contract ERC20 is ERC20Interface{
//类似于字典(键=>值组合)
mapping(address=>uint256) public balanceOf;
mapping(address=>mapping(address=>uint256)) internal allowed;
constructor () public{
name = "YuanToken";
symbol = "Yuan";
decimals = 0;//小数点后位数为0,即一个代币最多只能分一份
totalSupply = 1000000;
balanceOf[msg.sender] = totalSupply; //Give all the tokens to the creator
}
//这个方法和上面的mapping(address=>uint256) public balanceOf是等价的,当定义了balanceOf之后编译器会默认生成balanceOf这个方法
function balanceOf(address _owner) public view returns (uint256 balance)
{
return balanceOf[_owner];
}
function transfer(address _to,uint256 _value) public returns(bool success)
{
require(_to != address(0));
require(balanceOf[msg.sender] >= _value);
//防止溢出:比如之前的余额加上收到的交易代币,可能超出该数字类型所能表达的最大值,溢出之后余额反而会比之前的余额少;
require(balanceOf[_to] + _value >= balanceOf[_to]);
balanceOf[msg.sender] -= _value;
balanceOf[_to] += _value;
//触发(调用)Transfer事件
emit Transfer(msg.sender,_to,_value);
success = true;
}
function transferFrom(address _from,address _to,uint256 _value) public returns(bool success)
{
require(_to != address(0));
require(balanceOf[_from] >= _value);
require(allowed[_from][msg.sender] >= _value);
//check for overflow
require(balanceOf[_to] + _value >= balanceOf[_to]);
balanceOf[_from] -= _value;
balanceOf[_to] += _value;
emit Transfer(_from,_to,_value);
success = true;
}
function approve(address _spender,uint256 _value) public returns(bool success)
{
//_spender这个地址的账户可以从当前账户操控的金额
allowed[msg.sender][_spender] = _value;
//触发委托(Approval)事件
emit Approval(msg.sender,_spender,_value);
success = true;
}
function allowance(address _owner,address _spender) public view returns(uint256 remaining)
{
return allowed[_owner][_spender];
}
}
参考ERC-20 Token Standard
(1)安装truffle
前提:先安装好了Node.js
npm install -g truffle
(2)为Truffle工程创建文件夹并打开
先进入你要存放Truffle工程的路径
mkdir pet-shop //创建文件夹
cd pet-shop //打开文件夹
(3)创建一个新的工程
truffle init
工程创建成功之后,工程目录的结构如下所示:
contracts/: 智能合约的存放目录
migrations/: 脚本化的部署文件的存放目录
test/: 测试App和智能合约的目录
truffle.js: Truffle配置文件
若要下载一些tuffle的模板工程,可使用如下命令:
truffle unbox
# 换成truffle模板工程名
(1)安装Ganache
注意从官网上下载的Windows版的Ganache的后缀是“.appx”,是不能直接安装的,可以通过将其后缀修改为“.zip”,然后在解压文件夹,在app文件夹中直接找到“Ganache.exe”文件,直接打开即可。(Win10系统下安装Ganache)
(2)编写部署合约的脚本
参考Migration files创建一个部署合约的脚本
注意:部署合约的脚本放在”..\migrations\”文件夹下
如文件..\migrations\2_deploy_contracts.js
var Adoption = artifacts.require("Adoption"); //注意:“Adoption”表示合约的名字
module.exports = function(deployer) {
// deployment steps
deployer.deploy(Adoption);
};
部署合约的脚本写好之后,因为合约需要运行在区块链上,所以我们需要为合约准备一个区块链的环境,这时候我们就需要用到Ganache。
(3)配置truffle所需连接的网络
参考Location在truffle.js文件中配置truffle所需连接的网络
module.exports = {
// See
// to customize your Truffle configuration!
networks: {
development: {
host: "127.0.0.1",
port: 7545, //如果连接Ganache的话,端口号就是7545
network_id: "*" // Match any network id
}
}
};
(4)编写合约测试用例
例如:下面是一个简单的宠物领养合约的测试用例(…\test\TestAdoption.sol)
pragma solidity ^0.4.20;
import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../contracts/Adoption.sol";
contract testAdoption{
Adoption adoption = Adoption(DeployedAddresses.Adoption());
//测试用户是否可以领养该宠物(检测返回的宠物的id是否和我们期望的id是否一致)
function testUserCanAdopt( ) public{
uint returnId = adoption.adopt(8);
uint expected = 8;
Assert.equal(returnId,expected,"Adoption of petId 8 should be record.");
}
//测试合约的地址是否是该宠物领养者的地址
function testGetAdoptersAddressByPetId() public {
address adopterAddress = adoption.adopters(8);
address expected = this; //当前地址
Assert.equal(adopterAddress,expected,"Owner of petId 8 should be record. ");
}
//测试数组里面的该宠物的领养地址是否和合约中的地址匹配
function testGetAdoptersAddressByPetIdInArray() public{
address[16] memory adoptersAddresses = adoption.getAdopters();
address expected = this;
Assert.equal(adoptersAddresses[8],expected,"Owner of petId 8 should be in array");
}
}
在工程目录下运行下列命令运行测试用例:
truffle test
参考WRITING TESTS IN SOLIDITY
(5) 初始化web环境
(1)将当前工程转化为一个可以使用npm来管理的工程
npm init
然后填写相应信息(如package name、version、description等),之后会自动生成一个package.json文件
(2)安装lite-server服务器
npm install lite-server
(3)在工程目录下创建一个Src文件目录,并将index.html放入其中;
(4)为lite-server配置站点目录
在工程目录下创建一个bs-config.json文件以告诉server在哪查找文件,并配置如下:
{
"server": {
"baseDir":["./Src/","./build/contracts/"]
}
}
(5)在package.json文件中配置一个“dev”属性,使运行”npm run dev”命令时,运行lite-server服务器。配置部分如下
"scripts": {
"dev":"lite-server",
"test": "echo \"Error: no test specified\" && exit 1"
},
(6)启动web服务器,运行web工程
npm run dev
(1)错误一
(2)错误二
在MetaMask中确认交易时出现如下错误:
ALERT: [ethjs-rpc] rpc error with payload {“id”:6832843685104,”jsonrpc”:”2.0”,”params”:[“0xf8881285174876e80082f6e7940422a0813dc48f53d9c2b845224bf0ecccb51c8280a48588b2c500000000000000000000000000000000000000000000000000000000000000012aa02a5660fb170846987d49b012eaf76b1f1b66b8f8b2b2519f3ca0c5d05f0ba5caa007e1d90886289e2925aed9367fde707936aac5dd5fa1e84fea9b38f6a36f08b7”],”method”:”eth_sendRawTransaction”} Error: the tx doesn’t have the correct nonce. account has nonce of: 10 tx has nonce of: 18
解决方法:
(3)错误三
出现如下错误:
ALERT: Trying to call a function on a non-contract address.
出错原因:
因为当前区块链是空的,所以在此之前应先创建几个区块。
解决方法:
在项目的终端下运行如下命令:
truffle test //运行测试用例的时候会创建几个区块
Web3.js docs
truffle-contract
Web3.js API