以太坊学习

1
remixd -s D:\GoCode\以太坊 ##连接本地

合约结构

状态变量

变量值会永久存储在合约的存储空间

函数

智能合约中的一个可执行单元

函数修饰符

在函数声明时,增强函数语义,满足条件时才会执行函数

事件

用于日志输出,便于跟踪调试

结构体类型

用户自定义的将几个变量组合在一起形成的类型

枚举类型

特殊的自定义类型,类型的所有值可枚举的情况

类型

1.值类型

指变量在传递过程中是将数值完整的拷贝一份,再赋值给新的变量,这种方式需要开辟新的内存空间,效率较低,两个变量完全独立,修改一个不会影响另一个。

整型、布尔、地址、定长字节数组、有理数和整型、枚举类型、函数。

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

contract Student{
uint _score = 80;
uint _num = 0;

//public可以输入变量执行
function exectue(uint score) public{
changeScore(score);
}
//privte不能输入变量执行,只能其他的函数进行调用
function changeScore(uint score) private {
_score = score;
_num = 90;
}
//constant可以使数据可见
function getScore() constant returns (uint) {
return _score;
}

function getNum() constant returns (uint){
return _num;
}
}

2.引用类型

不定长字节数组、字符串、数组、结构体

memory:相当于值拷贝

storage:引用

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

contract Student{
string _name = "ck";

function exectue() public{
changeName(_name);
}
//storage表示引用,因此传递的是地址
function changeName(string storage name) private {
bytes(name)[0] = "C";
}

function getName() constant returns (string){
return _name;
}
}

3.布尔类型

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
pragma solidity ^0.4.20;

contract BoolTest{
uint v1 = 10 ;
uint v2 = 20 ;
bool b1 = true;
bool b2 = false;

function f1() constant returns (bool){
return !b1;
}

function f2() constant returns (bool){
return b1 && b2;
}

function f3() constant returns (bool){
return b1 || b2;
}

function f4() constant returns (bool){
return v1 == v2;
}

function f5() constant returns (bool){
return v1 != v2;
}
}

4.整型

int(有负号) , uint(无负号),支持int8 到int256,uint同理,int默认为int256,uint默认为uint256

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
pragma solidity ^0.4.21;


contract IntegerTest {
/*
//&,|,^(异或),~(位取反)
10 uint8 1010
4 uint8 0100

& 0000 0
| 1110 14
^ 1110 14

~ 3 0000,0011 1100 12
1111,1100
1111,1111 - 3
2^8 -1 -3 = 256 -4 = 252
*/
uint8 a = 10;
uint8 b = 4;
uint8 c = 3;

function f1() constant public returns (bool) {
return a & b == 0;//同为真则真
}

function f2() constant public returns (bool) {
return a | b == 14;//一个为真则真
}
function f3() constant public returns (bool) {
return a ^ b == 14;//同为假不同为真
}
function f4() constant public returns (bool){
return ~c == 252;//按位取反 因为使uint8,所以是252
}
}

5.地址类型

以太坊地址的长度,大小为20个字节,160位,可以用一个uint160编码。地址是所有合约的基础,所有的合约都会继承地址对象,也可以随时用一个地址串得到对应代码进行调用。

地址类型的成员:

  1. 属性:balance
  2. 方法:send( )、transfer( )、 call( ) 、delegatecall( ) 、callcode( )

1. 地址类型与整型转换

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

contract AddressTest{
address _add1 = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
address _add2 = 0x7b96aF9Bd211cBf6BA5b0dd53aa61Dc5806b6AcE;
uint160 public _num = 0;
address public _add;


function addressTOuint160() public returns (uint160) {
_num = uint160(_add1);
return _num;
}

function uint160TOaddress() public returns (address){
_add = address(_num);
return _add;
}
}

2. balance获取余额

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//获取当前地址余额
pragma solidity ^0.4.24;

