在编写sol文件之前要加上
// SPDX-License-Identifier: MIT
或者
// SPDX-License-Identifier: SimPL-3.0
数据类型
特殊变量/全局变量,是全局可用的变量
Solidity 特殊变量/全局变量
Solidity 变量作用域
局部变量的作用域仅限于定义它们的函数,但是状态变量可以有四种作用域类型:
- public – 公共状态变量可以在内部访问,也可以从外部访问。对于公共状态变量,将自动生成一个 getter 函数。
- private – 私有状态变量只能从当前合约内部访问,派生合约内不能访问。
- internal – 内部状态变量只能从当前合约或其派生合约内访问。
- external - 外部状态变量只能在合约之外调用 ,不能被合约内的其他函数调用。
Solidity 条件运算符
? : (条件运算符 )
如果条件为真 ? 则取值X : 否则值Y
function getResult() public pure returns(uint){
uint a = 1; // 局部变量
uint b = 2;
uint result = (a > b? a: b); //条件运算
return result;
}
输出结果
{
"0": "uint256: 2"
}
循环语句
while循环
function whilexunhuan(uint loop) public pure returns (uint) {
uint sum = 0;
uint i = 1;
while (i <= loop) {
sum += i;
i++;
}
return sum;
}
for循环
function forXunhuan(uint loop) public pure returns (uint) {
uint sum = 0;
for (uint i = 1;i <= loop;i++) {
sum += i;
}
return sum;
}
do-while循环
function doWhileXunhuan(uint loop) public pure returns (uint) {
uint sum = 0;
uint i = 1;
do{
sum += i;
i++;
} while (i < loop);
return sum;
}
pure和view
solidity pure函数,也就是纯函数,是指函数不会读取或修改状态。
换言之,solidity pure函数不会操作链上数据。
solidity view函数,也就是视图函数,是指函数只会读取状态,不会修改状态。
换言之,solidity view函数只会读取链上数据,不会修改链上数据。
构造函数
Solidity构造函数是一个特殊函数,它仅能在智能合约部署的时候调用一次,之后就不能再次被调用。
Solidity构造函数常用来进行状态变量的初始化工作。
Solidity编译器中,使用关键词 constructor 作为构造函数。
uint public user;
address public owner;
constructor (uint _user) {
// 将部署者地址存储到owner变量
owner = msg.sender;
user = _user;
}
onlyOwner修饰符
/**
* @dev 调用者不是‘主人’,就会抛出异常
*/
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
onlyOwner 函数修饰符是这么用的:
contract MyContract is Ownable {
event LaughManiacally(string laughter);
//注意! `onlyOwner`上场 :
function likeABoss() external onlyOwner {
LaughManiacally("Muahahahaha");
}
}
Solidity 加密函数
- keccak256(bytes memory) returns (bytes32) 计算输入的Keccak-256散列。
- sha256(bytes memory) returns (bytes32) 计算输入的SHA-256散列。
- ripemd160(bytes memory) returns (bytes20) 计算输入的RIPEMD-160散列。
- ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address) 从椭圆曲线签名中恢复与公钥相关的地址,或在出错时返回零。函数参数对应于签名的ECDSA值: r – 签名的前32字节; s: 签名的第二个32字节; v: 签名的最后一个字节。这个方法返回一个地址。
keccak256加密
function callkeccak256(string memory a) public pure returns (bytes memory) {
bytes memory byteArray = bytes(a);
bytes32 hash = keccak256(byteArray);
return abi.encodePacked(hash);
}
Solidity 不可变量 immutable
Solidity immutable 是另一种常量的表达方式。与常量类似,但是不必硬编码,可以在构造函数时传值,部署后无法改变。
immutable 不可变量同样不会占用状态变量存储空间,在部署时,变量的值会被追加的运行时字节码中, 因此它比使用状态变量便宜的多,也同样带来了更多的安全性。
address public immutable owner = msg.sender;
Solidity 合约继承
virtual 和 override
solidity 引入了 virtual,override 关键字,用于重写函数。
父合约可以使用 virtual 关键字声明一个虚函数,子合约使用 override 关键字来覆盖父合约的方法
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract test1{
string public name;
uint public age;
function getSalary() external pure virtual returns(string memory){
return "Hello World";
}
}
contract test2 is test1{
function getSalary() external pure override returns(string memory){
return "hahahaha";
}
}
abstract
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
abstract contract test3{ //抽象合约
function getTest() public pure virtual returns (string memory);
}
contract test4 is test3 { //实现合约
function getTest() public pure override returns (string memory) {
return "I am an implementation class";
}
}
Solidity 异常处理
Solidity 是通过回退状态的方式来处理异常错误。
Solidity 发生异常时,会撤消当前调用和所有子调用改变的状态,同时给调用者返回一个错误标识。
Solidity 提供了require 、assert 和 revert 来处理异常。
require
uint a = 8;
function shuru(uint b) public view returns (uint) {
require(b == a, "b is not equal to a");
return 0;
}
随便输入一个不等于8的数字提示
assert
function shuru2(uint b) public view returns (uint) {
assert(a == b);
return 0;
}
随便输入一个不等于8的数字提示
require、assert 使用场景
require() 函数用于检测输入变量或状态变量是否满足条件,以及验证调用外部合约的返回值。
require() 语句的失败报错,应该被看作一个正常的判断语句流程不能通过的事件。
assert()语句的失败报错,意味着发生了代码层面的错误事件,很大可能是合约中有一个bug需要修复。
delete
用于将某个变量重置为初始值。对于整数,运算符的效果等同于a = 0。而对于定长数组,则是把数组中的每个元素置为初始值,变长数组则是将长度置为0。对于结构体,也是类似,是将所有的成员均重置为初始值。
uint data;
function change(uint i) internal {
data = i;
}
function getData() public returns (uint) {
delete data;
return data;
}
接受函数receive
// 定义事件
event Received(address Sender, uint Value);
// 接收ETH时释放Received事件
receive() external payable {
emit Received(msg.sender, msg.value);
}
回退函数
event fallbackCalled(address Sender, uint Value, bytes Data);
// fallback
function fallback() external payable{
emit fallbackCalled(msg.sender, msg.value, msg.data);
}
回退函数fallback和receive区别
简单来说,合约接收ETH时,msg.data为空且存在receive()时,会触发receive();msg.data不为空或不存在receive()时,会触发fallback(),此时fallback()必须为payable。
receive()和payable fallback()均不存在的时候,向合约直接发送ETH将会报错(你仍可以通过带有payable的函数向合约发送ETH)。
触发fallback() 还是 receive()?
接收ETH
|
msg.data是空?
/ \
是 否
/ \
receive()存在? fallback()
/ \
是 否
/ \
receive() fallback()
特点
- 一个合约最多存在一个回退函数。
- 它必须被标记为外部(external)函数。
selfdestruct 自毁函数
msg.sender和tx.origin的区别
在Solidity中,
msg.sender
和tx.origin
都代表了交易的发送者,但两者的含义和应用场景有所不同。
msg.sender
:它代表直接调用智能合约函数的账户地址或智能合约地址。无论是外部账户还是智能合约内部的其他合约,只要是直接发起调用的,其地址都会被记录为msg.sender
。这意味着,如果一个合约A内部调用了另一个合约B的一个函数,那么在合约B中,msg.sender
将表示合约A的地址。tx.origin
:它代表整个交易过程中最初的那个交易发送方的地址。这通常是一个外部账户的地址,因为只有外部账户的地址才被视为交易的发起者。在智能合约内部,如果一个合约接收到来自外部账户的交易调用,那么在合约内部,tx.origin
将表示发起该交易的外部账户地址。
简而言之,
msg.sender
关注的是直接调用者,而tx.origin
关注的是原始交易的发送者。在实际应用中,开发者需要根据具体的场景和需求来决定使用哪一个。
abi编码
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;
contract Test {
uint256 x = 10;
address addr = 0x13a6D1fe418de7e5B03Fb4a15352DfeA3249eAA4;
string str = "This is China";
uint256[2] arr = [1, 2];
function core(uint256 _x, address _addr, string calldata _str, uint256[2] calldata _arr) public {
}
function testEncode() public view returns (bytes memory result) {
result = abi.encode(x, addr, str, arr);
}
function testEncodePacked() public view returns (bytes memory result) {
result = abi.encodePacked(x, addr, str, arr);
}
function testEncodeWithSignature() public view returns (bytes memory result) {
result = abi.encodeWithSignature("core(uint256,address,string,uint256[2])", x, addr, str, arr);
}
function testEncodeWithSelector() public view returns (bytes memory result) {
result = abi.encodeWithSelector(bytes4(keccak256("core(uint256,address,string,uint256[2])")), x, addr, str, arr);
}
function testDecode() public view returns (uint256 _x, address _addr, string memory _str, uint256[2] memory _arr) {
bytes memory result = testEncode();
return abi.decode(result, (uint256, address, string, uint256[2]));
}
}
abi.encodePacked(x, addr, str, arr) 的作用是将参数 x、addr、str 和 arr 进行打包编码。在 Solidity 中,当需要将多个类型不同的参数一起传递给一个函数时,可以使用 abi.encodePacked() 函数将这些参数打包成一个字节数组。这样可以减少调用数据的成本,因为打包后的数据只需要占用一个存储空间。
abi.encodeWithSignature() 函数用于将参数编码为字节数组,并附加一个签名。这个签名可以用于在智能合约中调用该函数时进行验证。
具体来说,abi.encodeWithSignature(“core(uint256,address,string,uint256[2])”, x, addr, str, arr) 的作用是将参数 x、addr、str 和 arr 按照指定的函数签名 “core(uint256,address,string,uint256[2])” 进行编码,生成一个字节数组。这个字节数组可以作为交易数据或消息体发送给其他智能合约。
需要注意的是,使用 abi.encodeWithSignature() 函数时,需要确保传入的参数类型与函数签名中的参数类型相匹配,否则会导致编码失败。
abi.encodeWithSelector() 函数用于将参数编码为字节数组,并附加一个选择器。这个选择器可以用于在智能合约中调用该函数时进行验证。
具体来说,abi.encodeWithSelector(bytes4(keccak256(“core(uint256,address,string,uint256[2])”)), x, addr, str, arr) 的作用是将参数 x、addr、str 和 arr 按照指定的函数签名 “core(uint256,address,string,uint256[2])” 进行编码,生成一个字节数组。这个字节数组可以作为交易数据或消息体发送给其他智能合约。
需要注意的是,使用 abi.encodeWithSelector() 函数时,需要确保传入的参数类型与函数签名中的参数类型相匹配,否则会导致编码失败。