Solidity简明教程本教程适用于有语言基础,速查或者快速入门。
<!--StartFragment-->
本教程适用于有语言基础,速查或者快速入门。
// SPDX-License-Identifier: MIT
一般放在文件开头
更多内容请查看https\://learnblockchain.cn/docs/solidity/layout-of-source-files.html
pragma solidity ^0.5.2;
含义是既不允许低于 0.5.2 版本的编译器编译, 也不允许高于(包含) 0.6.0
版本的编译器编译(第二个条件因使用 ^
被添加)。
pragma solidity >=0.6.12 <0.9.0;
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
contract DefaultValues {
bool public b; //false
uint public u; //0
int public i; //0
address public a; //0x0000000(一共40个0,20位16进制数字)
bytes32 public b32; //0x00000 一共64个0,32位
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
contract Constants {
address public constant MY_ADDRESS = 0x2E35C375782713b7feedEd99B18C2F2728B2566D;
uint public constant MY_UINT = 123;
}
contract Var {
address public myaddr = 0x2E35C375782713b7feedEd99B18C2F2728B2566D;
}
constant定义常量
常量和变量消耗的gas不一样,deploy之后点击MY_ADDRESS
可以查看gas费用是373
execution cost 373 gas (Cost only applies when called by a contract)
下面这是Var合约内读取myaddr
消耗的gas
execution cost 2483 gas (Cost only applies when called by a contract)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract IfElse {
function example(uint x) external pure returns (uint) {
if (x < 10) {
return 1;
} else if ( x < 20) {
return 2;
} else {
return 3;
}
}
function ternary(uint x) external pure returns (uint) {
return x < 10 ? 1 : 2;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract ForAndWhile {
function loops() external pure {
for (uint i = 0; i < 10; ++i) {
//code
if (i == 2) continue;
if (i == 4) {
break;
}
}
int b = 3;
while(b > 0) {
--b;
}
}
}
和C语言一样,同时要注意控制循环次数,太多的循环会导致gas费用的高昂
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
// require, revert, assert
// - gas refund, state updates are reverted
// 8.0 version 更新了,custom error, save gas
contract Error {
function testRequire(uint _i) public pure {
require(_i <= 10, "i > 10");
//code
}
function testRevert(uint _i) public pure {
if(_i > 10) {
revert("i > 10");
}
//code
}
uint public num = 123;
function testAssert() public view {
assert(num == 123);
}
function foo(uint _i) public {
num += 1;
require(_i < 10); //如果i不符合条件,前面+1的num会被回滚,状态不会被更改,gas退还
}
function testCustomError1(uint _i) public pure {
require(_i <= 10, "very long error message xxxxxxxxxxxxxxx");//使用require时,如果error信息很长,会消耗很多gas
}
error MyError();
function testCustomError2(uint _i) public pure {
if (_i > 10) revert MyError(); //通过这种方式触发自定义报错
}
//同时可以:
error MyError2(address caller, uint i);
function testCustomError3(uint _i) public view {
if (_i > 10) revert MyError2(msg.sender, _i); //通过这种方式触发自定义报错
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
contract Counter {
uint public cnt;
function inc() external {
cnt += 1;
}
function dec() external {
cnt -= 1;
}
}
external:外部可视,意思是在合约内部的其他函数不可以调用,只能被外部读取
public:公开可视,内部外部都可以调用
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract FUnctionOutputs {
function returnMany() public pure returns (uint, bool) {
return (1, true);
}
function returnManyWithname() public pure returns (uint x, bool b) {
return (1, true);
}
//隐式赋值返回
function assigned() public pure returns (uint x, bool b) {
x = 1;
b = true;
}
//合约内调用
function otherfunc() public pure {
(uint a, bool b) = returnMany();
(, bool _b) = returnManyWithname();
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract FunctionModifier {
bool public paused;
uint public cnt;
function setPasuse(bool _paused) external {
paused = _paused;
}
function inc() external {
require(!paused, "paused");
cnt += 1;
}
function dec() external {
require(!paused, "paused");
cnt -= 1;
}
//当我们期望合约暂停时cnt不被修改,我们可以在修改的func中添加require,函数修改器支持更强大的操作,
//有点类似于C语言中,把暂停的判断提炼到一个判断函数内,然后在修改的函数都提前调用一下判断函数
//写法:
modifier whenNotPaused() {
require(!paused, "paused");
_; //此处代表,使用此修改器的函数的其他的代码在哪里运行
}
function new_incOr_dec() external whenNotPaused {
//cnt += 1; or cnt -= 1;
}
//带参数的函数修改器
modifier cap(uint _x) {
require(_x < 100, "x >= 100");
_;
}
function incBy(uint _x) external whenNotPaused cap(_x) {
cnt += _x;
}
//sanddwich写法
//下面相当于把foo中对cnt的一部分操作提到了修改器中
//cnt 先执行+=10,再返回foo执行+=1,再返回sandwich执行*=2
modifier sandwich() {
cnt += 10;
_;
cnt *= 2;
}
function foo() external sandwich {
cnt += 1;
}
}
合约部署时被调用一次,之后再也不能被调用
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract Constructor {
address public owner;
uint public x;
constructor(uint _x) {
owner = msg.sender;
x = _x;
}
//通过传参赋予x值,ctrl+s编译好之后,部署时,Deploy旁边会多一个输入框,代表构造函数的参数列表
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract Array {
uint[] public nums = [1, 2, 3];
uint[10] public numsFixed= [4, 5, 6];
function examples() external {
nums.push(4); //[1, 2, 3, 4]; 适用于不定长数组
nums[1] = 9; //下标,和C语言一样[1, 9, 3, 4]
delete nums[1]; //[1, 0, 3, 4] !不能减少数组长度
nums.pop(); //[1, 0, 3] 类似于stl中的stack,减少了数组长度
uint len = nums.length; //长度
//create array in memory
uint[] memory arr = new uint[](5);
//在内存中的不允许pop、push
}
function returnArray() external view returns (uint[] memory) {
return nums;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract Array {
mapping(address => uint) public balances;
mapping(address => mapping(address => bool)) public isFriend;
function examples() external {
balances[msg.sender] = 123;
uint bal = balances[msg.sender];
uint bal2 = balances[address(1)]; // 0
balances[msg.sender] += 456;
delete balances[msg.sender]; //0
isFriend[msg.sender][address(this)] = true;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
//结合数组和mapping
contract Structs {
struct Car {
string model;
uint year;
}
Car public car;
Car[] public cars;
mapping(address => Car[]) public carsByOwner;
function examples() external {
Car memory toyota = Car("Toyota", 1999);
Car memory lambo = Car({year: 111, model: "Lamborghini"});
Car memory zero;
zero.model = "aaaa";
zero.year = 1993;
cars.push(zero);
cars.push(toyota);
cars.push(lambo);
cars.push(Car("Tesla", 1991));
Car memory temp = cars[0];
Car storage real = cars[0];
real.year = 0; //storage带有指针效果
delete real.year; //恢复cars[0]的成员year的默认值
delete cars[1]; //默认值全部恢复
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
//结合数组和mapping
contract Enums {
enum Status {
None,
Pending,
Shipped,
Completed,
Rejected,
Canceled
}
Status public status;
struct Order {
address buyer;
Status status;
}
Order[] public orders;
function get() external view returns (Status) {
return status;
}
function set(Status _status) external {
status = _status;
}
function ship() external {
status = Status.Shipped;
}
function reset() external {
delete status; //枚举类型的默认值是第一个字段,本例为"None"
}
}
管理员合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract Owner {
address public owner;
constructor() {
owner = msg.sender;
}
modifier onlyOwner() {
require(owner == msg.sender, "no owner");
_;
}
function setOwner(address _newowner) external onlyOwner {
require(_newowner != address(0), "no 0 address");
owner = _newowner;
}
function ownerUse() external onlyOwner {
//code
}
function anyoneUse() external {
//code
}
}
<!--StartFragment-->
memory 内存的存储类型,局部变量,生命周期随着作用域结束而结束
storage 状态变量的存储类型,有点像引用或者指针
calldata 只能用在参数中,如果使用calldata,会节约gas
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;
contract DataLocations {
struct Mystruct {
uint foo;
string text;
}
mapping(address => Mystruct) public mys;
function examples(uint[] calldata y, string calldata s) external returns (uint[] memory) {
mys[msg.sender] = Mystruct({foo: 123, text: "bar"});
Mystruct storage p = mys[msg.sender]; //此处也可以直接mys[msg.sender].text进行修改,当修改量少时消耗较少的gas,修改项多时使用storage
p.text = "new_text"; //mys[msg.sender].text is: new_text
Mystruct memory readonly = mys[msg.sender];
readonly.foo = 444; //but ..mys[msg.sender].foo is: 123
_internal(y);
//1. 如果_internal函数的参数列表中的y是memory类型,那么这里调用的时候会进行一次拷贝,消耗gas
//如果使用calldata那么可以直接传递过去,不会发生拷贝
uint[] memory memArr = new uint[](5);
memArr[0] = 1;
return memArr;
}
function _internal(uint[] calldata y) private {
uint x = y[0];
}
}
记录当前contract状态的方法,不会记录在状态变量之中,而是体现在区块链浏览器上或者交易记录中的log
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;
contract Event {
event Log(string message, uint val);
event IndexLog(address indexed sender, uint val, uint a, uint b); //添加indexed 链外可以搜索查询
function examples() external {
emit Log("hahaha", 1234);
emit IndexLog(msg.sender, 22, 22, 22); //在链外通过工具可以查询该地址的一些事件
}
event Message(address indexed _from, address indexed _to, string message);
function sendMessage(address _to, string calldata message) external {
emit Message(msg.sender, _to, message);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;
contract A {
function foo() public pure virtual returns (string memory) {
return "A";
}
function bar() public pure virtual returns (string memory) {
return "A";
}
function baz() public pure returns (string memory) {
return "A";
}
}
contract B is A { //继承写法
function foo() public pure override returns (string memory) {
return "B";
}
function bar() public pure virtual override returns (string memory) {
return "A";
}
}
contract C is B {
function bar() public pure override returns (string memory) {
return "C";
}
}
优先写比较基类的继承关系,
X基类,Y is X
Z is X, Y
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;
contract X {
function foo() public pure virtual returns (string memory) {
return "X";
}
function bar() public pure virtual returns (string memory) {
return "X";
}
function x() public pure returns (string memory) {
return "X";
}
}
contract Y is X { //继承写法
function foo() public pure virtual override returns (string memory) {
return "Y";
}
function bar() public pure virtual override returns (string memory) {
return "Y";
}
function y() public pure returns (string memory) {
return "Y";
}
}
contract Z is X, Y {
function foo() public pure override(X, Y) returns (string memory) {
return "Z";
}
function bar() public pure override(X, Y) returns (string memory) {
return "Z";
}
}
contract X {
function foo() public pure virtual returns (string memory) {
return "X";
}
function bar() public pure virtual returns (string memory) {
return "X";
}
function x() public pure returns (string memory) {
return "X";
}
}
contract Y is X {
X.foo(); //1
super.foo(); //2 多重继承时,父类的func都会被执行
}
关键词:immutable
第一次定义后,就不可被改变
一般用于你不知道其初始化,但是又是常量的情况。第一次赋值后变为常量。
关键词:payable
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;
contract Payable {
address payable public owner; //可发送ETH主币
constructor() {
owner = payable(msg.sender); //使用时要给带有payable属性
}
function deposit() external payable { //函数可接受ETH主币的传入
}
function getBalance() external view returns (uint) {
return address(this).balance;
}
}
当你调用合约中不存在的函数时,或者向合约发送主币的时候,都会调用回退函数
当你期望发送主币的时候触发fallback,那么你需要给fallback方法加上payable属性,
solidity8.0以上,新增receive用来只接受主币
触发逻辑:当合约收到主币时,判断是否调用了数据,也就是msg.data是否为空,如果没有调用数据,执行fallback。如果为空,判断receive是否存在,存在调用receive,调用receive,如果不为空,不存在调用fallback
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;
contract Fallback {
event Log(string func, address sender, uint value, bytes data);
fallback() external payable {
emit Log("fallback", msg.sender, msg.value, msg.data);
}
receive() external payable {
emit Log("receive", msg.sender, msg.value, "");
}
}
transfer
消耗2300gas,gas耗费完或者其他异常情况,revert
send
消耗2300gas,返回bool值
call
会把剩余的gas都发送过去
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;
//transfer 2300gas revert
//send 2300 gas, return bool
//call all gas, returns bool and data
contract SendETH {
constructor() payable {
}
receive() external payable {
}
//三种发送ETH主币的方法
function Sendbytransfer(address payable _to) external payable {
_to.transfer(123);
}
function Sendbysend(address payable _to) external payable {
bool sent = _to.send(123);
require(sent, "send failed");
}
function Sendbycall(address payable _to) external payable returns (bytes memory) {
(bool success, bytes memory data) = _to.call{value: 123}("");
require(success, "send failed");
return data;
}
}
//接受ETH的合约,即上面的发送合约发送ETH到该地址
contract Ethreceive {
event Log(uint amount, uint gas);
receive() external payable {
emit Log(msg.value, gasleft());
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;
contract EthWallet {
address payable public owner;
constructor() payable {
owner = payable(msg.sender);
}
receive() external payable { }
function withdraw(uint _amount) external {
require(msg.sender == owner, "no owner");
owner.transfer(_amount); //两种效果一样,但是msg.sender在内存,节约gas
//payable(msg.sender).transfer(_amount);
}
function getBalance() external view returns (uint) {
return address(this).balance;
}
function getOwnBalance() external view returns (uint) {
return msg.sender.balance;
}
}
区分两种不同的调用方式,以及可以在调用的时候发送主币
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;
contract CallTest {
function setOtherContract_X(address _test, uint _x) external {
Test(_test).setX(_x);
}
function getOtherContract_X(Test _test) external view returns (uint) {
return _test.getX(); //直接将另一个合约名字作为类型传参,然后调用也可
}
function setOtherContract_XandETH(address _test, uint _x) external payable {
Test(_test).setXandReceiveETH{value: msg.value}(_x);
}
function getOtherContract_XandETH(Test _test) external view returns (uint, uint) {
return _test.getXandETH();
}
}
contract Test {
uint public x;
uint public value;
function setX(uint _x) external {
x = _x;
}
function getX() external view returns (uint) {
return x;
}
function setXandReceiveETH(uint _x) external payable {
x = _x;
value = msg.value;
}
function getXandETH() external view returns (uint, uint) {
return (x, value);
}
}
当我们不知道另一个合约的源码,或者另一个合约源码很长,可以通过接口方法来调用
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;
//此为外部其他的合约,我们不知道其源码,但是我们知道它实现了Icounter接口
contract Count {
uint public count;
function inc() external {
count += 1;
}
function dec() external {
count -= 1;
}
}
interface Icounter {
function count() external view returns (uint); //外部合约Count中有count变量,所以这也可以是一个接口
function inc() external;
}
contract CallInterface {
uint public count;
function example(address _cnt) external {
Icounter(_cnt).inc();
count = Icounter(_cnt).count();
}
}
在call合约中使用:
_test.call{value: 111}(abi.encodeWithSignature());
来调用另一个合约的函数,依靠abi.encodeWithSignature
来实现。
其中value: 111
表示在调用是发送主币,也可以带有gasvalue: 111, gas:5000
但是要确保gas够用。
当Test合约的fallback不存在时,使用call调用不存在的函数时,即下文callnoexist
会失败,原理见:回退函数
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;
contract Test {
string public message;
uint public x;
event Log(string message);
fallback() external payable {
emit Log("fallback was called");
}
function foo(string memory _message, uint _x) external payable returns (bool, uint) {
message = _message;
x = _x;
return (true, 11);
}
}
contract Call {
bytes public data;
function callFoo(address _test) external payable {
(bool success, bytes memory _data) = _test.call{value: 111}(abi.encodeWithSignature(
"foo(string, uint256", "call foo", 123
));
require(success, "call failed");
data = _data;
}
function callnoexist(address _test) external {
(bool success, ) = _test.call(abi.encodeWithSignature("noexist"));
require(success, "call failed");
}
}
传统调用:
A call B, send 100wei
B call C, send 50wei
在C的视角
msg.sender = B;
msg.value = 50;
可能发生的状态变量也是在C上,ETH主币也会留在C中
而委托调用:delegatecall
A call B, send 100wei
B delegatecall C
此时,在C的视角
msg.sender = A;
msg.value = 100;
100个ETH主币保存在B,状态也是B中的状态变量发生改变。
人话: 合约X委托调用合约Y的函数,相当于使用了Y的函数作用于自身,在下面的函数中,DelegateCall委托调用了Test中的setVars方法,作用于DelegateCall合约自身。发生改变的也是DelegateCall的状态变量。
tip:两个合约的状态变量等布局要保持一致,不然会发生奇怪的错误,本质上像是内存布局的调用,假设DelegateCall的状态变量和Test的前面的状态变量一样,Test后面新增几个新的状态变量,就不会发生错误。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;
contract Test {
uint public num;
address public sender;
uint public value;
function setVars(uint _num) external payable {
num = _num*2;
sender = msg.sender;
value = msg.value;
}
}
contract DelegateCall {
uint public num;
address public sender;
uint public value;
function setVars(address _test, uint _num) external payable {
//_test.delegatecall (
// abi.encodeWithSignature("setVars(uint256)", _num)
//);
//两种方法效果一样
(bool success, bytes memory _data) = _test.delegatecall (
abi.encodeWithSelector(Test.setVars.selector, _num)
);
require(success, "delegatecall failed");
}
}
可以使用一个合约,创建另一个合约,同时也是可以添加payable方法,使用value
来给合约发送ETH主币。
在ide测试使用的时候,deploy工厂之后,生成了一个account地址,可以通过deploy按钮下面的At address
来直接生成对应的Account合约,然后就可以查看其内容了。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;
contract Account {
address public bank;
address public owner;
constructor(address _owner) payable {
bank = msg.sender;
owner = _owner;
}
}
contract AccountFactory {
Account[] public accounts;
function createAccount(address _owner) external payable {
Account account = new Account{value: 123}(_owner);
accounts.push(account);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;
library Math {
function max(uint x, uint y) internal pure returns (uint) {
return x >= y ? x : y;
}
}
contract Test {
function testMax(uint x, uint y) external pure returns (uint) {
return Math.max(x, y);
}
}
library ArrayLib {
function find(uint[] storage arr, uint x) internal view returns (uint) {
for(uint i = 0; i < arr.length; i++) {
if (arr[i] == x) {
return i;
}
}
revert("not found");
}
}
contract TestArray {
uint[] public arr = [3, 2, 1];
function testFind() external view returns (uint i) {
return ArrayLib.find(arr, 2);
}
}
//更方便的写法
contract TestArray2 {
using ArrayLib for uint[];
uint[] public arr = [3, 2, 1];
function testFind() external view returns (uint i) {
return arr.find(2);
}
}
keccak256
tip:在做hash运算进行编码时,使用abi.encodexxxx编码时,不同的编码方式出来的结果不一致
image-20240630194319168
可以看到一个会补0,另一个不会补0。
那么使用encodePacked
对"AAAA", "BBB"
"AAA", "ABBB"
编码,出来的结果会是一样的。如果在这个的基础上再次进行hash运算,就会导致hash碰撞。所以编码时比较好的方式是采用abi.encode
,或者在要编码的字符串之前添加uint等方式来隔开。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;
contract HashFunc {
function hash(string memory text, uint num, address addr) external pure returns (bytes32) {
return keccak256(abi.encodePacked(text, num, addr));
}
function encode(string memory text1, string memory text2) external pure returns (bytes memory) {
return abi.encode(text1, text2);
}
function encodePacked(string memory text1, string memory text2) external pure returns (bytes memory) {
return abi.encodePacked(text1, text2);
}
}
对一个消息签名分为四步
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;
contract Verifysign {
function verify(address _signer, string memory _message, bytes memory _sign) external pure returns (bool) {
bytes32 messageHash = getMessageHash(_message);
bytes32 ethSignedMessageHash = getEthSignedMessageHash(messageHash);
return recover(ethSignedMessageHash, _sign) == _signer;
}
function getMessageHash(string memory _message) public pure returns (bytes32) {
return keccak256(abi.encodePacked(_message));
}
function getEthSignedMessageHash(bytes32 _messageHash) public pure returns (bytes32) {
return keccak256(abi.encodePacked(
"\x19Ethereum Signed Message:\n32",
_messageHash)); //进行两次hash的原因可能是在数学界一次hash已经有被破解的可能性了
}
function recover(bytes32 _ethSignedMessageHash, bytes memory _sign) public pure returns (address) {
(bytes32 r, bytes32 s, uint8 v) = _split(_sign);
return ecrecover(_ethSignedMessageHash, v, r, s);
}
function _split(bytes memory _sign) internal pure returns (bytes32 r, bytes32 s, uint8 v) {
require(_sign.length == 65, "invalid signature length");
assembly {
r := mload(add(_sign, 32))
s := mload(add(_sign, 64))
v := byte(0, mload(add(_sign, 96)))
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;
contract Killer {
constructor() payable {}
function kill() external {
selfdestruct(payable(msg.sender)); //像该地址强制发送ETH,即使你是不接受ETH主币的合约,也会给你
}
function testCall() external pure returns (uint) {
return 123;
}
}
contract Helper {
function getBalance() external view returns (uint) {
return address(this).balance;
}
function kill(Killer _kill) external {
_kill.kill();
}
}
ERC20标准包含了一组接口IERC20,只要你的代码实现了全部接口,就代表你满足了ERC20标准
<!--EndFragment-->
https\://remix.ethereum.org/
<!--EndFragment-->
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!