0%

以太坊DApp实战笔记

HelloWorld

编写合约

hello.sol:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pragma solidity ^0.5.2;

contract InfoContract {

string fName;
uint age;

function setInfo(string memory _fName, uint _age) public {
fName = _fName;
age = _age;
}

function getInfo() public view returns (string memory, uint) {
return (fName, age);
}
}

编译合约

1
solcjs hello.sol --abi --bin -o ./

生成hello_sol_InfoContract.abi和hello_sol_InfoContract.bin文件。

生成合约的Java文件

1
web3j solidity generate --solidityTypes -b hello_sol_hello.bin -a hello_sol_hello.abi -o ./ -p com.test

将生成的Java问价导入到项目中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
public class Hello extends Contract {
private static final String BINARY = "608060405234801561001057600080fd5b50610346806100206000396000f3fe608060405234801561001057600080fd5b5060043610610053576000357c0100000000000000000000000000000000000000000000000000000000900480635a9b0b89146100585780638262963b146100e2575b600080fd5b6100606101a7565b6040518080602001838152602001828103825284818151815260200191508051906020019080838360005b838110156100a657808201518184015260208101905061008b565b50505050905090810190601f1680156100d35780820380516001836020036101000a031916815260200191505b50935050505060405180910390f35b6101a5600480360360408110156100f857600080fd5b810190808035906020019064010000000081111561011557600080fd5b82018360208201111561012757600080fd5b8035906020019184600183028401116401000000008311171561014957600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929080359060200190929190505050610253565b005b6060600080600154818054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156102445780601f1061021957610100808354040283529160200191610244565b820191906000526020600020905b81548152906001019060200180831161022757829003601f168201915b50505050509150915091509091565b8160009080519060200190610269929190610275565b50806001819055505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106102b657805160ff19168380011785556102e4565b828001600101855582156102e4579182015b828111156102e35782518255916020019190600101906102c8565b5b5090506102f191906102f5565b5090565b61031791905b808211156103135760008160009055506001016102fb565b5090565b9056fea165627a7a7230582007c004e4d5d896b794dd7a63a8b6bdd16e95744ed1d0aa3c5b0eb8c4c7590e250029";

public static final String FUNC_GETINFO = "getInfo";

public static final String FUNC_SETINFO = "setInfo";

@Deprecated
protected Hello(String contractAddress, Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) {
super(BINARY, contractAddress, web3j, credentials, gasPrice, gasLimit);
}

protected Hello(String contractAddress, Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider) {
super(BINARY, contractAddress, web3j, credentials, contractGasProvider);
}

@Deprecated
protected Hello(String contractAddress, Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) {
super(BINARY, contractAddress, web3j, transactionManager, gasPrice, gasLimit);
}

protected Hello(String contractAddress, Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider) {
super(BINARY, contractAddress, web3j, transactionManager, contractGasProvider);
}

public RemoteCall<Tuple2<Utf8String, Uint256>> getInfo() {
final Function function = new Function(FUNC_GETINFO,
Arrays.<Type>asList(),
Arrays.<TypeReference<?>>asList(new TypeReference<Utf8String>() {}, new TypeReference<Uint256>() {}));
return new RemoteCall<Tuple2<Utf8String, Uint256>>(
new Callable<Tuple2<Utf8String, Uint256>>() {
@Override
public Tuple2<Utf8String, Uint256> call() throws Exception {
List<Type> results = executeCallMultipleValueReturn(function);
return new Tuple2<Utf8String, Uint256>(
(Utf8String) results.get(0),
(Uint256) results.get(1));
}
});
}

public RemoteCall<TransactionReceipt> setInfo(Utf8String _fName, Uint256 _age) {
final Function function = new Function(
FUNC_SETINFO,
Arrays.<Type>asList(_fName, _age),
Collections.<TypeReference<?>>emptyList());
return executeRemoteCallTransaction(function);
}

@Deprecated
public static Hello load(String contractAddress, Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) {
return new Hello(contractAddress, web3j, credentials, gasPrice, gasLimit);
}

@Deprecated
public static Hello load(String contractAddress, Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) {
return new Hello(contractAddress, web3j, transactionManager, gasPrice, gasLimit);
}

public static Hello load(String contractAddress, Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider) {
return new Hello(contractAddress, web3j, credentials, contractGasProvider);
}

public static Hello load(String contractAddress, Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider) {
return new Hello(contractAddress, web3j, transactionManager, contractGasProvider);
}

public static RemoteCall<Hello> deploy(Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider) {
return deployRemoteCall(Hello.class, web3j, credentials, contractGasProvider, BINARY, "");
}

@Deprecated
public static RemoteCall<Hello> deploy(Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) {
return deployRemoteCall(Hello.class, web3j, credentials, gasPrice, gasLimit, BINARY, "");
}

public static RemoteCall<Hello> deploy(Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider) {
return deployRemoteCall(Hello.class, web3j, transactionManager, contractGasProvider, BINARY, "");
}

@Deprecated
public static RemoteCall<Hello> deploy(Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) {
return deployRemoteCall(Hello.class, web3j, transactionManager, gasPrice, gasLimit, BINARY, "");
}
}

