以太坊智能合约学习笔记1

最近在学习智能合约,记录下学习笔记

环境

以太坊的智能合约是使用Solidity编程语言,所以首先需要搭建相关的环境

  1. 以太坊

使用golang开发的客户端geth: https://github.com/ethereum/go-ethereum

相关命令:

1
2
3
4
5
6
7
$ geth account list      # 列出以太坊账号
$ geth account new # 如果没有账号使用该命令新建一个
$ geth # 下面列出可能需要使用的启动参数
--unlock addr # 解锁账号, 也可以到控制台使用personal解锁
--testnet # 切换到Ropsten network测试网络
--datadir dir # 设置以太坊数据存储目录
--rpc --rpcaddr 0.0.0.0 --rpccorsdomain "*" # 开启RPC,默认端口8545,可以通过web3.js调用

下面举例使用Ropsten测试网络:

1
2
3
4
5
$ screen -S geth
$ ./geth --testnet
CTRL+A D
$ ./geth attach
>
  1. solc编译器

geth最新版已经移除了solc编译器,所以需要我们自己在本地安装一个:

1
$ npm install -g solc

编译出abi:

1
$ solcjs --abi -o output test.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
$ cat test.sol 
pragma solidity ^0.4.18;

import 'zeppelin-solidity/contracts/ownership/Ownable.sol';

contract King is Ownable {

address public king;
uint public prize;

function King() public payable {
king = msg.sender;
prize = msg.value;
}

function() external payable {
require(msg.value >= prize || msg.sender == owner);
king.transfer(msg.value);
king = msg.sender;
prize = msg.value;
}
}
$ npm install zeppelin-solidity
$ solcjs --abi -o output node_modules/zeppelin-solidity/contracts/ownership/Ownable.sol king.sol
$ cat output/king_sol_King.abi
[{"constant":false,"inputs":[],"name":"renounceOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"king","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"prize","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[],"payable":true,"stateMutability":"payable","type":"constructor"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"previousOwner","type":"address"}],"name":"OwnershipRenounced","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"previousOwner","type":"address"},{"indexed":true,"name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"}]
  1. IDE

ide使用的是remix-ide,可以使用在线版的: http://remix.ethereum.org/

也可以本地自己搭一个:

1
2
$ npm install -g remix-ide
$ remix-ide
  1. 以太坊数据查询

公链数据查询: https://etherscan.io/
Ropsten测试链: https://ropsten.etherscan.io/
其他查询网站: https://www.etherchain.org/

以太坊/区块链基础知识

在以太坊中任何操作(转账,部署合约,调用合约函数)都是以交易的形式存在,每进行一次交易,都会加入pending列表中,同步给其他节点,当有一个节点挖出了一个区块,将会把pending列表中的所以交易打包

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
> eth.blockNumber
5833460 # geth客户端同步的最大区块数
> eth.getBlock(0) # 获取区块信息,0是创世区块
{
difficulty: 17179869184,
extraData: "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa",
gasLimit: 5000,
gasUsed: 0,
hash: "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3",
logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
miner: "0x0000000000000000000000000000000000000000",
mixHash: "0x0000000000000000000000000000000000000000000000000000000000000000",
nonce: "0x0000000000000042",
number: 0,
parentHash: "0x0000000000000000000000000000000000000000000000000000000000000000",
receiptsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
size: 540,
stateRoot: "0xd7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544",
timestamp: 0,
totalDifficulty: 17179869184,
transactions: [],
transactionsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
uncles: []
}
> eth.getBlock(5833460) # 获取最新区块信息
{
difficulty: 3405471720099834,
extraData: "0x436f72746578",
gasLimit: 7992236,
gasUsed: 7978832,
hash: "0x1d7b7d1e6926c048089ecf02b0ea30d87027b168c594f4ba8fa91043e9a2faca",
logsBloom: "0x021000818840a288010400002000200000002c0000408204340201204c0801000488101400000000448040431001040242401800014004900410000008e0a0000000205428000418104c050842407088081000020824200000000100102900008260104c2222050285105082040008301000041149040000006023141080000000a000080000000081401589c08002005000018008800002020a000204002810428c100006c098008280480075000900010814888809101004800000008801001400080350000008482003a9403020a0420308002088080404000002122a2488009020042b220600119502c10400760422400000445002000048be1228000000",
miner: "0x9435d50503aee35c8757ae4933f7a0ab56597805",
mixHash: "0xec3cff1e31ea6049ca89447e43833c2abf9c3c34f15ab679be016656dd44fd6e",
nonce: "0xa02c0ff7551c38a0",
number: 5833460,
parentHash: "0xbeb2058d273049a7bedc78284bb4b3d29195e46d3183b559bdb5e1e597291588",
receiptsRoot: "0xb935e9bbcf039e7359694f1070b82886f8248fb195c8db1e48afc3b1c0eb9dae",
sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
size: 37212,
stateRoot: "0x5416a9c99d906d546f61f537016eac65d3dbe7745f4f5d7686ec9656057d4f42",
timestamp: 1529659415,
totalDifficulty: 4.911074548581714109311e+21,
transactions: ["0x5f2a644707ec534cda626d1fd8cb0f679d0b4f5c475f95183e3326e38ec596d4"......] # 区块中打包的交易地址
transactionsRoot: "0x97331e3aadd2e144f30354051a687d554c451af8d86237b36cbdc711111ea14f",
uncles: []
}