contract addressSender{
address public _owner ;

//获取查询目标地址
function senderTest() public {
_owner = msg.sender;
}

//获取余额
function getOwnerBalance()constant public returns (uint) {
return _owner.balance;
}

//获取查询人地址
function getInvoker()constant public returns (address) {
return msg.sender;
}
}

4. This和payable转账

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//This获取合约地址

pragma solidity ^0.4.24;

contract addressThisTest{

uint _money;
function addressThis() payable public{
_money=msg.value;
}

function getThis() constant public returns (address){
return this;
}

function getBalance() constant public returns(uint256){
return this.balance;
}
function getMoney() constant public returns(uint){
return _money;
}
}

5. 转账transfer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//msg.sender获取的是获取本人地址
pragma solidity ^0.4.20;

contract TransferTest{
address CKNO2 = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2;
uint _money;
function transfer() payable public{
CKNO2.transfer(msg.value);
}

function getThis() constant public returns (address){
return msg.sender;
}

function getBalance() constant public returns(uint256){
return CKNO2.balance;
}
}

6. 转账send

Send在转账时不会向transfer一样抛出异常,而是返回一个false。

1
2
3
4
5
6
7
8
9
10
11
pragma solidity ^0.4.20;

contract TransferTest{
address CKNO2 = 0xCF208DD52bB63b08616c1a1bC498b48c544858A8;
bool res;

function transfer() payable public returns(bool){
res = CKNO2.send(msg.value);
return res;
}
}

6.枚举类型

枚举类型是一种用户自定义类型。他可以显式的转换与整数进行转换,但不能进行隐式转换。显示的转换会在运行时检查数值范围,如果不匹配,将会引起异常。枚举类型应该至少由一名成员,枚举元素默认为uint8,当元素数量足够多时,会自动变为uint16,第一个元素默认为0,使用超出范围的数值时会报错。

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
pragma solidity ^0.4.0;

contract enumTest {
enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill } //uint8类型 看下标
ActionChoices _choice; //默认为0
ActionChoices defaultChoice = ActionChoices.GoStraight;//为GoStraight的下标,2

function setGoStraight(ActionChoices choice) public {
_choice = choice;
}

function getChoice() constant public returns (ActionChoices) {
return _choice;
}

function getDefaultChoice() constant public returns (uint) {
//因为返回值要求为uint类型,即使默认为uint8,但是依旧要进行显式转换
return uint(defaultChoice);//返回值为2
}

function isGoLeft(ActionChoices choice) constant public returns (bool){
if (choice == ActionChoices.GoLeft){//GoLeft为0
return true;
}
return false;
}
}

7.函数类型

函数本身也是一个特殊的变量,它可以当作变量赋值,也可以作为函数参数进行传递。它包括:

  • 内部类型(internal):仅能在合约内部调用,如当前代码块中、内部库函数中、继承的函数中。
  • 外部类型(external):仅能在合约外部调用,由地址和函数签名两部分组成,可作为外部函数调用的参数,或者由外部函数调用返回。
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
pragma solidity ^0.4.5;

contract FuntionTest{
uint public v1;
uint public v2;

/*
function v1() constant returns (uint) {
return v1;
}
*/
//仅能在合约内部调用
function internalFunc() internal{
v1 = 10;
}
//仅能在合约外部调用
function externalFunc() external returns (uint res){
v2 = 20;
return v2;
}

function resetV2() public {
v2 = 0;
}

function callFunc() {
//直接使用内部的方式调用
internalFunc(); //<--- 合约内部直接调用,正确

//不能在内部调用一个外部函数,会报编译错误。
//Error: Undeclared identifier.
//externalFunc(); //<--- 外部合约可以调用

//不能通过`external`的方式调用一个`internal`
//Member "internalFunc" not found or not visible after argument-dependent lookup in contract FuntionTest
//this.internalFunc(); //<--- this相当于外部调用

//使用`this`以`external`的方式调用一个外部函数
this.externalFunc();
}
}