获取钱包

1. 创建新钱包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 创建新的钱包(钱包余额为0)
*
* @param password: 钱包的密码(不是私钥)
* @param path: 钱包文件的路径
* @return
* @throws NoSuchAlgorithmException
* @throws NoSuchProviderException
* @throws InvalidAlgorithmParameterException
* @throws CipherException
* @throws IOException
*/
private Credentials genCredentials(String password, String path) throws NoSuchAlgorithmException,
NoSuchProviderException, InvalidAlgorithmParameterException, CipherException, IOException {
String fileName = WalletUtils.generateNewWalletFile(password,
new File(path), false);
return WalletUtils.loadCredentials(password, path + "/" + fileName);
}

2. 获取现有钱包

1
2
3
4
5
6
7
8
/**
* 获取已存在钱包
* @param privKey
* @return
*/
private Credentials getCredentials(String privKey) {
return Credentials.create(privKey);
}

在项目中获取合约对象

1. 部署合约

1
2
3
4
5
private Hello ethDeploy() throws Exception {
Web3j web3 = Web3j.build(new HttpService("http://127.0.0.1:7545")); // 区块链客户端地址(ganache, geth等)
Credentials credentials = getCredentials("e35c46af1701a40df4b86385de0af9078c79cab9fb4e2ce1358b376cc24b0ab3");
return Hello.deploy(web3, credentials, GAS_PRICE, GAS_LIMIT).send();
}

2. 加载已部署合约

需要提前将合约部署到区块链中,我使用的是Remix和Ganache的方式模拟。

1
2
3
4
5
6
7
8
9
private Hello ethLoad(String contractAddress) throws Exception {
String password = "15927382215";
String filePath = "/home/hearing/WorkSpace/eth/test/src/main/resources";

Web3j web3 = Web3j.build(new HttpService("http://127.0.0.1:7545"));
Credentials credentials = getCredentials("e35c46af1701a40df4b86385de0af9078c79cab9fb4e2ce1358b376cc24b0ab3");

return Hello.load(contractAddress, web3, credentials, GAS_PRICE, GAS_LIMIT);
}

调用合约

1
2
3
4
5
TransactionReceipt receipt = contract.setInfo(new Utf8String("hearing"), new Uint256(21)).send();
if (receipt.isStatusOK()) {
Tuple2 tuple2 = contract.getInfo().send();
System.out.println(tuple2.getValue1());
}

CryptoZombie

erc721.sol

1
2
3
4
5
6
7
8
9
10
11
pragma solidity ^0.4.19;
contract ERC721 {
event Transfer(address indexed _from, address indexed _to, uint256 _tokenId);
event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId);

function balanceOf(address _owner) public view returns (uint256 _balance);
function ownerOf(uint256 _tokenId) public view returns (address _owner);
function transfer(address _to, uint256 _tokenId) public;
function approve(address _to, uint256 _tokenId) public;
function takeOwnership(uint256 _tokenId) public;
}

safemath.sol

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
pragma solidity ^0.4.18;

/**
* @title SafeMath
* @dev Math operations with safety checks that throw on error
*/
library SafeMath {

/**
* @dev Multiplies two numbers, throws on overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
uint256 c = a * b;
assert(c / a == b);
return c;
}

/**
* @dev Integer division of two numbers, truncating the quotient.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
// assert(b > 0); // Solidity automatically throws when dividing by 0
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}

/**
* @dev Substracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
assert(b <= a);
return a - b;
}

/**
* @dev Adds two numbers, throws on overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
assert(c >= a);
return c;
}
}

ownable.sol

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
pragma solidity ^0.4.19;
/**
* @title Ownable
* @dev The Ownable contract has an owner address, and provides basic authorization control
* functions, this simplifies the implementation of "user permissions".
*/
contract Ownable {
address public owner;

event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

/**
* @dev The Ownable constructor sets the original `owner` of the contract to the sender
* account.
*/
function Ownable() public {
owner = msg.sender;
}


/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(msg.sender == owner);
_;
}


/**
* @dev Allows the current owner to transfer control of the contract to a newOwner.
* @param newOwner The address to transfer ownership to.
*/
function transferOwnership(address newOwner) public onlyOwner {
require(newOwner != address(0));
OwnershipTransferred(owner, newOwner);
owner = newOwner;
}

}

zombiefactory.sol

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
pragma solidity ^0.4.19;

import "./ownable.sol";
import "./safemath.sol";

