这篇文章仅用于记录上课笔记!!点击 solidity 查看更多
基本数据类型:
整数,枚举,布尔
Address,contract
Fixed byte array
-
Integer(int,uint)
uint(unsigned int) 无符号整数,2的n次方减一为最大数值。可以使用type(x).max() 来查看该数据类型的最大值和最小值
部署合约之后可以看到最大值:
可以写一个increment函数来检验:当数据值的范围超出最大值之后,就不会再增加,并且会给出错误信息。
-
枚举类型(Enum type)
-
address
address含有20个字节长度,address可以转换为uint160和bytes20,表示部署合约的账号地址值。 -
contract合约关键字
用于声明合约
-
定长字节数组
使用下标访问,但是不可以使用下标写入数据。
定义方式:
bytes1 data; // 1表示这是一个字节的定长字节数组
bytes d = data[0];
- 引用类型
数组,struct(结构体),mapping映射
数组:push 方法 向数组中添加元素
pop 方法,删除数组当中的一个元素 - mapping映射
mapping(key type => value type)
如果要想映射作为函数的参数传入,那么这个函数的可见性必须是internal或者是private.不能是public。
mapping (string => string) nameToDesc;
mapping (string => uint8) nameToAge;
mapping (string => mapping(string=>uint8)) public name2age;
function setnameToAge (mapping(string=> uint8) storage data) internal{
}
contract Storage {
mapping(address => uint256) balances;
constructor(){
address deployer = msg.sender;
balances[deployer] = 100;
}
function balanceOf(address owner) public view returns(uint256){
return balances[owner];
}
function transfer(address from, address to ,uint256 amount)public{
require(balances[from] >= amount,"The address do not have enough money");
balances[from] -= amount;
balances[to] += amount;
}
}
- sd
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.2 <0.9.0;
contract ArrayTest{
uint256[4] public fixedData;
uint256[] public dynamicData;
// 定长数组 使用下标来存放数据
function setFixedData(uint8 index,uint256 value)public{
fixedData[index] = value;
}
// 动态数组 使用push方法来存入变量,不需要使用下标索引
function addDataToDynamic(uint256 value) public{
dynamicData.push(value);
}
function removeAllData(uint8 n) public{
uint256 length = dynamicData.length;
for (uint8 i = 0 ;i<length;i++){
dynamicData.pop();
}
}
// 内存当中的动态数组
// function memoryDynamic(uint8 size) public{
// uint256 [] memory temp = new uint256[](size);
// }
}
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.2 <0.9.0;
struct Student {
uint256 num;
string name;
}
contract Storage {
uint256 number;
Student [] public student;
function setStudent(uint256 _num,string memory _name) public{
Student memory st = Student(_num,_name);
// Student memory st = Student({num:_num,name:_name});
student.push(st);
}
}
引用数据类型
在solidity当中,引用数据类型的变量和其他语言并不相同,因为solidity当中含有storage这个 数据块的存储位置,所以变量的数据块会固化在存储当中,并不会指向引用的数据变量当中的内容,所以不会出现其他语言当中的引用拷贝,只会出现值拷贝。
uint256[3] public stateData1;
uint256[3] public stateData2;
function assignStateVariable()public{
stateData1 = stateData2; // 只会把stateData2的值给到stateData1,并不会产生一个指针
stateData2[0] = 5;
}
function storage2memory () public returns (uint256[3] memory){
uint256[3] memory data1 = stateData1; // 只会把磁盘当中的数据放到内存当中,不会创建一个指针
stateData1[0] = 10;
return data1;
}
- location对数据空间的分割
uint256[3] public stateData1;
uint256[3] public stateData2;
function storage2memory () public returns (uint256[3] memory){
uint256[3] memory data1 = stateData1; // 两个变量的location是memory和storage,所以是值引用,并且只会把磁盘当中的数据放到内存当中,不会创建一个指针
stateData1[0] = 10;
return data1;
}
- 判定算法
function storage2memory () public {
uint256[3] memory data;
data = stateData1; // 因为两个数据的location并不相同,所以不会出现引用拷贝,只会是值拷贝
stateData1 [0] = 9;
}
*-------------------------------------------------------
function storage2memory () public {
uint256[3] memory data;
stateData1 = data;
stateData1 [0] = 9;
}
-------------------------------------------------------------------
uint256[3] public stateData1;
uint256[3] public stateData2;
function assignStateVariable()public{
stateData1 = stateData2; // 只会把stateData2的值给到stateData1,并不会产生一个指针
stateData2[0] = 5;
}
function storage2memory () public {
uint256[3] memory data;
uint256[3] storage sdp = stateData1; // 成员变量默认的location是storage 所以是引用拷贝
sdp = stateData2;
data = sdp; // 两个变量的location不同 ,所以应该是值拷贝
stateData1 = data;
stateData1 [0] = 9;
}
https://solidity-by-example.org/
函数的调用机制
静态调用
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.2 <0.9.0;
contract Caller{
uint256 public mycount;
address callee;
constructor(address _callee){
callee = _callee;
}
function fetchCallee()public{
Callee calleeContract = Callee(callee);
uint256 callee_value = calleeContract.get_number();
mycount = callee_value;
}
}
contract Callee{
uint256 number = 0;
function get_number()public view returns(uint256) {
return number;
}
function increment() public{
number += 1;
}
}
当调用函数和被调用函数不在一个合约当中时,我们需要在调用函数所在的合约当中引入被调用函数所在的合约
import “./callee.sol”;
通过接口调用
调用者不再需要被调用者的原文件,不需要import导入
需要写一个接口:
// 接口
interface CalleeI{
// 接口指向的函数
function get_number() external view returns(uint256);
}
之后在caller当中修改调用callee函数的语句:
// 使用接口CalleeI强制转换合约地址为被调用的合约
CalleeI calleeContract = CalleeI(callee);
之前使用import导入的方式当中调用callee函数的语句:
Callee calleeContract = Callee(callee);
- 上课代码
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.2 <0.9.0;
contract Callee1{
uint256 count = 0;
function getCount()public view returns(uint256){
return count;
}
function increment()public{
count +=1;
}
}
contract Callee2{
uint256 count = 0;
function getCount()public view returns(uint256){
return count;
}
function increment()public{
count +=2;
}
}
调用过程中的上下文变量
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract Caller{
address callee;
// 调用者拿到被调用者的合约地址
constructor(address _callee){
callee = _callee;
}
function setCalleeX(uint256 _x) public{
// 把地址强制转换成合约对象
Callee calleeC = Callee(callee);
calleeC.setX(_x);
}
}
contract Callee{
uint256 public x;
function setX(uint256 _x) public{
x = _x;
}
}
关于各个函数合约的调用者
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract Caller{
address callee;
address public whocallCaller;
// 调用者拿到被调用者的合约地址
constructor(address _callee){
callee = _callee;
}
function setCalleeX(uint256 _x) public{
whocallCaller = msg.sender;
// 把地址强制转换成合约对象
Callee calleeC = Callee(callee);
calleeC.setX(_x);
}
}
contract Callee{
uint256 public x;
address public whocallCallee;
function setX(uint256 _x) public{
whocallCallee = msg.sender;
x = _x;
}
}
Caller的调用者是外部账号(eoa),Callee的调用者是Caller
利用上下文变量tx来查看触发transaction的外部账号
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract Caller{
address callee;
// 在调用链条上出发transaction的外部账号(也就是eoa)
address public eoa;
address public whocallCaller;
// 调用者拿到被调用者的合约地址
constructor(address _callee){
callee = _callee;
}
function setCalleeX(uint256 _x) public{
eoa = tx.origin;
whocallCaller = msg.sender;
// 把地址强制转换成合约对象
Callee calleeC = Callee(callee);
calleeC.setX(_x);
}
}
contract Callee{
uint256 public x;
address public whocallCallee;
address public eoa;
function setX(uint256 _x) public{
eoa = tx.origin;
whocallCallee = msg.sender;
x = _x;
}
}
this 关键字
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract Caller{
address callee;
// 在调用链条上出发transaction的外部账号(也就是eoa)
address public eoa;
address public whocallCaller;
// 调用者拿到被调用者的合约地址
constructor(address _callee){
callee = _callee;
}
function callSetCalleesetX(uint256 _x) public{
// 合约内部函数之间的调用 不会产生新的message
// this关键字会使函数的调用产生新的message 这个message就是这个合约自己不再是eoa
this.setCalleeX(_x);
}
function setCalleeX(uint256 _x) public{
eoa = tx.origin;
whocallCaller = msg.sender;
// 把地址强制转换成合约对象
Callee calleeC = Callee(callee);
calleeC.setX(_x);
}
}
contract Callee{
uint256 public x;
address public whocallCallee;
address public eoa;
function setX(uint256 _x) public{
eoa = tx.origin;
whocallCallee = msg.sender;
x = _x;
}
}
当合约当中的一个函数要调用另一个可见性为external的函数的时候,必须要使用this关键字 否则会报错
contract Caller{
address callee;
// 在调用链条上出发transaction的外部账号(也就是eoa)
address public eoa;
address public whocallCaller;
// 调用者拿到被调用者的合约地址
constructor(address _callee){
callee = _callee;
}
function callSetCalleesetX(uint256 _x) public{
// 合约内部函数之间的调用 不会产生新的message
// this关键字会使函数的调用产生新的message 这个message就是这个合约自己不再是eoa
this.setCalleeX(_x);
}
function setCalleeX(uint256 _x) external{
eoa = tx.origin;
whocallCaller = msg.sender;
// 把地址强制转换成合约对象
Callee calleeC = Callee(callee);
calleeC.setX(_x);
}
}
erc 和 rfc
函数的动态调用
Call调用语法:
因为call是address的语法,所以调用call的对象必须是一个地址,call的函数是一个字节数组,
sig是函数的签名 keccak256是一种哈希算法
把函数签名和参数列表作为参数输入 返回打包之后的列表
动态调用代码:
Callee合约:
contract Callee{
uint public x;
uint public y;
function setXAndY(uint _x,uint _y) external {
x = _x;
y = _y;
}
}
Caller合约
contract Caller{
address callee;
constructor(address _a){
callee = _a;
}
function setCalleeX(uint _x,uint _y) external{
// (函数签名,参数列表)
// 被调合约的函数的签名(只需要参数的类型 不要参数的名称) 参数列表
bytes memory data = abi.encodeWithSignature("setXAndY(uint256, uint256)",_x,_y);
(bool success,bytes memory rdata) = callee.call(data);
if(!success){
revert("setXAndY fail!"); // 终止执行
}
}
}
调用者合约当中通过函数参数传递然后进行转化的数据就是data 而这个data可以在被调用者合约当中通过上下文变量得到 验证这两个data是相同的
contract Caller{
address callee;
bytes public callee_setxy_data; // 使用编码形成的data
constructor(address _a){
callee = _a;
}
function setCalleeX(uint _x,uint _y) external{
// (函数签名,参数列表)
// 被调合约的函数的签名(只需要参数的类型 不要参数的名称) 参数列表
bytes memory data = abi.encodeWithSignature("setXAndY(uint256,uint256)",_x,_y);
callee_setxy_data = data;
(bool success,bytes memory rdata) = callee.call(data);
if(!success){
revert("setXAndY fail!"); // 终止执行
}
}
}
contract Callee{
uint public x;
uint public y;
bytes public callee_data;
function setXAndY(uint _x,uint _y) external {
callee_data = msg.data; // 调用者通过函数参数传过来的数据
x = _x;
y = _y;
}
}
备胎fallback函数
- fallback函数的可见度必须是external
- fallback函数的名称就是fallback
- 书写方式:
fallback()external{
// 函数语句
}
如果在调用者合约当中的的调用函数语句出现了错误,导致无法正常调用应该要调用的函数,就会触发被调用合约当中的fallback函数。
Gas与转账收款
Payable关键字(也是函数的一种属性),用于修饰函数,表示该函数可以接受转账,调用该函数当中的data前四位函数选择器对应这个函数时,message当中的value可以大于0,函数当中带的钱会放在value当中。
message : from to value data(前四位表示函数选择器,后边表示函数的参数)
contract Storage {
uint256 number;
function store(uint256 num) public payable {
number = num;
}
}
转账收钱
contract Caller {
address callee;
constructor(address _callee){
callee = _callee;
}
// 首先定义一个存钱的函数,才能完成下面转账的功能
function deposit () public payable {
}
// 转账操作
function callCalllee() public payable {
bytes memory data = abi.encodeWithSignature("receiveMoney()");
(bool success,bytes memory r_data) = callee.call{value:1 ether}(data);
if (!success){
revert("sdajshd Failed!");
}
}
}
contract Callee{
uint256 number;
// 使fallback函数也具有收钱的功能
fallback() external payable{
}
function receiveMoney () public payable{
}
}
如果在交易
的时候函数签名出现问题,那么就不能成功完成转账这个操作,这时就要用到fallback函数,为了转账成功,所以要给fallback函数一个payable属性,使他可以收钱。
因为有了fallback函数,所以reteiveMoney函数就不会再起到作用。所以在Caller合约当中调用函数时什么也不调用就可以,传一个空字符串就行。
10.31
合约的边界性
非合约地址不一定是合法的外部账号,所以并不一定可以完成接受转账的功能
11.5
内存动态数组和成员变量动态数组的区别
- 内存动态数组位于memory 中,成员变量动态数组位于storage中。
- 成员变量数组初始长度为0,通过push,pop的动态变化,内存动态数组在程序运行时分配固定长度,一旦分配不可变
整型溢出的解决办法
- 使用高版本
- 使用safemath库
mapping为什么不能作为public函数的参数
- 因为public函数可能被链下的其他函数和合约调用,而mapping又不能被拷贝,所以不能作为public函数的参数
引用拷贝和值拷贝
- 变量本身的指针旋转发生变化,指向其他变量
- 直接把变量的值拿过来,是数据块本身发生的拷贝
使用mapping和msg
因为java当中引用类型的赋值一定是引用拷贝,不会发生值拷贝
netmask是一个钱包 适用于以太坊和跟他兼容的区块链
web3js 访问合约需要合约地址和abi信息
dapp(decentralized application)去中心化
- 区块链,智能合约是去中心化的
- 如果一个应用的核心业务逻辑是构建在区块链和智能合约上就是DAPP
读取合约函数 getOwner 更改合约状态changeOwner
通过import接口调用合约的好处
- 不容易写错
函数的签名和参数编码 data
函数调用者传过来多少钱 value
合约的调用者 sender
触发调用链条的链下的钱包地址 tx.origin
msg变化规则(沿着调用链条):
- 函数内部调用msg不变
- 跨月合约调用,调用者向被调用者发送message,msg发生变化
- 通过this关键字调用内部函数,msg变化
返回值是struct时,如何解码?