contract Son is FuntionTest {
function callInternalFunc() public{
internalFunc();
//externalFunc();
}
}

contract FunctionTest1{
uint public v3;
function externalCall(FuntionTest ft){
//调用另一个合约的外部函数
v3 = ft.externalFunc();

//不能调用另一个合约的内部函数
//Error: Member "internalFunc" not found or not visible after argument-dependent lookup in contract FuntionTest
//ft.internalFunc();
}

function resetV3() public {
v3 = 0;
}
}

8.数组

1. 定长数组

bytes1….bytes32

  • 字节不可修改
  • 长度不可修改
  • 支持length(只读)
  • 支持下标索引
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pragma solidity ^0.4.20 ;

contract fixedArray {
//bytes1...bytes32

//bytes1 b1 = "12";//error
bytes2 public b2 = "ck"; //0x636b
bytes3 public b3 = "ck"; //0x636b00

uint public len = b3.length; //length=3
//b3.length = 10 ; //error

bytes1 public b4 = b2[0]; //0x63->c
//b2[1] = "K"; //error
}

2.不定长数组

内容和长度均可修改,包括以下三种:

  1. 类型[长度]

    • 动态数组
    • 可以修改
    • 可以改变长度
    • 支持length、push、pop方法

    可以使用new关键字创建一个memory的数组,与stroage数组不同的是,不能通过length,push,pop的长度来修改数组大小属性。

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

    contract unfixedTypeLenTest {
    uint public len2;
    function memoryfunc(){
    //通过new创建数组
    uint[] memory value = new uint[](10);
    //uint [10] value = {1,2,3,4,5}; 内容可变,长度不可变
    value[0] = 1;
    //value.push(2); error
    //value.length = 20; error
    }

    uint[] public value2;
    function storagefunc(){
    value2 = new uint[](10);//默认为stroage数组
    value2[0] = 1;//修改数组指定下标的数
    value2.length = 20;//修改长度
    value2.push(2);//进栈下标为20的数为2
    len2 = value2.length;//20
    }
    }
  2. bytes

    • 动态字节数组
    • 引用类型
    • 支持下标索引
    • 支持length、push方法
    • 可以修改
    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
    pragma solidity ^0.4.20;

    contract unFixedBytes {

    bytes public _name = new bytes(1);
    bytes public _name2;

    function setLength(uint length) {
    _name.length = length;
    }

    function getLength() constant returns (uint) {
    return _name.length;
    }

    function setName(bytes name){
    _name = name;
    }

    function changeName(bytes1 name){
    _name[0] = name;
    }

    function setInside(){
    _name = "helloWorld";
    _name2 = "HELLOWORLD";
    }
    }
  3. string

    • 动态尺寸的UTF-8编码字符串,是特殊的可变字节数组
    • 引用类型
    • 不支持下标索引
    • 可以修改(需通过bytes转换),bytes和string可以自由转换
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
pragma solidity ^0.4.20;

contract unfixedStringTest {
string public _name = "lily";

function nameBytes() constant returns (bytes) {
return bytes(_name);

}

function nameLength() constant returns (uint) {
//uint len = _name.length;
return bytes(_name).length;
}

function changeName() public {
bytes(_name)[0] = "L";

//s2[0] = "H"; //ERROR,不支持下标索引
}

function changeLength() {
bytes(_name).length = 15;
bytes(_name)[14] = "x";
// bytes(_name)[19] = "x";
}
}

bytes与string之间的转换:

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
pragma solidity ^0.4.20;