contract ZombieFactory is Ownable {

using SafeMath for uint256;

event NewZombie(uint zombieId, string name, uint dna);

uint dnaDigits = 16;
uint dnaModulus = 10 ** dnaDigits;
uint cooldownTime = 1 days;

struct Zombie {
string name;
uint dna;
uint32 level;
uint32 readyTime;
uint16 winCount;
uint16 lossCount;
}

Zombie[] public zombies;

mapping (uint => address) public zombieToOwner;
mapping (address => uint) ownerZombieCount;

function _createZombie(string _name, uint _dna) internal {
uint id = zombies.push(Zombie(_name, _dna, 1, uint32(now + cooldownTime), 0, 0)) - 1;
zombieToOwner[id] = msg.sender;
ownerZombieCount[msg.sender]++;
NewZombie(id, _name, _dna);
}

function _generateRandomDna(string _str) private view returns (uint) {
uint rand = uint(keccak256(_str));
return rand % dnaModulus;
}

function createRandomZombie(string _name) public {
require(ownerZombieCount[msg.sender] == 0);
uint randDna = _generateRandomDna(_name);
randDna = randDna - randDna % 100;
_createZombie(_name, randDna);
}

}

zombiefeeding.sol

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
pragma solidity ^0.4.19;

import "./zombiefactory.sol";

contract KittyInterface {
function getKitty(uint256 _id) external view returns (
bool isGestating,
bool isReady,
uint256 cooldownIndex,
uint256 nextActionAt,
uint256 siringWithId,
uint256 birthTime,
uint256 matronId,
uint256 sireId,
uint256 generation,
uint256 genes
);
}

contract ZombieFeeding is ZombieFactory {

KittyInterface kittyContract;

modifier onlyOwnerOf(uint _zombieId) {
require(msg.sender == zombieToOwner[_zombieId]);
_;
}

function setKittyContractAddress(address _address) external onlyOwner {
kittyContract = KittyInterface(_address);
}

function _triggerCooldown(Zombie storage _zombie) internal {
_zombie.readyTime = uint32(now + cooldownTime);
}

function _isReady(Zombie storage _zombie) internal view returns (bool) {
return (_zombie.readyTime <= now);
}

function feedAndMultiply(uint _zombieId, uint _targetDna, string _species) internal onlyOwnerOf(_zombieId) {
Zombie storage myZombie = zombies[_zombieId];
require(_isReady(myZombie));
_targetDna = _targetDna % dnaModulus;
uint newDna = (myZombie.dna + _targetDna) / 2;
if (keccak256(_species) == keccak256("kitty")) {
newDna = newDna - newDna % 100 + 99;
}
_createZombie("NoName", newDna);
_triggerCooldown(myZombie);
}

function feedOnKitty(uint _zombieId, uint _kittyId) public {
uint kittyDna;
(,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId);
feedAndMultiply(_zombieId, kittyDna, "kitty");
}
}

zombiehelper.sol

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
pragma solidity ^0.4.19;

import "./zombiefeeding.sol";

contract ZombieHelper is ZombieFeeding {

uint levelUpFee = 0.001 ether;

modifier aboveLevel(uint _level, uint _zombieId) {
require(zombies[_zombieId].level >= _level);
_;
}

function withdraw() external onlyOwner {
owner.transfer(this.balance);
}

function setLevelUpFee(uint _fee) external onlyOwner {
levelUpFee = _fee;
}

function levelUp(uint _zombieId) external payable {
require(msg.value == levelUpFee);
zombies[_zombieId].level++;
}

function changeName(uint _zombieId, string _newName) external aboveLevel(2, _zombieId) onlyOwnerOf(_zombieId) {
zombies[_zombieId].name = _newName;
}

function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_zombieId) {
zombies[_zombieId].dna = _newDna;
}

function getZombiesByOwner(address _owner) external view returns(uint[]) {
uint[] memory result = new uint[](ownerZombieCount[_owner]);
uint counter = 0;
for (uint i = 0; i < zombies.length; i++) {
if (zombieToOwner[i] == _owner) {
result[counter] = i;
counter++;
}
}
return result;
}

}

zombieattach.sol

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
pragma solidity ^0.4.19;

import "./zombiehelper.sol";

contract ZombieBattle is ZombieHelper {
uint randNonce = 0;
uint attackVictoryProbability = 70;

function randMod(uint _modulus) internal returns(uint) {
randNonce++;
return uint(keccak256(now, msg.sender, randNonce)) % _modulus;
}

function attack(uint _zombieId, uint _targetId) external onlyOwnerOf(_zombieId) {
Zombie storage myZombie = zombies[_zombieId];
Zombie storage enemyZombie = zombies[_targetId];
uint rand = randMod(100);
if (rand <= attackVictoryProbability) {
myZombie.winCount++;
myZombie.level++;
enemyZombie.lossCount++;
feedAndMultiply(_zombieId, enemyZombie.dna, "zombie");
} else {
myZombie.lossCount++;
enemyZombie.winCount++;
_triggerCooldown(myZombie);
}
}
}

zombieownership.sol

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
pragma solidity ^0.4.19;

import "./zombieattack.sol";
import "./erc721.sol";
import "./safemath.sol";