在以太坊中,重要的有三个地址,个人账户地址,合约地址,交易地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 获取交易地址信息
> eth.getTransaction("0x5f2a644707ec534cda626d1fd8cb0f679d0b4f5c475f95183e3326e38ec596d4")
{
blockHash: "0x1d7b7d1e6926c048089ecf02b0ea30d87027b168c594f4ba8fa91043e9a2faca",
blockNumber: 5833460,
from: "0xd2bb24e348651970e0a3ae50ea89e5ff2b37c09b",
gas: 45000,
gasPrice: 122000000000,
hash: "0x5f2a644707ec534cda626d1fd8cb0f679d0b4f5c475f95183e3326e38ec596d4",
input: "0x",
nonce: 21,
r: "0x5b431a69cd3ea332aad413e3e63a88a03f33305445e430257ab05b6eed1c56",
s: "0x395069ef7f4848d8019c5a63f16e21b0d56baca887aa5cd3cda76b2936bf77a2",
to: "0x2737062e58d2d52e7726df72e1da0828f89bd3b7",
transactionIndex: 0,
v: "0x25",
value: 34100000000000000
}

不管是个人账户还是合约地址,都能储存以太币余额,两者的区别是,个人账户地址的所有者具有该地址的公私钥,能进行签名,所以能向任意个人/合约地址转账,而合约地址不具有公私钥,所以如果一个合约不具有转账的函数,这个合约将会成为一个黑洞,以太币只能存进行,但任何人都没法取出来

个人账户和合约地址还有一个区别,合约地址能储存数据,个人账户不行:

1
2
3
4
> eth.getCode("0xd2bb24e348651970e0a3ae50ea89e5ff2b37c09b")  # 个人账户地址
"0x"
> eth.getCode("0x0b76544F6C413a555F309Bf76260d1E02377c02A") # 合约地址
"0x606060405236156101255763ffffffff7c01000000000000000000000000.......

智能合约会被转换成字节码储存在区块中, 但是在https://etherscan.io/网站中,一些智能合约能查看源码,这是由于合约发布者在该网站主动公布了源码,否则我们只能获取到字节码,所以智能合约的反编译也是一个有必要的工作

在以太坊中,基本单位是Wei,我们可以使用下面的函数将其和ether进行转换:

1
2
3
4
> web3.toWei(1)
"1000000000000000000"
> web3.fromWei(1000000000000000000)
"1"

通过命令行控制智能合约

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
pragma solidity ^0.4.24;

contract test{

address public owner;
uint256 public value;

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

function test1() returns (string){
return "hello";
}

function () payable {
value = msg.value;
}

}

使用remix-ide在ropsten测试链上进行部署,得到合约地址: 0x22BAf676C25fB4a9462a6f3571769Ad98Ce6B2Cb

在ide上获取该合约的abi,去掉回车:

1
2
>>> abi.replace("\n", "").replace("\t", "")
'[{"constant": false,"inputs": [],"name": "test1","outputs": [{"name": "","type": "string"}],"payable": false,"stateMutability": "nonpayable","type": "function"},{"payable": true,"stateMutability": "payable","type": "fallback"},{"inputs": [],"payable": false,"stateMutability": "nonpayable","type": "constructor"},{"constant": true,"inputs": [],"name": "owner","outputs": [{"name": "","type": "address"}],"payable": false,"stateMutability": "view","type": "function"},{"constant": true,"inputs": [],"name": "value","outputs": [{"name": "","type": "uint256"}],"payable": false,"stateMutability": "view","type": "function"}]'

然后在geth命令行进行操作:

1
2
3
abi = [{"constant": false,"inputs": [],"name": "test1","outputs": [{"name": "","type": "string"}],"payable": false,"stateMutability": "nonpayable","type": "function"},{"payable": true,"stateMutability": "payable","type": "fallback"},{"inputs": [],"payable": false,"stateMutability": "nonpayable","type": "constructor"},{"constant": true,"inputs": [],"name": "owner","outputs": [{"name": "","type": "address"}],"payable": false,"stateMutability": "view","type": "function"},{"constant": true,"inputs": [],"name": "value","outputs": [{"name": "","type": "uint256"}],"payable": false,"stateMutability": "view","type": "function"}]
contract = eth.contract(abi)
test=contract.at("0x22BAf676C25fB4a9462a6f3571769Ad98Ce6B2Cb")

然后我们可以对该合约进行操作:

1
2
3
4
> test.owner()     # 部署人的地址
"0x0109dea8b64d87a26e7fe9af6400375099c78fdd"
> test.test1({from:eth.accounts[0]})
"0xf642261cb4794ba71be3a1ecf0de0b6b613961a37a7b5e213dae06bd79e1177d"

因为调用函数需要消耗gas,所以需要指定是哪个账号调用该合约函数,在web3js中调用函数的参数分别是,该函数本身需要的参数,以太坊相关信息参数(一个字典),回调函数

以太坊相关信息参数字典:

1
2
3
4
5
6
{
from: addr,
to: addr,
value: 1 # 转账1wei
gas: 1000 # 调用该函数的过程中只允许消耗1000gas
}

向智能合约转账触发回退函数:

1
2
3
4
5
6
7
8
9
10
> test.value()
0
> eth.getBalance(test.address)
0
> eth.sendTransaction({from:eth.accounts[0], to: test.address, value:web3.toWei(0.5)})
"0xb411711d15a49fdfa242f6be178d3e5aae24646569c60da9fe8f0da555416070"
>eth.getBalance(test.address)
500000000000000000
> test.value()
500000000000000000

因为该合约没有写转账代码,所以这0.5以太币就永远待在这个合约当中了,不过因为是在测试链中,所以舍得~

文章目录
  1. 1. 环境
  2. 2. 以太坊/区块链基础知识
  3. 3. 通过命令行控制智能合约