contract Test {

bytes10 b10 = 0x68656c6c6f776f726c64; //helloworld
//bytes bs10 = b10;

bytes public bs10 = new bytes(b10.length);

//1. 固定字节数组转动态字节数组
function fixedBytesToBytes() public{
for (uint i = 0; i< b10.length; i++){
bs10[i] = b10[i];
}
}


//2. 动态字节数组转string
string greeting = "helloworld";
bytes public b1;
function StringToBytes() public {
b1 = bytes(greeting);
}

//3.string转动态字节数组
string public str3;
function BytesToString() public {
str3 = string(bs10);
}

//4. fixed bytes to String
function FiexBytesToString(){
//string tmp = string(b10);
}
}

9.结构体

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
pragma solidity ^0.4.20;

contract Test{
//定义结构体
struct Student{
string name;
uint age;
uint score;
string sex;
}

//两种初始化方法
Student public stu1= Student("CK" , 22 , 100 , "boy");
Student public stu2 = Student({
name : "YX",
age :23,
score : 99,
sex : "girl"
});

//定义一个结构体数组
Student [] public Students;
function assign() public{
//将结构体添加至数组中
Students.push(stu1);
Students.push(stu2);
}
}

10.字典

字典(映射)是一对一的键值关系,关键字mapping

语法:

1
mapping(_keyType => _valueType)

键的类型允许除映射外的所有类型,如数组、合约、枚举、结构体。值的类型无限制。

字典可以被视作为一个哈希表,其中所有可能的键已被虚拟化的创建,被映射到一个默认值(二进制表示的零)。在映射表中,我们并不存储键的数据,而是存储它的哈希值用来查找时使用。

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

contract test {
//id -> name
mapping(uint => string) id_names;

constructor() public{
id_names[0x001] = "lily";
id_names[0x002] = "Jim";
id_names[0x002] = "Lily";
}

function getNameById(uint id) constant public returns (string){
string storage name = id_names[id];
return name;
}
}

11.自动推导类型

通过赋值的类型来进行推断。类似于go语言。

函数的参数不可以使用var

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

contract varTest {

string str1 = "hello!";
uint256 public count = 0;
function loopTest() public{
//因为这里对i进行自动推导出的时uint8,它一直会死循环,所以不使用i作为退出的条件,用count作为计数器
for (var i = 0; i< 2000 ; i++){
count++;
//uint8 0-256
if (count > 257){
break;
}
}
var str2 = str1;
}
}

storage和memory的区别

  1. storage: 成员变量,可以跨函数调用,有点类似于硬盘;
  2. memory: 临时数据存储,类似于电脑的存储,函数的参数可以理解为memory类型;

全局函数/变量

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
pragma solidity ^0.4.21;

contract Test {
bytes32 public blockhash;
address public coinbase;
uint public difficulty;
uint public gaslimit;
uint public blockNum;
uint public timestamp;
bytes public calldata;
uint public gas;
address public sender;
bytes4 public sig;
uint public msgValue;
uint public _now;
uint public gasPrice;
address public txOrigin;

function tt () payable public {
//给定区块号的哈希值,只支持最近256个区块,且不包含当前区块
blockhash = block.blockhash(block.number - 1);
coinbase = block.coinbase ;//当前块矿工的地址。
difficulty = block.difficulty;//当前块的难度。
gaslimit = block.gaslimit;// (uint)当前块的gaslimit。
blockNum = block.number;// (uint)当前区块的块号。
timestamp = block.timestamp;// (uint)当前块的时间戳。
calldata = msg.data;// (bytes)完整的调用数据(calldata)。
gas = gasleft();// (uint)当前还剩的gas。
sender = msg.sender; // (address)当前调用发起人的地址。
sig = msg.sig;// (bytes4)调用数据的前四个字节(函数标识符)。
msgValue = msg.value;// (uint)这个消息所附带的货币量,单位为wei。
_now = now;// (uint)当前块的时间戳,等同于block.timestamp
gasPrice = tx.gasprice;// (uint) 交易的gas价格。
txOrigin = tx.origin;// (address)交易的发送者(完整的调用链)
}
}

货币单位与时间单位

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
pragma solidity ^0.4.0;