/// TODO: 把这里变成 natspec 标准的注释把
contract ZombieOwnership is ZombieAttack, ERC721 {

using SafeMath for uint256;

mapping (uint => address) zombieApprovals;

function balanceOf(address _owner) public view returns (uint256 _balance) {
return ownerZombieCount[_owner];
}

function ownerOf(uint256 _tokenId) public view returns (address _owner) {
return zombieToOwner[_tokenId];
}

function _transfer(address _from, address _to, uint256 _tokenId) private {
ownerZombieCount[_to] = ownerZombieCount[_to].add(1);
ownerZombieCount[msg.sender] = ownerZombieCount[msg.sender].sub(1);
zombieToOwner[_tokenId] = _to;
Transfer(_from, _to, _tokenId);
}

function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) {
_transfer(msg.sender, _to, _tokenId);
}

function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) {
zombieApprovals[_tokenId] = _to;
Approval(msg.sender, _to, _tokenId);
}

function takeOwnership(uint256 _tokenId) public {
require(zombieApprovals[_tokenId] == msg.sender);
address owner = ownerOf(_tokenId);
_transfer(owner, msg.sender, _tokenId);
}
}

Geth 常用命令

启动

1
geth cmd console
  • -–identity:指定节点 ID;
  • -–rpc:表示开启 HTTP-RPC 服务;
  • -–rpcport:指定 HTTP-RPC 服务监听端口号(默认为 8545);
  • -–datadir:指定区块链数据的存储位置;
  • -–port:指定和其他节点连接所用的端口号(默认为 30303);
  • -–nodiscover:关闭节点发现机制,防止加入有同样初始配置的陌生节点。

控制台中的对象

控制台是一个交互式的JavaScript执行环境,在这里面可以执行JavaScript代码,其中也内置了一些用来操作以太坊的JavaScript对象,可以直接使用这些对象。这些对象主要包括:

  • eth:包含一些跟操作区块链相关的方法;
  • net:包含一些查看p2p网络状态的方法;
  • admin:包含一些与管理节点相关的方法;
  • miner:包含启动&停止挖矿的一些方法;
  • personal:主要包含一些管理账户的方法;
  • txpool:包含一些查看交易内存池的方法;
  • web3:包含了以上对象,还包含一些单位换算的方法。

控制台操作

进入以太坊Javascript Console后,就可以使用里面的内置对象做一些操作,常用命令有:

  • personal.newAccount():创建账户;
  • personal.unlockAccount():解锁账户;
  • eth.accounts:枚举系统中的账户;
  • eth.getBalance():查看账户余额,返回值的单位是 Wei(Wei 是以太坊中最小货币面额单位,类似比特币中的聪,1 ether = 10^18 Wei);
  • eth.blockNumber:列出区块总数;
  • eth.getTransaction():获取交易;
  • eth.getBlock():获取区块;
  • miner.start():开始挖矿;
  • miner.stop():停止挖矿;
  • web3.fromWei():把 wei 转为如下种类的以太坊单位(还有其他代币token单位);
    • kwei/ada
    • mwei/babbage
    • gwei/shannon
    • szabo
    • finney
    • ether
    • kether/grand/einstein
    • mether
    • gether
    • tether
  • web3.toWei():把以太坊单位(包含代币单位)转为 wei;
  • txpool.status:交易池中的状态;
  • admin.addPeer():连接到其他节点;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
> web3.fromWei("425000000000000000000", "ether")
"425"

> web3.toWei("1", "ether")
"1000000000000000000"

# 转账:转账必须解锁账户,默认账户无需解锁
> personal.unlockAccount(user1)
Unlock account 0xf09dca9e10f3f1d0adfe9af7beeeb579c1d1dd37
Passphrase:
true
> eth.accounts
["0xd7ec36444d13cc079eb116b4f2602cffcdc9adee", "0xf09dca9e10f3f1d0adfe9af7beeeb579c1e7456b3ded2fa250545de4de1"]
> eth.sendTransaction({from:"0xd7ec36444d13cc079eb116b4f2602cffcdc9adee",to:"0xf09dcc1d1dd37",value:web3.toWei(6,"ether")})
"0x68dc8f5db24371bbb3acca13ff15f3561cae4a6d8753f62beab1356380211949"
> eth.getBalance(user1)
6000000000000000000
> eth.getBalance(user0)
1.15792089237316195423570985008687907853269984665640564039451584007913129639927e+77

Mist

Mist是以太坊官方的在线钱包管理工具,通过 Mist 可以很方便的连接上私有网络,从而更好的开发、调试、测试。可以在github上下载。

1
mist --rpc url

Truffle

创建项目

  • 使用 truffle unbox 下载任意 truffle boxes。
  • 使用 truffle init 初始化一个不包含智能合约的项目。

目录结构:

  • contracts/:合约目录
  • migrations/:脚本化部署文件
  • test/:测试智能合约和应用
  • truffle.js:配置文件

编译合约

命令

1
truffle compile

