借此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;
}
// 删除数组中最后一个元素,会使数组的长度 -1
function 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);
}
// 可以将返回值赋给他的名称,这种情况下可以省略return
function 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上找到的,写的很详细,很适合新手来钻研其中的逻辑,这里我去除了代理人的逻辑,因为比较难,后续有时间再加上。
这个案例有三个角色,分别是提案,投票者,主席。基于这三个角色的互动实现了基础的投票
还有很多优化的地方比如使用构造器来确定主席的地址等等,后续再改