//货币单位及换算
contract EthUnit{
uint a = 1 ether;
uint b = 10 ** 18 wei;
uint c = 1000 finney;
uint d = 1000000 szabo;

function f1() constant public returns (bool){
return a == b;
}

function f2() constant public returns (bool){
return a == c;
}

function f3() constant public returns (bool){
return a == d;
}

function f4() pure public returns (bool){
return 1 ether == 100 wei;
}
}


//时间单位及换算
contract TimeUnit{

function f1() pure public returns (bool) {
return 1 == 1 seconds;
}

function f2() pure public returns (bool) {
return 1 minutes == 60 seconds;
}

function f3() pure public returns (bool) {
return 1 hours == 60 minutes;
}

function f4() pure public returns (bool) {
return 1 days == 24 hours;
}

function f5() pure public returns (bool) {
return 1 weeks == 7 days;
}

function f6() pure public returns (bool) {
return 1 years == 365 days;
}
}

constant、view和pure

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.20;


contract Test {

//一、constant介绍
uint public v1 = 10;
uint constant v2 = 10;

string str1 = "hello!";
string constant str2 = "test!";

function f1() public {
v1 = 20;
//v2 = 30; //constant修饰的值类型无法被修改

str1 = "Hello!";
// str2 = "Test!";
}

struct Person {
string name;
uint age;
}


//Person constant p1; //constant仅可以修饰值类型,无法修饰引用类型(string除外)


function f2() constant public{
v1 = 20; //constant 修饰的函数内,如果修改了状态变量,那么状态变量的值是无法改变的

}

//二、view介绍
// 1. view只可以修饰函数
// 2. 它表明该函数内只可以对storage类型的变量进行读取,无法修改


//三、pure介绍
//pure
// 1. pure只可以修饰函数
// 2. 它表明该函数内,无法读写状态变量
function f3() pure public returns(uint){
//return v1;
}
}

错误处理

传统方法: if…throw和throw

新方法:require()、assert()、revert()

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
pragma solidity ^0.4.20;

contract HasAnOwner {
address public owner;

//构造函数
constructor() public {
owner = msg.sender;
}

function useSuperPowers() public {
require(msg.sender == owner);
/*
if (msg.sender != owner){
throw;
}
等价于
if (msg.sender != owner){
revert();
}
assert(msg.sender == owner);
require(msg.sender == owner);
*/
}
}

delete

delete操作符可以用于任何变量,将其设置成默认值,而不是销毁

如果对动态数组delete,则删除所有元素,长度变为0

如果对静态数组delete,则重置所有索引的值

如果对map类型使用delete,什么都不会发送

如果对map的一个键进行delete,则会删除与该键相关的值

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
pragma solidity ^0.4.20;


contract deleteTest {
string public str1 = "hello";

//delete操作符可以用于任何变量(mapping除外),将其设置成默认值
function delStr() public{
delete str1;
}

function setStr() public {
str1 = "HELLO";
}

//静态数组,动态数组
uint[10] public staticArray = [4,2,4,5,6,7,8];
uint[] public dynamicArray = new uint[](10);

function intDynamicArray () public {
for (uint i = 0; i< 10; i++) {
//dynamicArray[i] = i;
dynamicArray.push(i);
}
}

//如果对静态数组使用delete,则重置所有索引的值
function delStaticArray() public {
delete staticArray;
}

//如果对动态数组使用delete,则删除所有元素,其长度变为0
function delDynamicArray() public {
delete dynamicArray;
}

function getArrayLength() view public returns (uint, uint){
return (staticArray.length, dynamicArray.length);
}


mapping(uint => bool) public map1;

function initMap() public {
map1[1] = true;
map1[2] = true;
map1[3] = false;

//delete map1;
}

function deleMapByKey(uint key) public {
delete map1[key];
}
//delete map1;

struct Person {
string name;
mapping(string => uint) nameScore;
}

Person public p1;

function initP1() public {
p1.name = "duke";
p1.nameScore["duke"] = 80;
}

function returnP1() view public returns (string, uint) {
return (p1.name, p1.nameScore["duke"]);
}

function deleteP1name() public {
delete p1;
}

function deleteP1Score() public{
delete p1.nameScore["duke"];
}
}

