Solidity笔记
pragma solidity >0.4.22; # 版本要求大于等于0.4.22,
# ^0.4.22的含义是大于0.4.22 但是小于一个大版本 0.5, 表示在这个范围内
contract Car {
# solidity中的公共的成员变量,如果没有,编译器自动生成get方法,返回price
uint public price;
string brand;
function setPrice(uint newPrice) public {
price = newPrice;
}
}
# pure 关键字代表为纯计算
function pureAdd(uint a, uint b) public pure returns(uint sum, uint origin_a) {
return (a+b, a);
}
-
bytes32 类型数据 ,是指32字节数据
-
constructor() 方法在 编译器版本 0.4.22 之后出现
-
pragma声明行尾少写 ; 就会导致如下错误
ParserError: Expected pragma, import directive or contract/interface/library definition.
- Solidity 数据类型
enum 枚举类型,定义的时候,枚举对象必须有一个数据,不能空定义。其次,枚举类型其中元素可以被显示转换成uint类型数据,对应枚举元素在枚举对象中的下标。
uint[] memory a = new uint[](len);
// 表示定义一个无符号int数组,长度为len
bytes memory b = new bytes(len);
// 定义一个动态长度的字符, 存储在内存中
// memory中,数组的长度最好在定义时指定。
Struct 结构体数据类型中可以包含其他各种数据类型元素,但是不能包含该结构体自己的元素类型,因为可能会出现循环引用。可以包含数组类型的该类结构体数组,因为数组是引用类型数据,变量中存储的是指针,就不会出现循环引用问题。
// 当合约中成员变量的访问为 public 则合约自动为该变量生成获得该变量的方法
pragma solidity ^0.4.0;
contract C {
mapping(address => uint) public balances;
function update(uint amount) public {
balances[msg.sender] = amount;
}
// function getBalance(address _addr) public view returns(uint) {
// return balances[_addr];
// }
}
contract D {
function fun() public returns(uint) {
C con = new C();
con.update(10);
// return con.getBalance(address(con)); // "0": "uint256: 0"
// return con.getBalance(address(this));// "0": "uint256: 10"
return con.balances(address(this));
// 可直接使用该方式调用public成员
}
}
数据存储位置(Data location)概念
1.1 storage, memory, calldata, stack区分
在 Solidity 中,有两个地方可以存储变量:存储(storage)以及内存(memory)。Storage 变量是指永久存储在区块链中的变量。
Memory 变量则是临时的,当外部函数对某合约调用完成时,内存型变量即被移除。
内存(memory)位置还包含2种类型的存储数据位置,一种是calldata,一种是栈(stack)。
(1)calldata
这是一块只读的,且不会永久存储的位置,用来存储函数参数。 外部函数的参数(非返回参数)的数据位置被强制指定为 calldata ,效果跟 memory 差不多。
(2) 栈(stack)另外,EVM是一个基于栈的语言,栈实际是在内存(memory)的一个数据结构,每个栈元素占为256位,栈最大长度为1024。 值类型的局部变量是存储在栈上。
不同存储的消耗(gas消耗)是不一样的,说明如下:
1.storage 会永久保存合约状态变量,开销最大;
2.memory 仅保存临时变量,函数调用之后释放,开销很小;
3. stack 保存很小的局部变量,免费使用,但有数量限制(16个变量);
4.calldata的数据包含消息体的数据,其计算需要增加n*68的GAS费用;
- storage 存储结构是在合约创建的时候就确定好了的,它取决于合约所声明状态变量。但是内容可以被(交易)调用改变。
- Solidity 称这个为状态改变,这也是合约级变量称为状态变量的原因。也可以更好的理解为什么状态变量都是storage存储。
- memory 只能用于函数内部,memory 声明用来告知EVM在运行时创建一块(固定大小)内存区域给变量使用。
- storage 在区块链中是用key/value的形式存储,而memory则表现为字节数组
solidity中设置为memory和storage临时变量的区别
- storage 的数据在链上存储,比较贵。
memory 的数据在内存中存储,不占用链上空间(即不能永久存库数据),比较便宜。
特例: 引用数据类型的局部变量存储在Storage中,内存无法实现存储引用数据分配无限扩展的动态空间。
- 特殊情况
pragma solidity ^0.4.12;
contract C {
uint public a; // a 每次在fun函数调用一次都会自加1
// 原因是fun函数中变长数组抢占了合约开始的存储地址,并在空间中存储了数组长度值
uint public b;
uint[] public data;
function fun() public {
// 局部状态函数存储在合约存储的第一个位置和 全局状态变量a位置重合
// x的存储是在存储地址中放变长数组的长度值
//查找x中某一元素是利用元素下标和内存地址求哈希,得到真正存储元素的地址。
// 改进方法, 在定义局部Storage变量时赋值,如uint[] x = data;
uint[] x;
x.push(12);
data = x;
}
}
- 注意点
合约中函数的参数中有存储在 storage 中的变量,函数声明必须是内部internal,public 声明会出现不可控的外界大量调用函数存储 storage 数据。
不同存储空间数据的赋值
- memory 中数据赋值给 storage 中数据:将数据完全拷贝复制到 storage 中
- 在局部函数中,将 storage 局部变量赋值给 storage 状态变量:将数据完全拷贝赋值
- 将 storage 状态变量赋值给 storage 局部变量:传递的是数据引用地址
这里产生错误的原因:y 是已经用来存储 storage 中数据的指针, 再次用 memory 中数据赋值就会产生冲突。
- 例2
contract GuessGame{
address public owner = msg.sender;
uint public luckNum = 52;
struct Guess{
address guesser;
uint guessNum;
}
Guess[] public guessHistory;
constructor () public payable{
}
function guess(uint _guessNum) public payable{
Guess storage newGuess; // 实际测试:newGuess居然不是指针地址???
newGuess.guesser = msg.sender;
newGuess.guessNum = _guessNum;
guessHistory.push(newGuess);
if (_guessNum == luckNum) {
msg.sender.transfer(msg.value * 2);
}
}
function () public payable{
} //?? 没有这个也转账成功了
}
实际情况:luckNum与guess中的数据一致,且随guess的不断变化而变化
分析:newGuess的数据分别占据了owner 和 luckNum 的 storage存储地址
contract GuessGame{
// address public owner = msg.sender;
uint public luckNum = 52;
struct Guess{
address guesser;
uint guessNum;
}
Guess[] public guessHistory;
constructor () public payable{
}
function guess(uint _guessNum) public payable{
Guess storage newGuess; ## 这里就是指针地址,并将40位16进制地址放到luckNum上
## ?? 疑问:为啥每次guess调用,luckNum没有跟随变化??
newGuess.guesser = msg.sender;
newGuess.guessNum = _guessNum;
guessHistory.push(newGuess);
if (_guessNum == luckNum) {
msg.sender.transfer(msg.value * 2);
}
}
function () public payable{
}
}
# 此时luckNum 在第一次guess之后为:
11544 14090 61981 17968 18182 30213 94152 80051 21425 0812
函数的类型
- 值类型 :internal 和 external
状态变量
-
状态变量的可见性不能设置external,默认是internal,public 的作用是为状态变量添加了同名状态变量的函数,用于外部读取
-
各个类型的区别
-
public 和 external:external 限制为仅外部访问,内部也不能访问,public 内部外部都可以访问。
-
private 和 internal:private限制更严,internal为内部访问,且派生合约也可以访问,private定义的函数和状态变量仅在当前定义的合约内部使用。
-
在部署合约的同时要想给合约地址转钱,就必须在constructor 函数上加上payable
问题
使用最新版本geth客户,当执行personal.unlockAccount()或在程序中调用personal_unlockAccount接口时,会出现:account unlock with HTTP access is forbidden异常。
新版本geth,出于安全考虑,默认禁止了HTTP通道解锁账户,相关issue:https://github.com/ethereum/go-ethereum/pull/17037
解决方案
如果已经了解打开此功能的风险,可通启动命令中添加参数:
–allow-insecure-unlock