Truffle仅默认编译自上次编译后被修改过的文件,来减少不必要的编译。如果你想编译全部文件,可以使用–compile-all选项。

约定

Truffle需要定义的合约名称和文件名准确匹配。举例来说,如果文件名为MyContract.sol,那么合约文件须为如下两者之一:

1
2
3
4
5
6
7
contract MyContract {
...
}
// or
library MyContract {
...
}

这种匹配是区分大小写的,也就是说大小写也要一致,推荐大写每一个开头字母。

移植

命令

1
truffle migrate

这个命令会执行所有的位于migrations目录内的移植脚本。如果你之前的移植是成功执行的。truffle migrate仅会执行新创建的移植。如果没有新的移植脚本,这个命令不同执行任何操作。可以使用选项–reset来从头执行移植脚本。

移植脚本文件

一个样例文件如下:4_example_migration.js

1
2
3
4
module.exports = function(deployer) {
// deployment steps
deployer.deploy(MyContract);
};

移植js里的exports的函数接受一个deployer对象作为第一个参数。这个对象用于发布过程,提供了一个清晰的语法支持,同时提供一些通过的合约部署职责,比如保存发布的文件以备稍后使用。deployer对象是用来缓存(stage)发布任务的主要操作接口。

初始移植

Truffle需要一个移植合约来使用移植特性。这个合约内需要指定的接口,但你可以按你的意味修改合约。对大多数工程来说,这个合约会在第一次移植时进行的第一次部署,后续都不会再更新。通过truffle init创建一个全新工程时,你会获得一个默认的合约。

文件名:contracts/Migration.sol:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
contract Migrations {
address public owner;

// A function with the signature `last_completed_migration()`, returning a uint, is required.
uint public last_completed_migration;

modifier restricted() {
if (msg.sender == owner) _
}

function Migrations() {
owner = msg.sender;
}

// A function with the signature `setCompleted(uint)` is required.
function setCompleted(uint completed) restricted {
last_completed_migration = completed;
}

function upgrade(address new_address) restricted {
Migrations upgraded = Migrations(new_address);
upgraded.setCompleted(last_completed_migration);
}
}

如果你想使用移植特性,你必须在你第一次部署合约时,部署这个合约。可以使用如下方式来创建一次移植。

文件名:migrations/1_initial_migrations.js

1
2
3
4
module.exports = function(deployer) {
// Deploy the Migrations contract as our only task
deployer.deploy(Migrations);
};

由此,你可以接着创建递增的数字前缀来部署其它合约。

部署器(deployer)

你的移植文件会使用部署器来缓存部署任务。所以,你可以按一定顺序排列发布任务,他们会按正确顺序执行。

1
2
3
// Stage deploying A before B
deployer.deploy(A);
deployer.deploy(B);

另一选中可选的部署方式是使用Promise。将部署任务做成一个队列,是否部署依赖于前一个合约的执行情况。

1
2
3
4
// Deploy A, then deploy B, passing in A's newly deployed address
deployer.deploy(A).then(function() {
return deployer.deploy(B, A.address);
});

如果你想更清晰,你也可以选择实现一个Promise链。

网络相关

要实现不同条件的不同部署步骤,移植代码中需要第二个参数network。示例如下:

1
2
3
4
5
6
module.exports = function(deployer, network) {
// Add demo data if we're not deploying to the live network.
if (network != "live") {
deployer.exec("add_demo_data.js");
}
}

移植到truffle开发环境

truffle有内置的私有连用于测试,这是电脑上的本地网络。使用如下命令创建私有连并与其交互:truffle develop。在 truffle 交互控制台中,可以省略 truffle 前缀,例如可以直接使用 compile 。

移植到 Ganache

在truffle配置文件中配置:

1
2
3
4
5
6
7
8
9
module.exports = {
networks: {
development: {
host: "127.0.0.1",
port: 7545,
network_id: "*"
}
}
};

部署

1
truffle migrate

ICO 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
pragma solidity 0.4.20;
/**
* 一个简单的代币合约。
*/
contract token {

string public standard = 'yuyangray';
string public name; //代币名称
string public symbol; //代币符号比如'$'
//代币单位,展示的小数点后面多少个0,和以太币一样后面是是18个0
uint8 public decimals = 2;

uint256 public totalSupply; //代币总量
/* 这里每个地址对应的是代币的数量,而不是捐赠的以太币的数量 */
mapping (address => uint256) public balanceOf;

event Transfer(address indexed from, address indexed to, uint256 value); //转帐通知事件


/* 初始化合约,并且把初始的所有代币都给这合约的创建者
* @param _owned 合约的管理者
* @param tokenName 代币名称
* @param tokenSymbol 代币符号
*/
function token(address _owned, string tokenName, string tokenSymbol) public {
//合约的管理者获得的代币总量
balanceOf[_owned] = totalSupply;

name = tokenName;
symbol = tokenSymbol;

}

/**
* 转帐,具体可以根据自己的需求来实现
* @param _to address 接受代币的地址
* @param _value uint256 接受代币的数量
*/
function transfer(address _to, uint256 _value) public {
//从发送者减掉发送额
balanceOf[msg.sender] -= _value;

//给接收者加上相同的量
balanceOf[_to] += _value;

//通知任何监听该交易的客户端
Transfer(msg.sender, _to, _value);
}

/**
* 增加代币,并将代币发送给捐赠新用户
* @param _to address 接受代币的地址
* @param _amount uint256 接受代币的数量
*/
function issue(address _to, uint256 _amount) public {
totalSupply = totalSupply + _amount;
balanceOf[_to] += _amount;

//通知任何监听该交易的客户端
Transfer(this, _to, _amount);
}
}

