借此CSDN博客笔记来巩固一下对solidity知识的认识
1.helloworld
// SPDX-License-Identifier:MIT
pragma solidity ^0.8.0;
contract First {string public hello = "HelloWorld";
}
public 修饰符自动创建一个get()函数用于返回内容
类似于
// SPDX-License-Identifier:MIT
pragma solidity ^0.8.0;
contract First {string hello = "HelloWorld";function getHello() public view returns(string memory) {return hello;}
}
view:函数里的功能只查看了状态变量或者全局变量,不改变状态变量的内容,用view修饰。调用view修饰的函数,不会消耗gas,只有消耗gas的函数调用view修饰的函数,才会消耗gas。
memory:数据位置的修饰符,修饰数组,映射,字节,于calldata的区别是 memory 不能修改变量的内容,而calldata可以
returns:声明返回的数据类型,可以返回多个。
2.第一个应用程序
// SPDX-License-Identifier:MIT
pragma solidity ^0.8.0;
contract First {uint256 count;function get() public view returns(uint256) {return count;}function inc() public {count += 1;}function dec() public {count -= 1;}
}
功能描述:用于加减状态变量count
细节:0.8.0以下的版本减到零之后就会返回最大值,也就是常说的整数溢出问题 使用safemath库可以解决或者更换到0.8.0以上的版本。
状态变量:状态变量就是具体要上链的数据
3.数据类型
boolean
有两个值 true和false 默认为false
uint
无符号的整型,最大值为uint256,默认为0
// SPDX-License-Identifier:MIT
pragma solidity ^0.8.0;
contract First {uint256 i;function getMax() public pure returns(uint256) {return type(uint256).max;}function getMin() public pure returns(uint256) {return type(uint256).min;}
}
可以用type()函数查看最大值和最小值
pure:通常用pure修饰符的函数是工具类
int
有符号的整型,最大值为int256,默认为0
// SPDX-License-Identifier:MIT
pragma solidity ^0.8.0;
contract First {int256 i;function getMax() public pure returns(int256) {return type(int256).max;}function getMin() public pure returns(int256) {return type(int256).min;}
}
也可以用type()函数查看最大值和最小值
address
地址类型,是solidity的特有的数据类型,以32位的十六进制表示,用于接受或发送以太币,默认为0x0000000000000000000000000000000000000000
4. 变量
solidity的变量有三种分别是:局部变量(local)、状态变量(state)、全局变量(global)。可以理解为solidity的状态变量是传统编程语言的全局变量,solidity的全局变量是提供有关区块链的信息的函数,比如(block.timestamp用于获取当前的时间戳、msg.sender获取当前账户的地址)
// SPDX-License-Identifier:MIT
pragma solidity ^0.8.0;
contract First {// 状态变量string text = "Hello";uint256 num = 123;function doSomething() public view returns(uint256,uint256,address){// 局部变量uint256 i = 456;// 全局变量uint256 timestamp = block.timestamp;address sender = msg.sender;return (i,timestamp,sender);}
}
5. 常量
// SPDX-License-Identifier:MIT
pragma solidity ^0.8.0;
contract First {address constant MY_ADDRESS = 0x0000000000000000000000000000000000000000;function get() public pure returns(address) {return MY_ADDRESS;}
}
这里解释一下 使用函数返回常量的值为什么使用pure修饰而不是view
因为constant修饰的是常量,常量的值是硬编码的,而pure修饰的函数就通常返回一些数学运算或者硬编码的值,view修饰的函数多用于查看状态变量或者全局变量。真因为常量的值是硬编码,所以可以节省gas费用。
6.不可变的
// SPDX-License-Identifier:MIT
pragma solidity ^0.8.0;
contract First {address immutable MY_ADDRESS;constructor() {MY_ADDRESS = msg.sender;}function get() public view returns(address) {return MY_ADDRESS;}
}
和常量很像,使用immutable修饰不可修改,多用于在部署合约时确定合约账户地址。
在定义常量和不可改变量时的标识符通常用大写来表示
7. 读取和改变状态变量
// SPDX-License-Identifier:MIT
pragma solidity ^0.8.0;
contract First {uint256 num;function set(uint256 _num) public {num = _num;}function get() public view returns(uint256) {return num;}
}
和我们第一个应用程序很像,读取变态变量不消耗gas,修改状态变量消耗gas
通常函数里的形参名称开头用_开头
8. ether与wei
ether和wei是以太坊用于交易的单位
1 ether = 1 * 10的18 次方 wei
在solidity中定义的uint或int默认的单位都是Wei
// SPDX-License-Identifier:MIT
pragma solidity ^0.8.0;
contract First {uint256 oneWei = 1 wei;uint256 oneEther = 1 ether; function fun1() public view returns(bool) {bool isOneWei = oneWei == 1;return isOneWei;// 返回true}function fun2() public view returns(bool) {bool isOneEther = oneEther == 1e18;return isOneEther;// 返回true}
}
9. Gas
在以太坊网络上进行交易,都需要支付一定的gas费用,举例子说明一下
设置Gas Limit。假设小明在以太坊网络上要部署合约,就是相当于一次交易。在部署合约时需要设置一个交易的Gas Limit这是小明愿意为这个交易使用的最大Gas量。假设设置的Gas Limit为300000Gas
设置Gas价格。接下来,小明需要设置一个Gas价格,这表示他愿意为每单位Gas支付的以太币数量。假设设置的Gas价格是50Gwei
计算交易费用。当小明的交易被执行时,实际上可能并不需要消耗全部的300000Gas,假设部署合约这个交易消耗了80000Gas。那么小明实际需要支付的Gas费用是:"消耗的Gas * Gas价格 = 80000Gas * 50Gwei = 4000000Gwei或0.004ETH" 。
未消耗的Gas退还。由于小明设置的Gas Limit是300000,但实际消耗了80000,剩余的220000Gas会回滚。
区块的Gas Limit。区块的Gas Limit是由以太坊网络决定的,限制一个区块内所有交易可以消耗的总Gas量。假设当前的区块Gas Limit是15,000,000 Gas,这意味着所有包含在这个区块中的交易,它们消耗的Gas总和不能超过这个限制。如果一个单独的交易超出区块的Gas Limit,这意味着当前交易太大,不能被任何矿工在单个区块中处理,所以矿工会忽略这样的交易。直到它被发送方取消或替换,或者网络的条件改变(例如区块的Gas Limit增加)。即便交易的Gas消耗没有超过区块的Gas Limit,但如果区块内已经包含的其他交易的Gas总和加上这个交易的Gas超过了区块的Gas Limit,那么这个交易将不会被包含在当前区块中。它会留在交易池中,等待下一个或未来某个区块有足够的空间。
10.if else
// SPDX-License-Identifier:MIT
pragma solidity ^0.8.0;
contract First {function fun(uint256 _num) public pure returns(uint256) {if(_num < 10) {return 0;} else if (_num < 20) {return 1;} else {return 2;}}function fun1(uint256 _x) public pure returns(uint256) {// if(_x < 10) {// return 1;// } else {// return 2;// }return _x < 10 ? 1 : 2; // 相当于上段代码}
}
solidity支持if,else if,else的写法,不过在区块链中进行判断多用于require(),因为可以进行回滚操作
11. for、while、dowhile
// SPDX-License-Identifier:MIT
pragma solidity ^0.8.0;contract First {// 100以内的偶数和function fun() public pure returns (uint256) {uint256 sum = 0;for (uint256 i = 0; i <= 100; i++) {if (i % 2 == 0) {sum += i;}}return sum;}// 100以内的奇数和function fun1() public pure returns (uint256) {uint256 sum = 0;uint256 i = 0;while (i <= 100) {if (i % 2 != 0) {sum += i;}i++;}return sum;}// 100以内的数字之和function fun2() public pure returns(uint256) {uint256 sum = 0;uint256 i = 0;do {sum += i;i++;} while(i <= 100);return sum;}
}
同样solidity支持for、while、dowhile的写法。这个例子也很好的诠释了pure修饰符的用处
12. mapping
// SPDX-License-Identifier:MIT
pragma solidity ^0.8.0;contract First {mapping(address => uint256) myMap;mapping(address => mapping(uint256 => bool)) nested;function get(address _addr) public view returns(uint256) {return myMap[_addr];}function set(address _addr,uint256 _i) public {myMap[_addr] = _i;}function remove(address _addr) public {delete myMap[_addr];}function get1(address _addr,uint256 _i) public view returns(bool) {return nested[_addr][_i];}function set1(address _addr,uint256 _i,bool _boo) public{nested[_addr][_i] = _boo;}function remove1(address _addr,uint256 _i) public {delete nested[_addr][_i];}}
mapping映射的key可以是任何值类型、字节、字符串、或者合约类型
value可以是任何类型,包括另一个映射或数组
13.数组
solidity的数组分为动态数组、静态数组和内存数组。
动态数组
// SPDX-License-Identifier:MIT
pragma solidity ^0.8.0;
contract Second {// 动态数组uint256[] arr;// 添加数组元素function push(uint256 _num) public {arr.push(_num);}// 根据索引获取数组元素function getIndexArr(uint256 _i) public view returns(uint256) {return arr[_i];}// 获取数组function getArr() public view returns(uint256[] memory) {return arr;}// 获取数组的长度function getArrLength() public view returns(uint256) {return arr.length;}// 删除数组中最后一个元素,会使数组的长度 -1function pop() public {arr.pop();}// 根据索引重置元素为默认值,不会删除数组的长度function remove(uint256 index) public {delete arr[index];}}
动态数组是solidity中常用的形式
静态数组
// SPDX-License-Identifier:MIT
pragma solidity ^0.8.0;contract Second {// 静态数组uint256[10] arr = [1, 2, 3, 4, 5];// 根据索引获取数组元素function getIndexArr(uint256 _i) public view returns (uint256) {return arr[_i];}// 获取数组function getArr() public view returns (uint256[10] memory) {return arr;}// 获取数组的长度function getArrLength() public view returns (uint256) {return arr.length;}// 静态数组添加元素function addArr() public {for (uint256 i = 0; i < 5; i++) {arr[i + 5] = i;}}
}
在静态数组中,不可以使用push pop函数等
内存数组
// SPDX-License-Identifier:MIT
pragma solidity ^0.8.0;contract Second {function fun() external pure returns(uint256[] memory){uint256[] memory arr = new uint256[](5);return arr;}
}
这里的external代表着只能从合约外部调用,不能被合约外部的其他函数调用
小案例
我们使用solidity想要删除数组,无非是使用 delete 和pop函数,可它们的缺点也很明显,delete函数只能根据索引重置数组内容为默认值,pop函数只能删除数组中的最后一个。但是我们想要的效果是根据索引删除元素,该如何实现呢?
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;contract Frist {uint256[] arr;function remove(uint256 index) public {for(uint256 i = index;i< arr.length - 1;i++) {arr[i] = arr[i + 1];// 将想要被删除的数替换为想要被删除的数之后的一位}arr.pop();}function getArr() public view returns(uint256[] memory) {return arr;}function test() public {arr = [1, 2, 3, 4, 5, 6];remove(1);// [1, 3, 4, 5, 6]assert(arr.length == 5);assert(arr[0] == 1);assert(arr[1] == 3);assert(arr[2] == 4);assert(arr[3] == 5);assert(arr[4] == 6);}
}
这里为了测试用到了断言语句,assert如果值相等才会往下执行
14. 枚举
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;contract Frist {enum Status {Pending,Shipped,Accepted,Rejected,Canceled}Status status;function get() public view returns(Status) {return status;// 默认值为枚举类型的第一个值}function set(Status _status) public {status = _status;}function cancel() public {status = Status.Canceled;}function reset() public {delete status;// 重置枚举类型为默认值}
}
15. 结构体
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;contract Frist {struct Todo {string text;bool completed;}Todo[] todos;// 结构体数组function create(string calldata _text,bool _completed) public{// 添加结构体数组的三种方式// 第一种todos.push(Todo(_text,_completed));// 第二种/**todos.push(Todo({text: _text,completed: _completed}));**/// 第三种/**Todo memory todo;todo.text = _text;todo.completed = _completed;todos.push(todo);**/}// 根据数组索引找到对应的结构体数据function get(uint256 _index) public view returns(string memory text,bool completed) {Todo memory todo = todos[_index];return (todo.text,todo.completed);}// 返回整个结构体数组function getTodos() public view returns(Todo[] memory) {return todos;}// 更新结构体数据function update(uint256 _index,string memory _text) public {Todo storage todo = todos[_index];todo.text = _text;todo.completed = !todo.completed;}
}
结构体常常和数组结合用来存储某一类的数据。
我们常说区块链是透明,不可更改等等,都只是狭义上的理解,这些概念都需要一个精确的解释。我们知道在solidity中状态变量就相当于上链的数据。为什么在这段代码里就可以修改状态变量status里的值呢?区块链不可更改就意味着一旦数据被记录到区块链上,就无法删除或篡改,每一个新区块都包含前一个区块的哈希值,在逻辑上形成了链。如果尝试更改链中的任何信息,都会使后续所有的区块哈希值无效。然而,不可更改并不意味着区块链上的应用状态不能变化。在solidity中状态变量的值可以随着新的交易执行而改变,创建一个新的区块,更新的状态也就在记录在链上了。所以说区块链上的记录是一个不可更改的操作历史,但数据的当前状态是可以通过智能合约的逻辑来改变。这个例子也很好的证明了区块链是公开透明的,因为每次状态的改变都是可查看、可验证的。
16. 数据存储位置
数据存储位置有Storage,Memory,Calldata
Storage
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Frist {struct MyStruct {uint256 num;string name;}mapping(uint256 => MyStruct) myStructs;function getMyStruct1() public view returns(MyStruct memory) {return myStructs[0];}function fun1() public {MyStruct storage myStruct = myStructs[0];myStruct.num = 666;myStruct.name = "Hello";}
}
让我们来解释一下这段代码的意思。首先定义了一个MyStruct结构体和map映射。在函数fun1()里,定义了一个MyStruct实例,使用storage修饰,并引用了map键为0的映射。这样在修改MyStruct实例的时候,实际会改变状态变量myStruct键为0的映射。getMyStruct1()是为了测试查看map映射的结果
Memory
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Frist {struct MyStruct {uint256 num;string name;}function fun1() public pure returns(MyStruct memory){MyStruct memory myStruct = MyStruct({num: 666,name:"hello" });return myStruct;}
}
memory声明的MyStruct实例,只有在函数运行期间有效,是一个临时的存储。
特别说明一下当结构体里的数据只有一个参数时,可以使用以下代码来初始化一个结构体
struct MyStruct {uint256 num;}
MyStruct memory myMemStruct = MyStruct(0);
Calldata
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract First {struct MyStruct {uint256 num;string name;}function fun1(MyStruct calldata myStruct) external pure returns(MyStruct calldata){return myStruct;}
}
calldata修饰的变量,是只读的,不能修改,通常和external搭配,使用外部的数据来给calldata修饰的变量赋值。
注意:在solidity0.8.0以后的版本才支持上述写法,在remix传入参数的值为数组形式[666,"hello"]
之后写的代码全是以0.5.0以上0.6.0以下版本书写
17. 函数
// SPDX-License-Identifier: MIT
pragma solidity ^0.5.0;contract First {// 函数可以返回多个值function returnMany() public pure returns(uint256,bool,uint256) {return(1,true,2);}// 也可以给函数的返回值命名function named() public pure returns(uint256 x,bool b,uint256 y) {return(1,true,2);}// 可以将返回值赋给他的名称,这种情况下可以省略returnfunction assigned() public pure returns(uint256 x,bool b,uint256 y) {x = 1;b = true;y = 3;}// 调用另一个函数时,也可以使用解构赋值function destructuringAssignments() public pure returns(uint256,bool,uint256,uint256,uint256) {// 用解构赋值的语法接收函数返回的值(uint256 i,bool b,uint256 j) = returnMany();// 返回的值也可以省略(uint256 x,,uint256 y) = returnMany();return(i,b,j,x,y);}// 不能使用map作为函数的输入值和输出值,但可以使用数组function arrayInput(uint256[] memory _arr) public {}}
关于函数的细节还是挺多的,知道不用和不知道也完全时两码事
17. pure和view
// SPDX-License-Identifier: MIT
pragma solidity ^0.5.0;contract First {uint256 public x = 1;function addToX(uint256 y) public view returns(uint256) {return x + y;}function add(uint256 i,uint256 j) public pure returns(uint256) {return i + j;}}
这两个修饰符也提到过很多次了
view修饰的函数只能查看状态变量或者全局变量
pure修饰的函数不能查看、修改状态变量或者全局变量
什么都不写代表着要更新状态变量
18.Error
// SPDX-License-Identifier: MIT
pragma solidity ^0.5.0;contract First {uint256 x = 1;function getX() public view returns(uint256) {return x;}function testRequire(uint256 _i) public{x = 2;// 条件不成立时,会回滚之前的操作require(_i > 10, "输入的数字必须大于10");}// 这段代码和testRequire函数的作用完全一样function testRevert(uint256 _i) public {x = 3;if(_i <= 10) {revert("输入的数字必须大于10");}}// assert多用于测试内部的错误function testAssert() public view {assert(x == 1);}
}
上述代码说明了require、revert、assert的作用,在高版本的solidity出现了自定义的错误,这种自定义错误可以节省gas费。以小案例来演示以下自定义错误的语法
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract First {error InsufficientBalance(uint256 balance,uint256 withdrawAmount,string msg);function testCustomError(uint256 _withdrawAmount) public view returns(uint256){uint256 bal = address(this).balance;// 返回当前合约的账户余额if(bal < _withdrawAmount) {revert InsufficientBalance({balance:bal,withdrawAmount:_withdrawAmount,msg:unicode"当前的账户余额小于输入值"});}return 0;}// 返回当前的账户余额,用于测试,因为当前账户没有余额所以返回的是默认值0,function getBalance() public view returns(uint256) {return address(this).balance;}
}
这个小案例用到了address(this).balance,这就之前一直提到的全局变量,这条语句的作用是将当前合约实例转化为地址类型,从而可以调用balance属性来获取当前合约账户的余额。
这里要特别理清三个概念。智能合约账户,智能合约地址,外部账户。这三个概念在区块链世界中容易混淆。在实际使用中,当我们说到“智能合约地址”,我们可能是在讨论与之交互的方法,例如发送以太币到这个地址或是调用它的函数。而当我们提及“智能合约账户”,则更多地在关注合约的状态和行为——例如它的余额、它能执行哪些操作,以及它存储了哪些数据。而外部账户用于部署合约,发送交易。
19. Modifier
// SPDX-License-Identifier:MIT
pragma solidity ^0.5.0;
contract Second {address public owner;uint256 public x = 10;bool public locked;// 构造函数,在合约部署的时候自动调用constructor() public{owner = msg.sender;}modifier onlyOwner() {require(msg.sender == owner,"你不是合约部署者");// 执行完了require再执行剩余用onlyOwner修饰的函数代码_;}modifier validAddress(address _addr) {require(_addr != address(0),"无效的地址值");_;}// 只有合约拥有者且地址值有效才可以修改合约拥有者function changeOwner(address _newOwner) public onlyOwner validAddress(_newOwner){owner = _newOwner;}// 防止用noReentrancy修饰的函数在执行的过程中再次调用modifier noReentrancy() {require(!locked,"不可重入");// 确保当前没有其他操作锁定了合约locked = true;// 在执行操作前锁定合约_;// 执行被修饰的函数的体locked = false;// 执行完毕后解锁合约,允许其他操作}function decrement(uint256 _i) public noReentrancy returns(uint256){x -= _i;if(_i > 1) {decrement(_i - 1);}return x;}
}
Modifier修饰符是可以在函数调用之前和之后运行的代码,多用于限制访问验证和防止重入攻击
19. Events事件
// SPDX-License-Identifier:MIT
pragma solidity ^0.5.0;
contract Second {event Log(address indexed sender,string message);event AnotherLog();function test() public {emit Log(msg.sender,"Hello World");emit Log(msg.sender,"Hello EVM");emit AnotherLog();}
}
事件是一种智能合约内部记录和触发日志信息的机制,在执行操作时向外部发送信息,在外部就可以被应用程序监听和处理。
indexed表示为地址添加索引,而动态类型的参数(string
和bytes
),由于其数据大小的不确定性,不能被设置为indexed。
20. constructor
// SPDX-License-Identifier:MIT
pragma solidity ^0.5.0;
contract X {string public name;constructor (string memory _name) public {name = _name;}
}
contract Y {string public text;constructor(string memory _text) public{text = _text;}
}
contract B is X("继承X"),Y("继承Y"){}contract C is X,Y {constructor(string memory _name,string memory _text) X(_name) Y(_text) public{}
}
contract D is X,Y{constructor() X("继承X") Y("继承Y") public {}
}
contract E is X,Y{constructor() Y("继承Y") X("继承X") public {}
}
注意
is关键字表示继承。构造函数 在部署合约的时候自动调用,注意就是在低版本的solidity中需要在constructor后面加上public修饰符,在高版本中就不需要加了。
21. 继承
继承函数
// SPDX-License-Identifier:MIT
pragma solidity ^0.8.0;
contract A {function foo() public pure virtual returns(string memory) {return "A";}
}
contract B is A {function foo() public pure virtual override returns(string memory) {return "B";}
}
contract C is A {function foo() public pure virtual override returns(string memory) {return "C";}
}
contract D is B,C {function foo() public pure override(B,C) returns(string memory) {return super.foo();}
}
contract E is C, B {function foo() public pure override(C, B) returns (string memory) {return super.foo();}
}
contract F is A, B {function foo() public pure override(A, B) returns (string memory) {return super.foo();}
}/* A/ \B C/ \ /
F D,E*/
在solidity中支持多重继承,在高版本中父合约需要被重写的函数要加上virtual修饰,子合约中需要重写父合约函数要加上override修饰。调用父合约的函数需要super
继承状态变量
// SPDX-License-Identifier:MIT
pragma solidity ^0.5.0;
contract A {string public name = "Contract A";function getName() public view returns(string memory) {return name;}
}
contract B is A {function setName() public returns(string memory) {name = "Contract B";}
}
继承下来的状态变量可以直接修改
调用父合约
// SPDX-License-Identifier:MIT
pragma solidity ^0.8.0;
contract A {// 方便追踪函数event Log(string message);function foo() public virtual {emit Log("A.foo called");}function bar() public virtual {emit Log("A.bar called");}
}
contract B is A {function foo() public virtual override {emit Log("B.foo called");A.foo();}function bar() public virtual override {emit Log("B.bar called");super.bar();}
}
contract C is A {function foo() public virtual override {emit Log("C.foo called");A.foo();}function bar() public virtual override {emit Log("C.bar called");super.bar();}
}
contract D is B, C {function foo() public override(B, C) {super.foo();}function bar() public override(B, C) {super.bar();}
}
调用父合约可以使用合约名或者super关键字
22. 可见性
函数
// SPDX-License-Identifier:MIT
pragma solidity ^0.5.0;
contract A {// 私有函数只能被合约内部调用,继承后不能被调用function privateFunc() private pure returns(string memory) {return "私有函数被调用了";}function testPrivateFunc() public pure returns(string memory) {return privateFunc();}// 内部函数可以被合约内部调用,也可以被继承调用function internalFunc() internal pure returns(string memory) {return "内部函数被调用了";}function testInternalFunc() public pure returns(string memory) {return internalFunc();}// 公共函数可以被外部的账户或合约调用function publicFunc() public pure returns(string memory) {return "公共函数被调用了";}// 外部函数只能被外部的账户或者合约调用,合约内部不可以调用function externalFunc() external pure returns(string memory) {return "外部函数被调用了";}
}
之前的操作中或多或少都遇到过,现在把它们总结一下
函数的修饰符有public、private、internal、external
public:任何合约或者账户都可以调用
private:只有在合约内部才可以调用
internal:只有在合约内部或者继承的合约才可以调用
external:只有其他外部合约或者外部账户才可以调用
状态变量
// SPDX-License-Identifier:MIT
pragma solidity ^0.5.0;
contract A {string private privateVar = "私有状态变量";string internal internalVar = "内部状态变量";string public publicVar = "公共变量";
}
状态变量的修饰符只有三种分别是public,private,internal,没有external修饰符,作用和函数类似。
23. Interface
24.案例
合约投票
// SPDX-License-Identifier: MIT
pragma solidity ^0.5.0;contract First {// 表示一个投票人struct Voter {uint256 weight; // 通过代理积累的权重bool voted; // 表示是否已经投过票uint256 vote; // 选择的提案编号}// 表示一个提案struct Proposal {bytes32 name; // 提案名称uint256 voteCount; // 积累的投票数量}address public chairperson; // 主席// 保存从地址到投票人数据的映射mapping(address => Voter) public voters;// 将提案保存在数组里Proposal[] public proposals;// 基于一组提案,构建一个投票协约function Ballot(bytes32[] memory proposalNames) public {chairperson = msg.sender; // 投票协约的创建人是主席voters[chairperson].weight = 1; // 主席的投票权重是1// 针对每一个提案名,创建一个对应的提案,并且保存在Proposals数组中for (uint256 i = 0; i < proposalNames.length; i++) {proposals.push(Proposal({name: proposalNames[i], voteCount: 0}));}}// 主席给予一个人的投票权利function giveRightToVote(address voter) public{require(msg.sender == chairperson,"只有主席才能给予一个人有投票的权力");require(!voters[voter].voted,"选民已经投过票了");require(voters[voter].weight == 0);voters[voter].weight = 1;}// 进行投票function vote(uint proposal) public {// 使用当前账户进行投票Voter storage sender = voters[msg.sender];require(!sender.voted,"选民已经投过票了");sender.voted = true;sender.vote = proposal;proposals[proposal].voteCount += sender.weight;}// 计算出胜者的提案function winningProposal() public view returns(uint256 winningProposal_) {uint winningVoteCount = 0;for(uint256 p = 0; p< proposals.length;p++) {if(proposals[p].voteCount > winningVoteCount) {winningVoteCount = proposals[p].voteCount;winningProposal_ = p;}}return winningProposal_;}// 返回胜出提案的名称function winnerName() public view returns(bytes32 winnerName_) {winnerName_ = proposals[winningProposal()].name;return winnerName_;}
}
这个案例从github上找到的,写的很详细,很适合新手来钻研其中的逻辑,这里我去除了代理人的逻辑,因为比较难,后续有时间再加上。
这个案例有三个角色,分别是提案,投票者,主席。基于这三个角色的互动实现了基础的投票
还有很多优化的地方比如使用构造器来确定主席的地址等等,后续再改