一、Solidity 语言
Solidity 是一种智能合约高级语言,运行在 Ethereum 虚拟机(EVM:Ethereum Virtual Machine)之上。
Solidity 的语法接近于 Javascript,是一种面向对象的语言,而且围绕着 Solidity 的各种开发工具链,都是使用属于 Javascript 生态系的 npm 来提供的。
Solidity语言:http://www.tryblockchain.org
Solidity documentation:https://solidity.readthedocs.org
GitHub:https://github.com/ethereum/solidity
二、编辑器
我目前是使用 Atom 搭配 solidity(linter-solium) 插件来开发。
三、Truffle 框架
Truffle 是针对基于以太坊的 Solidity 语言的一套开发框架,本身基于 Javascript。
四、工具安装
1、安装 Node.js 和 NPM;
2、安装 Truffle 框架,在终端运行命令:
$ sudo npm install -g truffle
3、安装 Truffle 客户端 EtherumJS TestRPC:
$ sudo npm install -g ganache-cli
五、启动 TestRPC
使用以下命令来启动以太坊测试环境:
$ ganache-cli
可以看到启动后自动建立了10个帐号(Accounts),与每个帐号对应的私钥(Private Key)。每个帐号中都有100个测试用的以太币(Ether)。要注意以太坊测试环境仅运行在內存中,因此每次重开时都会回到全新的状态。
六、创建项目
重新打开一个终端窗口,运行以下命令以创建项目:
$ mkdir SmartContractProject
$ cd SmartContractProject/
$ mkdir HelloWorld
$ cd HelloWorld/
$ truffle init
目录结构:
-
contracts/
:Truffle默认的合约文件存放地址; -
migrations/
:存放发布的脚本文件; -
test/
:存放测试应用和合约的测试文件; -
truffle.js 和 truffle-config.js
:Truffle的配置文件。
七、新建 HelloWorld 合约
在 contracts
文件夹下新建 HelloWorld.sol 文件:
$ cd contracts/
$ truffle create contract HelloWorld
HelloWorld.sol
文件内容如下:
讲解:
pragma solidity ^0.4.4;
第一行指名目前使用的 solidity 版本,不同版本的 solidity 可能会编译出不同的 bytecode。^
代表兼容 solidity 0.4.4 ~ 0.4.9
的版本。
function HelloWorld() {
// constructor
}
}
contract
关键字类似于其他语言中较常见的 class
。因为solidity 是专为智能合约(Contact)设计的语言,声明 contract
后即内置了开发智能合约所需的功能。也可以把这句理解为 class HelloWorld extends Contract
。
函数的结构与其他程序类似,但如果有传入的参数或回传值,需要指定参数或回传值的类型(type
)。
八、向合约中添加新的方法
function sayHello() returns (string) {
return ("Hello World");
}
九、编译合约
现在执行 truffle compile
命令,我们可以将 HelloWorld.sol
原始码编译成 Ethereum bytecode
:
$ cd ..
$ truffle compile
【注意出现了警告】
重新修改 HelloWorld.sol
文件中的方法:
pragma solidity ^0.4.4;
contract HelloWorld {
function HelloWorld() public {
// constructor
}
function sayHello() public pure returns (string) {
return ("Hello World");
}
}
保存后重新运行命令:
$ truffle compile
命令运行成功后会多出一个 build
的目录,如下:
在 HelloWorld
文件夹下面的 build/contracts
文件夹下面会看见 HelloWorld.json
文件:
十、修改 truffle.js 文件内容:
添加以下内容到 truffle.js 文件并保存 :
networks: {
development: {
host:"localhost",
port:8545,
network_id:"*" // 匹配任何network id
}
}
十一、部署合约
在 migrations
目录下创建移植文件:
$ cd migrations/
$ truffle create migration 2_deploy_helloworld
修改文件名及文件内容如下:
var HelloWorld = artifacts.require("./HelloWorld.sol");
module.exports = function(deployer) {
deployer.deploy(HelloWorld);
};
使用 artifacts.require
语句来取得准备部署的合约。
使用deployer.deploy
语句将合约部署到区块链上。
这边HelloWorld
是 contract
的名称而不是文件名。
因此可以用此语法读入任一 .sol
文件中的任一合约。
现在执行 truffle migrate
命令:
$ truffle migrate
部署成功你会看到 启动 TestRPC 的终端窗口会有以下变化:
十二、与合约互动
Truffle 提供命令行工具,执行 truffle console
命令后,可用Javascript 来和刚刚部署的合约互动:
$ cd ..
$ truffle console
$ HelloWorld.deployed().then(instance => contract = instance)
yutaos-MacBook-Pro:migrations yutaozhang$ cd ..
yutaos-MacBook-Pro:HelloWorld yutaozhang$ truffle console
truffle(development)> HelloWorld.deployed().then(instance => contract = instance)
TruffleContract {
constructor:
{ [Function: TruffleContract]
_static_methods:
{ setProvider: [Function: setProvider],
new: [Function: new],
at: [Function: at],
deployed: [Function: deployed],
defaults: [Function: defaults],
hasNetwork: [Function: hasNetwork],
isDeployed: [Function: isDeployed],
detectNetwork: [Function: detectNetwork],
setNetwork: [Function: setNetwork],
resetAddress: [Function: resetAddress],
link: [Function: link],
clone: [Function: clone],
addProp: [Function: addProp],
toJSON: [Function: toJSON] },
_properties:
{ contract_name: [Object],
contractName: [Object],
abi: [Object],
network: [Function: network],
networks: [Function: networks],
address: [Object],
links: [Function: links],
events: [Function: events],
binary: [Function: binary],
deployedBinary: [Function: deployedBinary],
unlinked_binary: [Object],
bytecode: [Object],
deployedBytecode: [Object],
sourceMap: [Object],
deployedSourceMap: [Object],
source: [Object],
sourcePath: [Object],
ast: [Object],
compiler: [Object],
schema_version: [Function: schema_version],
schemaVersion: [Function: schemaVersion],
updated_at: [Function: updated_at],
updatedAt: [Function: updatedAt] },
_property_values: {},
_json:
{ contractName: 'HelloWorld',
abi: [Array],
bytecode: '0x6060604052341561000f57600080fd5b6101578061001e6000396000f300606060405260043610610041576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063ef5fb05b14610046575b600080fd5b341561005157600080fd5b6100596100d4565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561009957808201518184015260208101905061007e565b50505050905090810190601f1680156100c65780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6100dc610117565b6040805190810160405280600b81526020017f48656c6c6f20576f726c64000000000000000000000000000000000000000000815250905090565b6020604051908101604052806000815250905600a165627a7a72305820b825548240e74063f6a0c0dd2a2b787ed288e3359cb7682b670288211d37f14f0029',
deployedBytecode: '0x606060405260043610610041576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063ef5fb05b14610046575b600080fd5b341561005157600080fd5b6100596100d4565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561009957808201518184015260208101905061007e565b50505050905090810190601f1680156100c65780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6100dc610117565b6040805190810160405280600b81526020017f48656c6c6f20576f726c64000000000000000000000000000000000000000000815250905090565b6020604051908101604052806000815250905600a165627a7a72305820b825548240e74063f6a0c0dd2a2b787ed288e3359cb7682b670288211d37f14f0029',
sourceMap: '25:164:0:-;;;49:53;;;;;;;;25:164;;;;;;',
deployedSourceMap: '25:164:0:-;;;;;;;;;;;;;;;;;;;;;;;;106:80;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;23:1:-1;8:100;33:3;30:1;27:2;8:100;;;99:1;94:3;90;84:5;80:1;75:3;71;64:6;52:2;49:1;45:3;40:15;;8:100;;;12:14;3:109;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;106:80:0;147:6;;:::i;:::-;161:22;;;;;;;;;;;;;;;;;;;;106:80;:::o;25:164::-;;;;;;;;;;;;;;;:::o',
source: 'pragma solidity ^0.4.4;\n\ncontract HelloWorld {\n function HelloWorld() public {\n // constructor\n }\n\n function sayHello() public pure returns (string) {\n return ("Hello World");\n}\n\n}\n',
sourcePath: '/Users/yutaozhang/SmartContractProject/HelloWorld/contracts/HelloWorld.sol',
ast: [Object],
compiler: [Object],
networks: [Object],
schemaVersion: '1.0.1',
updatedAt: '2018-01-16T09:55:58.496Z' },
setProvider: [Function: bound setProvider],
new: [Function: bound new],
at: [Function: bound at],
deployed: [Function: bound deployed],
defaults: [Function: bound defaults],
hasNetwork: [Function: bound hasNetwork],
isDeployed: [Function: bound isDeployed],
detectNetwork: [Function: bound detectNetwork],
setNetwork: [Function: bound setNetwork],
resetAddress: [Function: bound resetAddress],
link: [Function: bound link],
clone: [Function: bound clone],
addProp: [Function: bound addProp],
toJSON: [Function: bound toJSON],
web3:
Web3 {
_requestManager: [Object],
currentProvider: [Object],
eth: [Object],
db: [Object],
shh: [Object],
net: [Object],
personal: [Object],
bzz: [Object],
settings: [Object],
version: [Object],
providers: [Object],
_extend: [Object] },
class_defaults:
{ from: '0xcb009af857f4e65d3b234f491ffebe76cee6cbe7',
gas: 6721975,
gasPrice: 100000000000 },
currentProvider:
HttpProvider {
host: 'http://localhost:8545',
timeout: 0,
user: undefined,
password: undefined,
send: [Function],
sendAsync: [Function],
_alreadyWrapped: true },
network_id: '1516087157119' },
abi:
[ { constant: true,
inputs: [],
name: 'sayHello',
outputs: [Array],
payable: false,
stateMutability: 'pure',
type: 'function' },
{ inputs: [],
payable: false,
stateMutability: 'nonpayable',
type: 'constructor' } ],
contract:
Contract {
_eth:
Eth {
_requestManager: [Object],
getBalance: [Object],
getStorageAt: [Object],
getCode: [Object],
getBlock: [Object],
getUncle: [Object],
getCompilers: [Object],
getBlockTransactionCount: [Object],
getBlockUncleCount: [Object],
getTransaction: [Object],
getTransactionFromBlock: [Object],
getTransactionReceipt: [Object],
getTransactionCount: [Object],
call: [Object],
estimateGas: [Object],
sendRawTransaction: [Object],
signTransaction: [Object],
sendTransaction: [Object],
sign: [Object],
compile: [Object],
submitWork: [Object],
getWork: [Object],
coinbase: [Getter],
getCoinbase: [Object],
mining: [Getter],
getMining: [Object],
hashrate: [Getter],
getHashrate: [Object],
syncing: [Getter],
getSyncing: [Object],
gasPrice: [Getter],
getGasPrice: [Object],
accounts: [Getter],
getAccounts: [Object],
blockNumber: [Getter],
getBlockNumber: [Object],
protocolVersion: [Getter],
getProtocolVersion: [Object],
iban: [Object],
sendIBANTransaction: [Function: bound transfer] },
transactionHash: null,
address: '0x742c7a36d3a2b65a5607180fdee7fd5befc8a164',
abi: [ [Object], [Object] ],
sayHello:
{ [Function: bound ]
request: [Function: bound ],
call: [Function: bound ],
sendTransaction: [Function: bound ],
estimateGas: [Function: bound ],
getData: [Function: bound ],
'': [Circular] },
allEvents: [Function: bound ] },
sayHello:
{ [Function]
call: [Function],
sendTransaction: [Function],
request: [Function: bound ],
estimateGas: [Function] },
sendTransaction: [Function],
send: [Function],
allEvents: [Function: bound ],
address: '0x742c7a36d3a2b65a5607180fdee7fd5befc8a164',
transactionHash: null }
truffle(development)>
讲解:
HelloWorld.deployed().then(instance => contract = instance)
truffle console
中预载了 truffle-contract
函数库,以方便操作部署到区块链上的合约。
这边使用 HelloWorld.deployed().then
语句来取得 HelloWorld
合约的 Instance
(实例),并存到 contract
变量中,以方便后续的调用。
输入以下命令:
$ contract.sayHello.call()
这里直接呼叫 contract.sayHello()
也会得到一样的结果。truffle-contract
提供使用 call()
来读取只读 (read only)
的数据,这样就不需提供 gas
。因此如果遇到的操作需要向区块链写入数据,我们就不能用 call
语句了。
如此一来,我们已写好并部署完成了第一个智能合约,也验证了合约确实可以运作。
十三、加入新方法
我们在 HelloWorld.sol
中再加入一个 echo
方法,echo
方法接受输入一个参数,并回传传送的参数`:
function echo(string name) public pure returns (string) {
return name;
}
由于更新了合约内容,我们需要先重新新编译一次,将编译结果部署到 testrpc
上,再透过 truffle console
执行看看结果。
$ truffle compile
$ truffle migrate --reset
$ truffle console
$ let contract
HelloWorld.deployed().then(instance => contract = instance)
有一点需要注意的,是这次如果还是用 $ truffle migrate
命令,我们会得到如下信息:
$ truffle migrate
Using network 'development'.
Network up to date.
Truffle 会告诉你现在网络上的合约都已是最新的,但事实上刚刚程序中新增的方法并没有更新到内存块链上。要更新内存块链上已部署的程序,需要改写 migrations
中的脚本,还好我们开发用的内存块链是怎么修改都没关系的 testrpc
,可以使用 truffle migrate --reset
命令直接重新在 testrpc
上部署一次。
总结
欢迎留言讨论,有错误请指出,谢谢!
【联系我(QQ:3500229193)或者加入社群,请戳这里!】
参考链接
- https://yq.aliyun.com/articles/212944
- http://www.tryblockchain.org
- http://truffle.tryblockchain.org/index.html
- Atom 官网:https://atom.io
- Atom 中文社区:https://atom-china.org
- Node.js 官网:https://nodejs.org
- Node.js 中文网:http://nodejs.cn
- NPM:https://www.npmjs.com/get-npm
更新日志
- 2018.01.16 第一次更新
- 2018.02.23 第二次更新
- 2018.08.07 第三次更新