/**
* 众筹合约
*/
contract Crowdsale is token {
address public beneficiary = msg.sender; //受益人地址,测试时为合约创建者
uint public fundingGoal; //众筹目标,单位是ether
uint public amountRaised; //已筹集金额数量, 单位是ether
uint public deadline; //截止时间
uint public price; //代币价格
bool public fundingGoalReached = false; //达成众筹目标
bool public crowdsaleClosed = false; //众筹关闭


mapping(address => uint256) public balance; //保存众筹地址及对应的以太币数量

// 受益人将众筹金额转走的通知
event GoalReached(address _beneficiary, uint _amountRaised);

// 用来记录众筹资金变动的通知,_isContribution表示是否是捐赠,因为有可能是捐赠者退出或发起者转移众筹资金
event FundTransfer(address _backer, uint _amount, bool _isContribution);

/**
* 初始化构造函数
*
* @param fundingGoalInEthers 众筹以太币总量
* @param durationInMinutes 众筹截止,单位是分钟
* @param tokenName 代币名称
* @param tokenSymbol 代币符号
*/
function Crowdsale(
uint fundingGoalInEthers,
uint durationInMinutes,
string tokenName,
string tokenSymbol
) token(this, tokenName, tokenSymbol) public {
fundingGoal = fundingGoalInEthers * 1 ether;
deadline = now + durationInMinutes * 1 minutes;
price = 500 finney; //1个以太币可以买 2 个代币
}


/**
* 默认函数
*
* 默认函数,可以向合约直接打款
*/
function () payable public {

//判断是否关闭众筹
require(!crowdsaleClosed);
uint amount = msg.value;

//捐款人的金额累加
balance[msg.sender] += amount;

//捐款总额累加
amountRaised += amount;

//转帐操作,转多少代币给捐款人
issue(msg.sender, amount / price * 10 ** uint256(decimals));
FundTransfer(msg.sender, amount, true);
}

/**
* 判断是否已经过了众筹截止限期
*/
modifier afterDeadline() { if (now >= deadline) _; }

/**
* 检测众筹目标是否已经达到
*/
function checkGoalReached() afterDeadline public {
if (amountRaised >= fundingGoal){
//达成众筹目标
fundingGoalReached = true;
GoalReached(beneficiary, amountRaised);
}

//关闭众筹
crowdsaleClosed = true;
}


/**
* 收回资金
*
* 检查是否达到了目标或时间限制,如果有,并且达到了资金目标,
* 将全部金额发送给受益人。如果没有达到目标,每个贡献者都可以退出
* 他们贡献的金额
* 注:这里代码应该是限制了众筹时间结束且众筹目标没有达成的情况下才允许退出。如果去掉限制条件afterDeadline,应该是可以允许众筹时间还未到且众筹目标没有达成的情况下退出
*/
function safeWithdrawal() afterDeadline public {

//如果没有达成众筹目标
if (!fundingGoalReached) {
//获取合约调用者已捐款余额
uint amount = balance[msg.sender];

if (amount > 0) {
//返回合约发起者所有余额
msg.sender.transfer(amount);
FundTransfer(msg.sender, amount, false);
balance[msg.sender] = 0;
}
}

//如果达成众筹目标,并且合约调用者是受益人
if (fundingGoalReached && beneficiary == msg.sender) {

//将所有捐款从合约中给受益人
beneficiary.transfer(amountRaised);

FundTransfer(beneficiary, amount, false);
}
}
}

代币示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
pragma solidity 0.4.20;

interface tokenRecipient { function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) external; }

/**
* owned 是一个管理者
*/
contract owned {
address public owner;

/**
* 初台化构造函数
*/
function owned () public {
owner = msg.sender;
}

/**
* 判断当前合约调用者是否是管理员
*/
modifier onlyOwner {
require (msg.sender == owner);
_;
}

/**
* 指派一个新的管理员
* @param newOwner address 新的管理员帐户地址
*/
function transferOwnership(address newOwner) onlyOwner public {
if (newOwner != address(0)) {
owner = newOwner;
}
}
}