modifier修饰器

modifier可以用来改变一个函数的行为,比如用于在函数执行前检查某种前置条件。修饰器是一种合约的属性,可以被继承,同时还可以被派生的合约重写(override)。

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

contract HasAnOwner {
address public owner;
uint public a ;

constructor() public {
owner = msg.sender;
}

modifier ownerOnly(address addr) {
require(addr == owner);
//下划线表示修饰器所修饰函数的代码,也就是a=10这段代码
_;
}

function useSuperPowers() ownerOnly(msg.sender) public {
a = 10;
// do something only the owner should be allowed to do
}
}

投票案例

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
pragma solidity ^0.4.20;
pragma experimental ABIEncoderV2;

contract VoteContent{
//投票人
struct Voter{
uint voteNumber;//投票号
bool isVoild;//是否投过票
uint weight;//投票权重,初始值为1,被委托作为代理人后,权重增加
address agent;//指定代理人
}

//候选人
struct Candidate{
string name;//候选人姓名
uint voteCount;//获得的投票数
}

//合约创建人,负责创建合约,授权给地址,使之成为投票人
address public admin;

//定义候选人集合
Candidate [] public candidates;
//定义投票人集合
mapping(address => Voter) public voters;

constructor(string[] candidatesNames) public{
admin = msg.sender;
//每一个名字都生成一个候选人,添加到候选人集合中
for (uint i = 0 ; i < candidatesNames.length ; i++){
Candidate memory tmp = Candidate({
name : candidatesNames[i],
voteCount : 0
});
candidates.push(tmp);
}
}
//修饰器,使管理员才有权限
modifier adminOnly() {
require(admin == msg.sender);
_;
}

//只有管理员有添加投票人的权力
function giveVoteRightTo(address addr) adminOnly public {
if (voters[addr].weight > 0){
revert();
}
voters[addr].weight = 1;
}

function agentFunc(address to) public{
Voter storage voter = voters[msg.sender];
//如果不是投票人或者已经投过票则不能委托
if (voter.weight <= 0 || voter.isVoild){
revert();
}
//轮询找到最终的代理人
while (voters[to].agent != address(0) && voters[to].agent != msg.sender){
to = voters[to].agent;
}
//这个代理人不能是自己
require(msg.sender != to);

//设置自己为已经投票
voter.isVoild = true;
//将to设置为自己的代理人
voter.agent = to;
//设置最终代理人
Voter storage finalAgent = voters[to];
//如果代理人投过票
if (finalAgent.isVoild){
//在候选人的得票数加上自己的权重
candidates[finalAgent.voteNumber].voteCount += voter.weight;
}else{
//否则在代理人的权重上加上自己的权重
finalAgent.weight += voter.weight;
}
}

function vote(uint voteNum) public{
Voter storage voter = voters[msg.sender];
//如果不是投票人或者已经投过票则不能投票
if (voter.weight <= 0 || voter.isVoild){
revert();
}
//修改为投过票了
voter.isVoild = true;
//赋值投票号
voter.voteNumber = voteNum;
//获得投票的候选人票数增加权重个
candidates[voteNum].voteCount += voter.weight;
}

function whoWin() public returns(string , uint) {
string winner;
uint winnerVoteCount;
//找到所有的候选人谁得票多
for (uint i = 0 ; i < candidates.length ; i++){
if (candidates[i].voteCount > winnerVoteCount){
winnerVoteCount = candidates[i].voteCount;
winner = candidates[i].name;
}
}
//返回得票最多的人的姓名和得票数
return (winner,winnerVoteCount);
}
}

//["a","b","c","d"]