/**
* @title 基础版的代币合约
*/
contract token {
/* 公共变量 */
string public standard = 'https://mshk.top';
string public name; //代币名称
string public symbol; //代币符号比如'$'
uint8 public decimals = 18; //代币单位,展示的小数点后面多少个0,和以太币一样后面是是18个0
uint256 public totalSupply; //代币总量

/*记录所有余额的映射*/
mapping (address => uint256) public balanceOf;
mapping (address => mapping (address => uint256)) public allowance;

/* 在区块链上创建一个事件,用以通知客户端*/
event Transfer(address indexed from, address indexed to, uint256 value); //转帐通知事件
event Burn(address indexed from, uint256 value); //减去用户余额事件

/* 初始化合约,并且把初始的所有代币都给这合约的创建者
* @param initialSupply 代币的总数
* @param tokenName 代币名称
* @param tokenSymbol 代币符号
*/
function token(uint256 initialSupply, string tokenName, string tokenSymbol) public {

//初始化总量
totalSupply = initialSupply * 10 ** uint256(decimals); //以太币是10^18,后面18个0,所以默认decimals是18

//给指定帐户初始化代币总量,初始化用于奖励合约创建者
//balanceOf[msg.sender] = totalSupply;
balanceOf[this] = totalSupply;

name = tokenName;
symbol = tokenSymbol;

}


/**
* 私有方法从一个帐户发送给另一个帐户代币
* @param _from address 发送代币的地址
* @param _to address 接受代币的地址
* @param _value uint256 接受代币的数量
*/
function _transfer(address _from, address _to, uint256 _value) internal {

//避免转帐的地址是0x0
require(_to != 0x0);

//检查发送者是否拥有足够余额
require(balanceOf[_from] >= _value);

//检查是否溢出
require(balanceOf[_to] + _value > balanceOf[_to]);

//保存数据用于后面的判断
uint previousBalances = balanceOf[_from] + balanceOf[_to];

//从发送者减掉发送额
balanceOf[_from] -= _value;

//给接收者加上相同的量
balanceOf[_to] += _value;

//通知任何监听该交易的客户端
Transfer(_from, _to, _value);

//判断买、卖双方的数据是否和转换前一致
assert(balanceOf[_from] + balanceOf[_to] == previousBalances);

}

/**
* 从主帐户合约调用者发送给别人代币
* @param _to address 接受代币的地址
* @param _value uint256 接受代币的数量
*/
function transfer(address _to, uint256 _value) public {
_transfer(msg.sender, _to, _value);
}

/**
* 从某个指定的帐户中,向另一个帐户发送代币
*
* 调用过程,会检查设置的允许最大交易额
*
* @param _from address 发送者地址
* @param _to address 接受者地址
* @param _value uint256 要转移的代币数量
* @return success 是否交易成功
*/
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
//检查发送者是否拥有足够余额
require(_value <= allowance[_from][msg.sender]); // Check allowance

allowance[_from][msg.sender] -= _value;

_transfer(_from, _to, _value);

return true;
}

/**
* 设置帐户允许支付的最大金额
*
* 一般在智能合约的时候,避免支付过多,造成风险
*
* @param _spender 帐户地址
* @param _value 金额
*/
function approve(address _spender, uint256 _value) public returns (bool success) {
allowance[msg.sender][_spender] = _value;
return true;
}

/**
* 设置帐户允许支付的最大金额
*
* 一般在智能合约的时候,避免支付过多,造成风险,加入时间参数,可以在 tokenRecipient 中做其他操作
*
* @param _spender 帐户地址
* @param _value 金额
* @param _extraData 操作的时间
*/
function approveAndCall(address _spender, uint256 _value, bytes _extraData) public returns (bool success) {
tokenRecipient spender = tokenRecipient(_spender);
if (approve(_spender, _value)) {
spender.receiveApproval(msg.sender, _value, this, _extraData);
return true;
}
}

/**
* 减少代币调用者的余额
*
* 操作以后是不可逆的
*
* @param _value 要删除的数量
*/
function burn(uint256 _value) public returns (bool success) {
//检查帐户余额是否大于要减去的值
require(balanceOf[msg.sender] >= _value); // Check if the sender has enough

//给指定帐户减去余额
balanceOf[msg.sender] -= _value;

//代币问题做相应扣除
totalSupply -= _value;

Burn(msg.sender, _value);
return true;
}

/**
* 删除帐户的余额(含其他帐户)
*
* 删除以后是不可逆的
*
* @param _from 要操作的帐户地址
* @param _value 要减去的数量
*/
function burnFrom(address _from, uint256 _value) public returns (bool success) {

//检查帐户余额是否大于要减去的值
require(balanceOf[_from] >= _value);

//检查 其他帐户 的余额是否够使用
require(_value <= allowance[_from][msg.sender]);

//减掉代币
balanceOf[_from] -= _value;
allowance[_from][msg.sender] -= _value;

//更新总量
totalSupply -= _value;
Burn(_from, _value);
return true;
}



/**
* 匿名方法,预防有人向这合约发送以太币
*/
/*function() {
//return; // Prevents accidental sending of ether
}*/
}

/**
* @title 高级版代币
* 增加冻结用户、挖矿、根据指定汇率购买(售出)代币价格的功能
*/
contract MyAdvancedToken is owned, token {

//卖出的汇率,一个代币,可以卖出多少个以太币,单位是wei
uint256 public sellPrice;

//买入的汇率,1个以太币,可以买几个代币
uint256 public buyPrice;

//是否冻结帐户的列表
mapping (address => bool) public frozenAccount;

//定义一个事件,当有资产被冻结的时候,通知正在监听事件的客户端
event FrozenFunds(address target, bool frozen);


/*初始化合约,并且把初始的所有的令牌都给这合约的创建者
* @param initialSupply 所有币的总数
* @param tokenName 代币名称
* @param tokenSymbol 代币符号
* @param centralMinter 是否指定其他帐户为合约所有者,为0是去中心化
*/
function MyAdvancedToken (
uint256 initialSupply,
string tokenName,
string tokenSymbol,
address centralMinter
) token (initialSupply, tokenName, tokenSymbol) public {

//设置合约的管理者
if(centralMinter != 0 ) owner = centralMinter;

sellPrice = 2; //设置1个单位的代币(单位是wei),能够卖出2个以太币
buyPrice = 4; //设置1个以太币,可以买0.25个代币
}


/**
* 私有方法,从指定帐户转出余额
* @param _from address 发送代币的地址
* @param _to address 接受代币的地址
* @param _value uint256 接受代币的数量
*/
function _transfer(address _from, address _to, uint _value) internal {

//避免转帐的地址是0x0
require (_to != 0x0);

//检查发送者是否拥有足够余额
require (balanceOf[_from] > _value);

//检查是否溢出
require (balanceOf[_to] + _value > balanceOf[_to]);

//检查 冻结帐户
require(!frozenAccount[_from]);
require(!frozenAccount[_to]);



//从发送者减掉发送额
balanceOf[_from] -= _value;

//给接收者加上相同的量
balanceOf[_to] += _value;

//通知任何监听该交易的客户端
Transfer(_from, _to, _value);

}

/**
* 合约拥有者,可以为指定帐户创造一些代币
* @param target address 帐户地址
* @param mintedAmount uint256 增加的金额(单位是wei)
*/
function mintToken(address target, uint256 mintedAmount) onlyOwner public {

//给指定地址增加代币,同时总量也相加
balanceOf[target] += mintedAmount;
totalSupply += mintedAmount;


Transfer(0, this, mintedAmount);
Transfer(this, target, mintedAmount);
}

/**
* 增加冻结帐户名称
*
* 你可能需要监管功能以便你能控制谁可以/谁不可以使用你创建的代币合约
*
* @param target address 帐户地址
* @param freeze bool 是否冻结
*/
function freezeAccount(address target, bool freeze) onlyOwner public {
frozenAccount[target] = freeze;
FrozenFunds(target, freeze);
}

/**
* 设置买卖价格
*
* 如果你想让ether(或其他代币)为你的代币进行背书,以便可以市场价自动化买卖代币,我们可以这么做。如果要使用浮动的价格,也可以在这里设置
*
* @param newSellPrice 新的卖出价格
* @param newBuyPrice 新的买入价格
*/
function setPrices(uint256 newSellPrice, uint256 newBuyPrice) onlyOwner public {
sellPrice = newSellPrice;
buyPrice = newBuyPrice;
}

/**
* 使用以太币购买代币
*/
function buy() payable public {
uint amount = msg.value / buyPrice;

_transfer(this, msg.sender, amount);
}

/**
* @dev 卖出代币
* @return 要卖出的数量(单位是wei)
*/
function sell(uint256 amount) public {

//检查合约的余额是否充足
require(this.balance >= amount * sellPrice);

_transfer(msg.sender, this, amount);

msg.sender.transfer(amount * sellPrice);
}
}

Truffle+OpenZeppelin ERC20代币实现

1、新建项目

1
truffle unbox react-box

2、安装zeppelin-solidity

1
npm install zeppelin-solidity

3、创建标准的代币合约

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pragma solidity ^0.4.4;

import "zeppelin-solidity/contracts/token/ERC20/StandardToken.sol";

contract BloggerCoin is StandardToken {
string public name = "BloggerCoin";
string public symbol = "BLC";
uint8 public decimals = 2;
uint256 public INITIAL_SUPPLY = 666666;

function BloggerCoin() public {
totalSupply_ = INITIAL_SUPPLY;
balances[msg.sender] = INITIAL_SUPPLY;
}
}
INITIAL_SUPPLY变量定义了在合约部署时,代币将创建的数量。

4、编译和部署

在migrations/目录下建立一个3_deploy_bloggercoin.js文件,内容如下:

1
2
3
4
5
var BloggerCoin = artifacts.require("./BloggerCoin.sol");

module.exports = function(deployer) {
deployer.deploy(BloggerCoin);
};

编译部署:

1
2
truffle compile
truffle migrate 部署