Chapter 6 Solidity Example
6.1 1. Hellow world
// SPDX-License-Identifier: MIT
// compiler version must be greater than or equal to 0.8.26 and less than 0.9.0
pragma solidity ^0.8.26;
contract HelloWorld {
string public greet = "Hello World!";
}
6.2 2. 简单合约
修改计数
// SPDX-License-Identifier: MIT
// compiler version must be greater than or equal to 0.8.26 and less than 0.9.0
pragma solidity ^0.8.26;
contract HelloWorld {
string public greet = "Hello World!";
}
6.3 3. 数据类型
- boolean
- uint256
- int56
- adress
// SPDX-License-Identifier: MIT
// compiler version must be greater than or equal to 0.8.26 and less than 0.9.0
pragma solidity ^0.8.26;
contract HelloWorld {
string public greet = "Hello World!";
}
6.4 4. 变量
- loacl
- 正在函数内部声明
- 不存储在区块链
- state
- 在函数外声明
- 存储的区块链
- global
- 提供关于区块链的信息
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract Variables {
// State variables are stored on the blockchain.
string public text = "Hello";
uint256 public num = 123;
function doSomething() public {
// Local variables are not saved to the blockchain.
uint256 i = 456;
// Here are some global variables
uint256 timestamp = block.timestamp; // Current block timestamp
address sender = msg.sender; // address of the caller
}
}
6.5 5. 常量
常量是无法修改的变量,可以节省gas
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract Variables {
// State variables are stored on the blockchain.
string public text = "Hello";
uint256 public num = 123;
function doSomething() public {
// Local variables are not saved to the blockchain.
uint256 i = 456;
// Here are some global variables
uint256 timestamp = block.timestamp; // Current block timestamp
address sender = msg.sender; // address of the caller
}
}
6.6 6. 不可变 Immutable
不可变变量就像常量一样。不可变变量的值可以在构造函数中设置,但不能随后修改。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract Immutable {
// coding convention to uppercase constant variables
address public immutable MY_ADDRESS;
uint256 public immutable MY_UINT;
constructor(uint256 _myUint) {
MY_ADDRESS = msg.sender;
MY_UINT = _myUint;
}
}
6.7 7. 状态变量的读写
要编写或更新状态变量,需要发送事务。另一方面,可以免费读取状态变量,而不需要任何交易费用。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract SimpleStorage {
// State variable to store a number
uint256 public num;
// You need to send a transaction to write to a state variable.
function set(uint256 _num) public {
num = _num;
}
// You can read from a state variable without sending a transaction.
function get() public view returns (uint256) {
return num;
}
}
6.8 8. 以太 Ether 和 Wei
1 Ether = 10^18 wei
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract EtherUnits {
uint256 public oneWei = 1 wei;
// 1 wei is equal to 1
bool public isOneWei = (oneWei == 1);
uint256 public oneGwei = 1 gwei;
// 1 gwei is equal to 10^9 wei
bool public isOneGwei = (oneGwei == 1e9);
uint256 public oneEther = 1 ether;
// 1 ether is equal to 10^18 wei
bool public isOneEther = (oneEther == 1e18);
}
6.9 9. gas
交易需要多少以太币?
你需要支付的以太币金额为:消耗的gas量 × gas价格,其中:
- gas 是计算的单位。
- 消耗的gas 是指在一次交易中使用的总gas量。
- gas价格 是你愿意为每个gas支付的以太币金额。
gas价格较高的交易会优先被纳入区块中。
未使用的gas将会退还。
gas限额
你可以花费的gas量有两个上限:
- gas限额(你愿意在交易中使用的最大gas量,由你自行设定)
- 区块gas限额(一个区块允许的最大gas量,由网络设定)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract Gas {
uint256 public i = 0;
// Using up all of the gas that you send causes your transaction to fail.
// State changes are undone.
// Gas spent are not refunded.
function forever() public {
// Here we run a loop until all of the gas are spent
// and the transaction fails
while (true) {
i += 1;
}
}
}
6.10 10. 条件判断
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract IfElse {
function foo(uint256 x) public pure returns (uint256) {
if (x < 10) {
return 0;
} else if (x < 20) {
return 1;
} else {
return 2;
}
}
function ternary(uint256 _x) public pure returns (uint256) {
// if (_x < 10) {
// return 1;
// }
// return 2;
// shorthand way to write if / else statement
// the "?" operator is called the ternary operator
return _x < 10 ? 1 : 2;
}
}
6.11 11. 循环
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract Loop {
function loop() public {
// for loop
for (uint256 i = 0; i < 10; i++) {
if (i == 3) {
// Skip to next iteration with continue
continue;
}
if (i == 5) {
// Exit loop with break
break;
}
}
// while loop
uint256 j;
while (j < 10) {
j++;
}
}
}
6.12 12. mapping
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract Mapping {
// Mapping from address to uint
mapping(address => uint256) public myMap;
function get(address _addr) public view returns (uint256) {
// Mapping always returns a value.
// If the value was never set, it will return the default value.
return myMap[_addr];
}
function set(address _addr, uint256 _i) public {
// Update the value at this address
myMap[_addr] = _i;
}
function remove(address _addr) public {
// Reset the value to the default value.
delete myMap[_addr];
}
}
contract NestedMapping {
// Nested mapping (mapping from address to another mapping)
mapping(address => mapping(uint256 => bool)) public nested;
function get(address _addr1, uint256 _i) public view returns (bool) {
// You can get values from a nested mapping
// even when it is not initialized
return nested[_addr1][_i];
}
function set(address _addr1, uint256 _i, bool _boo) public {
nested[_addr1][_i] = _boo;
}
function remove(address _addr1, uint256 _i) public {
delete nested[_addr1][_i];
}
}
6.13 13. array
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract Array {
// Several ways to initialize an array
uint256[] public arr;
uint256[] public arr2 = [1, 2, 3];
// Fixed sized array, all elements initialize to 0
uint256[10] public myFixedSizeArr;
function get(uint256 i) public view returns (uint256) {
return arr[i];
}
// Solidity can return the entire array.
// But this function should be avoided for
// arrays that can grow indefinitely in length.
function getArr() public view returns (uint256[] memory) {
return arr;
}
function push(uint256 i) public {
// Append to array
// This will increase the array length by 1.
arr.push(i);
}
function pop() public {
// Remove last element from array
// This will decrease the array length by 1
arr.pop();
}
function getLength() public view returns (uint256) {
return arr.length;
}
function remove(uint256 index) public {
// Delete does not change the array length.
// It resets the value at index to it's default value,
// in this case 0
delete arr[index];
}
function examples() external {
// create array in memory, only fixed size can be created
uint256[] memory a = new uint256[](5);
}
}
6.14 14. 枚举
Solidity 支持枚举,它们对于建模选择和跟踪状态很有用。 枚举可以在合同之外声明。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract Enum {
// Enum representing shipping status
enum Status {
Pending,
Shipped,
Accepted,
Rejected,
Canceled
}
// Default value is the first element listed in
// definition of the type, in this case "Pending"
Status public status;
// Returns uint
// Pending - 0
// Shipped - 1
// Accepted - 2
// Rejected - 3
// Canceled - 4
function get() public view returns (Status) {
return status;
}
// Update status by passing uint into input
function set(Status _status) public {
status = _status;
}
// You can update to a specific enum like this
function cancel() public {
status = Status.Canceled;
}
// delete resets the enum to its first value, 0
function reset() public {
delete status;
}
}
6.15 15. 用户定义类型
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
// Code copied from optimism
// https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/dispute/lib/LibUDT.sol
type Duration is uint64;
type Timestamp is uint64;
type Clock is uint128;
library LibClock {
function wrap(Duration _duration, Timestamp _timestamp)
internal
pure
returns (Clock clock_)
{
assembly {
// data | Duration | Timestamp
// bit | 0 ... 63 | 64 ... 127
clock_ := or(shl(0x40, _duration), _timestamp)
}
}
function duration(Clock _clock)
internal
pure
returns (Duration duration_)
{
assembly {
duration_ := shr(0x40, _clock)
}
}
function timestamp(Clock _clock)
internal
pure
returns (Timestamp timestamp_)
{
assembly {
timestamp_ := shr(0xC0, shl(0xC0, _clock))
}
}
}
// Clock library without user defined value type
library LibClockBasic {
function wrap(uint64 _duration, uint64 _timestamp)
internal
pure
returns (uint128 clock)
{
assembly {
clock := or(shl(0x40, _duration), _timestamp)
}
}
}
contract Examples {
function example_no_uvdt() external {
// Without UDVT
uint128 clock;
uint64 d = 1;
uint64 t = uint64(block.timestamp);
clock = LibClockBasic.wrap(d, t);
// Oops! wrong order of inputs but still compiles
clock = LibClockBasic.wrap(t, d);
}
function example_uvdt() external {
// Turn value type into user defined value type
Duration d = Duration.wrap(1);
Timestamp t = Timestamp.wrap(uint64(block.timestamp));
// Turn user defined value type back into primitive value type
uint64 d_u64 = Duration.unwrap(d);
uint64 t_u54 = Timestamp.unwrap(t);
// LibClock example
Clock clock = Clock.wrap(0);
clock = LibClock.wrap(d, t);
// Oops! wrong order of inputs
// This will not compile
// clock = LibClock.wrap(t, d);
}
}
6.16 16. 结构体
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract Todos {
struct Todo {
string text;
bool completed;
}
// An array of 'Todo' structs
Todo[] public todos;
function create(string calldata _text) public {
// 3 ways to initialize a struct
// - calling it like a function
todos.push(Todo(_text, false));
// key value mapping
todos.push(Todo({text: _text, completed: false}));
// initialize an empty struct and then update it
Todo memory todo;
todo.text = _text;
// todo.completed initialized to false
todos.push(todo);
}
// Solidity automatically created a getter for 'todos' so
// you don't actually need this function.
function get(uint256 _index)
public
view
returns (string memory text, bool completed)
{
Todo storage todo = todos[_index];
return (todo.text, todo.completed);
}
// update text
function updateText(uint256 _index, string calldata _text) public {
Todo storage todo = todos[_index];
todo.text = _text;
}
// update completed
function toggleCompleted(uint256 _index) public {
Todo storage todo = todos[_index];
todo.completed = !todo.completed;
}
}
6.17 17. 数据位置 - storage , memory,calldata
- storage : 存储在区块链上
- memory : 位于内存中,并且在调用函数时存在
- calldata : 含函数参数的特殊数据位置
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract DataLocations {
uint256[] public arr;
mapping(uint256 => address) map;
struct MyStruct {
uint256 foo;
}
mapping(uint256 => MyStruct) myStructs;
function f() public {
// call _f with state variables
_f(arr, map, myStructs[1]);
// get a struct from a mapping
MyStruct storage myStruct = myStructs[1];
// create a struct in memory
MyStruct memory myMemStruct = MyStruct(0);
}
function _f(
uint256[] storage _arr,
mapping(uint256 => address) storage _map,
MyStruct storage _myStruct
) internal {
// do something with storage variables
}
// You can return memory variables
function g(uint256[] memory _arr) public returns (uint256[] memory) {
// do something with memory array
}
function h(uint256[] calldata _arr) external {
// do something with calldata array
}
}
6.18 18. 暂时存储
pragma solidity ^0.8.26;
// Make sure EVM version and VM set to Cancun
// Storage - data is stored on the blockchain
// Memory - data is cleared out after a function call
// Transient storage - data is cleared out after a transaction
interface ITest {
function val() external view returns (uint256);
function test() external;
}
contract Callback {
uint256 public val;
fallback() external {
val = ITest(msg.sender).val();
}
function test(address target) external {
ITest(target).test();
}
}
contract TestStorage {
uint256 public val;
function test() public {
val = 123;
bytes memory b = "";
msg.sender.call(b);
}
}
contract TestTransientStorage {
bytes32 constant SLOT = 0;
function test() public {
assembly {
tstore(SLOT, 321)
}
bytes memory b = "";
msg.sender.call(b);
}
function val() public view returns (uint256 v) {
assembly {
v := tload(SLOT)
}
}
}
contract ReentrancyGuard {
bool private locked;
modifier lock() {
require(!locked);
locked = true;
_;
locked = false;
}
// 35313 gas
function test() public lock {
// Ignore call error
bytes memory b = "";
msg.sender.call(b);
}
}
contract ReentrancyGuardTransient {
bytes32 constant SLOT = 0;
modifier lock() {
assembly {
if tload(SLOT) { revert(0, 0) }
tstore(SLOT, 1)
}
_;
assembly {
tstore(SLOT, 0)
}
}
// 21887 gas
function test() external lock {
// Ignore call error
bytes memory b = "";
msg.sender.call(b);
}
}
6.19 19. 函数
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract Function {
// Functions can return multiple values.
function returnMany() public pure returns (uint256, bool, uint256) {
return (1, true, 2);
}
// Return values can be named.
function named() public pure returns (uint256 x, bool b, uint256 y) {
return (1, true, 2);
}
// Return values can be assigned to their name.
// In this case the return statement can be omitted.
function assigned() public pure returns (uint256 x, bool b, uint256 y) {
x = 1;
b = true;
y = 2;
}
// Use destructuring assignment when calling another
// function that returns multiple values.
function destructuringAssignments()
public
pure
returns (uint256, bool, uint256, uint256, uint256)
{
(uint256 i, bool b, uint256 j) = returnMany();
// Values can be left out.
(uint256 x,, uint256 y) = (4, 5, 6);
return (i, b, j, x, y);
}
// Cannot use map for either input or output
// Can use array for input
function arrayInput(uint256[] memory _arr) public {}
// Can use array for output
uint256[] public arr;
function arrayOutput() public view returns (uint256[] memory) {
return arr;
}
}
// Call function with key-value inputs
contract XYZ {
function someFuncWithManyInputs(
uint256 x,
uint256 y,
uint256 z,
address a,
bool b,
string memory c
) public pure returns (uint256) {}
function callFunc() external pure returns (uint256) {
return someFuncWithManyInputs(1, 2, 3, address(0), true, "c");
}
function callFuncWithKeyValue() external pure returns (uint256) {
return someFuncWithManyInputs({
a: address(0),
b: true,
c: "c",
x: 1,
y: 2,
z: 3
});
}
}
6.20 20. view 和 pure 函数
- View 函数声明不会更改任何状态。
- 纯函数声明不会更改或读取任何状态变量。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract ViewAndPure {
uint256 public x = 1;
// Promise not to modify the state.
function addToX(uint256 y) public view returns (uint256) {
return x + y;
}
// Promise not to modify or read from the state.
function add(uint256 i, uint256 j) public pure returns (uint256) {
return i + j;
}
}
6.21 21. 错误
- Request 用于在执行之前验证输入和条件。
- revert 和 revert 类似
- assert 用于检查永远不应该为 false 的代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract Error {
function testRequire(uint256 _i) public pure {
// Require should be used to validate conditions such as:
// - inputs
// - conditions before execution
// - return values from calls to other functions
require(_i > 10, "Input must be greater than 10");
}
function testRevert(uint256 _i) public pure {
// Revert is useful when the condition to check is complex.
// This code does the exact same thing as the example above
if (_i <= 10) {
revert("Input must be greater than 10");
}
}
uint256 public num;
function testAssert() public view {
// Assert should only be used to test for internal errors,
// and to check invariants.
// Here we assert that num is always equal to 0
// since it is impossible to update the value of num
assert(num == 0);
}
// custom error
error InsufficientBalance(uint256 balance, uint256 withdrawAmount);
function testCustomError(uint256 _withdrawAmount) public view {
uint256 bal = address(this).balance;
if (bal < _withdrawAmount) {
revert InsufficientBalance({
balance: bal,
withdrawAmount: _withdrawAmount
});
}
}
}
6.22 22. 函数修饰符
修饰符是可以在函数调用之前和/或之后运行的代码。
修饰符可用于: 1. 限制访问 2. 确认输入 3. 防止黑客攻击
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract FunctionModifier {
// We will use these variables to demonstrate how to use
// modifiers.
address public owner;
uint256 public x = 10;
bool public locked;
constructor() {
// Set the transaction sender as the owner of the contract.
owner = msg.sender;
}
// Modifier to check that the caller is the owner of
// the contract.
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
// Underscore is a special character only used inside
// a function modifier and it tells Solidity to
// execute the rest of the code.
_;
}
// Modifiers can take inputs. This modifier checks that the
// address passed in is not the zero address.
modifier validAddress(address _addr) {
require(_addr != address(0), "Not valid address");
_;
}
function changeOwner(address _newOwner)
public
onlyOwner
validAddress(_newOwner)
{
owner = _newOwner;
}
// Modifiers can be called before and / or after a function.
// This modifier prevents a function from being called while
// it is still executing.
modifier noReentrancy() {
require(!locked, "No reentrancy");
locked = true;
_;
locked = false;
}
function decrement(uint256 i) public noReentrancy {
x -= i;
if (i > 1) {
decrement(i - 1);
}
}
}
6.23 23 event 事件
event 允许对以太区块链进行日志记录: 1. 监听事件,更新前端 2. 一种便宜的存储方式
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract Event {
// Event declaration
// Up to 3 parameters can be indexed.
// Indexed parameters helps you filter the logs by the indexed parameter
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();
}
}
6.24 24. 事件高级主题
在Solidity中,事件是一种强大的工具,可实现各种高级功能和架构。一些事件的高级用例包括:
- 实时更新和分析的事件过滤与监控
- 事件日志分析与解码,用于数据提取和处理
- 面向去中心化应用(dApps)的事件驱动架构
- 实时通知和更新的事件订阅
事件驱动架构
EventDrivenArchitecture 合约展示了一种事件驱动的架构,在该架构中,事件用于协调和触发流程的不同阶段,例如发起和确认转账。
事件订阅与实时更新
EventSubscription 合约展示了如何实现事件订阅,允许外部合约或客户端订阅事件并在事件发出时接收实时更新。同时,该合约还演示了如何处理事件订阅及管理订阅的生命周期。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.14;
// Event-Driven Architecture
contract EventDrivenArchitecture {
event TransferInitiated(
address indexed from, address indexed to, uint256 value
);
event TransferConfirmed(
address indexed from, address indexed to, uint256 value
);
mapping(bytes32 => bool) public transferConfirmations;
function initiateTransfer(address to, uint256 value) public {
emit TransferInitiated(msg.sender, to, value);
// ... (initiate transfer logic)
}
function confirmTransfer(bytes32 transferId) public {
require(
!transferConfirmations[transferId], "Transfer already confirmed"
);
transferConfirmations[transferId] = true;
emit TransferConfirmed(msg.sender, address(this), 0);
// ... (confirm transfer logic)
}
}
// Event Subscription and Real-Time Updates
interface IEventSubscriber {
function handleTransferEvent(address from, address to, uint256 value)
external;
}
contract EventSubscription {
event LogTransfer(address indexed from, address indexed to, uint256 value);
mapping(address => bool) public subscribers;
address[] public subscriberList;
function subscribe() public {
require(!subscribers[msg.sender], "Already subscribed");
subscribers[msg.sender] = true;
subscriberList.push(msg.sender);
}
function unsubscribe() public {
require(subscribers[msg.sender], "Not subscribed");
subscribers[msg.sender] = false;
for (uint256 i = 0; i < subscriberList.length; i++) {
if (subscriberList[i] == msg.sender) {
subscriberList[i] = subscriberList[subscriberList.length - 1];
subscriberList.pop();
break;
}
}
}
function transfer(address to, uint256 value) public {
emit LogTransfer(msg.sender, to, value);
for (uint256 i = 0; i < subscriberList.length; i++) {
IEventSubscriber(subscriberList[i]).handleTransferEvent(
msg.sender, to, value
);
}
}
}
6.25 25. 构造函数
构造函数是在创建契约时执行的可选函数。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
// Base contract X
contract X {
string public name;
constructor(string memory _name) {
name = _name;
}
}
// Base contract Y
contract Y {
string public text;
constructor(string memory _text) {
text = _text;
}
}
// There are 2 ways to initialize parent contract with parameters.
// Pass the parameters here in the inheritance list.
contract B is X("Input to X"), Y("Input to Y") {}
contract C is X, Y {
// Pass the parameters here in the constructor,
// similar to function modifiers.
constructor(string memory _name, string memory _text) X(_name) Y(_text) {}
}
// Parent constructors are always called in the order of inheritance
// regardless of the order of parent contracts listed in the
// constructor of the child contract.
// Order of constructors called:
// 1. X
// 2. Y
// 3. D
contract D is X, Y {
constructor() X("X was called") Y("Y was called") {}
}
// Order of constructors called:
// 1. X
// 2. Y
// 3. E
contract E is X, Y {
constructor() Y("Y was called") X("X was called") {}
}
6.26 26 继承
Solidity 支持多重继承
合约可以通过使用 is 关键字继承其他合约。
- 需要被子合约重写的函数必须声明为
virtual。 - 要重写父合约函数的函数必须使用
override关键字。 - 继承的顺序非常重要。
你必须按照“最基础”的合约到“最派生”的合约的顺序列出父合约。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
/* Graph of inheritance
A
/ \
B C
/ \ /
F D,E
*/
contract A {
function foo() public pure virtual returns (string memory) {
return "A";
}
}
// Contracts inherit other contracts by using the keyword 'is'.
contract B is A {
// Override A.foo()
function foo() public pure virtual override returns (string memory) {
return "B";
}
}
contract C is A {
// Override A.foo()
function foo() public pure virtual override returns (string memory) {
return "C";
}
}
// Contracts can inherit from multiple parent contracts.
// When a function is called that is defined multiple times in
// different contracts, parent contracts are searched from
// right to left, and in depth-first manner.
contract D is B, C {
// D.foo() returns "C"
// since C is the right most parent contract with function foo()
function foo() public pure override(B, C) returns (string memory) {
return super.foo();
}
}
contract E is C, B {
// E.foo() returns "B"
// since B is the right most parent contract with function foo()
function foo() public pure override(C, B) returns (string memory) {
return super.foo();
}
}
// Inheritance must be ordered from “most base-like” to “most derived”.
// Swapping the order of A and B will throw a compilation error.
contract F is A, B {
function foo() public pure override(A, B) returns (string memory) {
return super.foo();
}
}
6.27 27 隐藏继承状态变量
与函数不同,状态变量不能通过在子协定中重新声明来重写。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract A {
string public name = "Contract A";
function getName() public view returns (string memory) {
return name;
}
}
// Shadowing is disallowed in Solidity 0.6
// This will not compile
// contract B is A {
// string public name = "Contract B";
// }
contract C is A {
// This is the correct way to override inherited state variables.
constructor() {
name = "Contract C";
}
// C.getName returns "Contract C"
}
6.28 28.调用父合约
可以直接调用父契约,也可以使用关键字 super 调用父契约。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
/* Inheritance tree
A
/ \
B C
\ /
D
*/
contract A {
// This is called an event. You can emit events from your function
// and they are logged into the transaction log.
// In our case, this will be useful for tracing function calls.
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 {
// Try:
// - Call D.foo and check the transaction logs.
// Although D inherits A, B and C, it only called C and then A.
// - Call D.bar and check the transaction logs
// D called C, then B, and finally A.
// Although super was called twice (by B and C) it only called A once.
function foo() public override(B, C) {
super.foo();
}
function bar() public override(B, C) {
super.bar();
}
}
6.29 29. visibility 可见度
函数和状态变量必须声明它们是否可以被其他合约访问。
函数可以声明为:
- public - 任何合约和账户都可以调用
- private - 仅在定义该函数的合约内部可用
- internal - 仅在继承了该内部函数的合约中可用
- external - 仅其他合约和账户可以调用
状态变量可以声明为public、private或internal,但不能声明为external。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract Base {
// Private function can only be called
// - inside this contract
// Contracts that inherit this contract cannot call this function.
function privateFunc() private pure returns (string memory) {
return "private function called";
}
function testPrivateFunc() public pure returns (string memory) {
return privateFunc();
}
// Internal function can be called
// - inside this contract
// - inside contracts that inherit this contract
function internalFunc() internal pure returns (string memory) {
return "internal function called";
}
function testInternalFunc() public pure virtual returns (string memory) {
return internalFunc();
}
// Public functions can be called
// - inside this contract
// - inside contracts that inherit this contract
// - by other contracts and accounts
function publicFunc() public pure returns (string memory) {
return "public function called";
}
// External functions can only be called
// - by other contracts and accounts
function externalFunc() external pure returns (string memory) {
return "external function called";
}
// This function will not compile since we're trying to call
// an external function here.
// function testExternalFunc() public pure returns (string memory) {
// return externalFunc();
// }
// State variables
string private privateVar = "my private variable";
string internal internalVar = "my internal variable";
string public publicVar = "my public variable";
// State variables cannot be external so this code won't compile.
// string external externalVar = "my external variable";
}
contract Child is Base {
// Inherited contracts do not have access to private functions
// and state variables.
// function testPrivateFunc() public pure returns (string memory) {
// return privateFunc();
// }
// Internal function can be called inside child contracts.
function testInternalFunc() public pure override returns (string memory) {
return internalFunc();
}
}
6.30 30 interface 接口
你可以通过声明接口与其他合约进行交互。
接口的特点:
- 不能实现任何函数
- 可以继承其他接口
- 所有声明的函数必须是外部的
- 不能声明构造函数
- 不能声明状态变量
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract Counter {
uint256 public count;
function increment() external {
count += 1;
}
}
interface ICounter {
function count() external view returns (uint256);
function increment() external;
}
contract MyContract {
function incrementCounter(address _counter) external {
ICounter(_counter).increment();
}
function getCount(address _counter) external view returns (uint256) {
return ICounter(_counter).count();
}
}
// Uniswap example
interface UniswapV2Factory {
function getPair(address tokenA, address tokenB)
external
view
returns (address pair);
}
interface UniswapV2Pair {
function getReserves()
external
view
returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
}
contract UniswapExample {
address private factory = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f;
address private dai = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
address private weth = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
function getTokenReserves() external view returns (uint256, uint256) {
address pair = UniswapV2Factory(factory).getPair(dai, weth);
(uint256 reserve0, uint256 reserve1,) =
UniswapV2Pair(pair).getReserves();
return (reserve0, reserve1);
}
}
6.31 31 payable
声明payable的函数和地址可以在合同中接收以太。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract Payable {
// Payable address can send Ether via transfer or send
address payable public owner;
// Payable constructor can receive Ether
constructor() payable {
owner = payable(msg.sender);
}
// Function to deposit Ether into this contract.
// Call this function along with some Ether.
// The balance of this contract will be automatically updated.
function deposit() public payable {}
// Call this function along with some Ether.
// The function will throw an error since this function is not payable.
function notPayable() public {}
// Function to withdraw all Ether from this contract.
function withdraw() public {
// get the amount of Ether stored in this contract
uint256 amount = address(this).balance;
// send all Ether to owner
(bool success,) = owner.call{value: amount}("");
require(success, "Failed to send Ether");
}
// Function to transfer Ether from this contract to address from input
function transfer(address payable _to, uint256 _amount) public {
// Note that "to" is declared as payable
(bool success,) = _to.call{value: _amount}("");
require(success, "Failed to send Ether");
}
}
6.32 32 发送以太币
6.32.1 发送以太币(transfer、send、call)
6.32.1.1 如何发送以太币?
你可以通过以下方式向其他合约发送以太币:
- transfer(2300 gas,抛出错误)
- send(2300 gas,返回布尔值)
- call(转发所有 gas 或设置 gas,返回布尔值)
6.32.1.2 如何接收以太币?
接收以太币的合约必须至少有以下一个函数:
receive() external payablefallback() external payable
当 msg.data 为空时,会调用 receive(),否则会调用 fallback()。
6.32.1.3 应该使用哪种方法?
自2019年12月起,推荐使用结合重入保护的 call 方法。
防止重入攻击的方法包括:
- 在调用其他合约之前先进行所有状态更改
- 使用重入保护修饰符
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract ReceiveEther {
/*
Which function is called, fallback() or receive()?
send Ether
|
msg.data is empty?
/ \
yes no
/ \
receive() exists? fallback()
/ \
yes no
/ \
receive() fallback()
*/
// Function to receive Ether. msg.data must be empty
receive() external payable {}
// Fallback function is called when msg.data is not empty
fallback() external payable {}
function getBalance() public view returns (uint256) {
return address(this).balance;
}
}
contract SendEther {
function sendViaTransfer(address payable _to) public payable {
// This function is no longer recommended for sending Ether.
_to.transfer(msg.value);
}
function sendViaSend(address payable _to) public payable {
// Send returns a boolean value indicating success or failure.
// This function is not recommended for sending Ether.
bool sent = _to.send(msg.value);
require(sent, "Failed to send Ether");
}
function sendViaCall(address payable _to) public payable {
// Call returns a boolean value indicating success or failure.
// This is the current recommended method to use.
(bool sent, bytes memory data) = _to.call{value: msg.value}("");
require(sent, "Failed to send Ether");
}
}
6.33 33 Fallback
fallback 是一个特殊的函数,它在以下情况下被执行:
- 调用一个不存在的函数时
- 直接向合约发送以太币,但
receive()函数不存在或msg.data不为空
当通过 transfer 或 send 调用时,fallback 函数的 gas 限制为 2300 gas。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract Fallback {
event Log(string func, uint256 gas);
// Fallback function must be declared as external.
fallback() external payable {
// send / transfer (forwards 2300 gas to this fallback function)
// call (forwards all of the gas)
emit Log("fallback", gasleft());
}
// Receive is a variant of fallback that is triggered when msg.data is empty
receive() external payable {
emit Log("receive", gasleft());
}
// Helper function to check the balance of this contract
function getBalance() public view returns (uint256) {
return address(this).balance;
}
}
contract SendToFallback {
function transferToFallback(address payable _to) public payable {
_to.transfer(msg.value);
}
function callFallback(address payable _to) public payable {
(bool sent,) = _to.call{value: msg.value}("");
require(sent, "Failed to send Ether");
}
}
6.34 34 call
6.34.1 call 函数
call 是一种低级函数,用于与其他合约进行交互。
这是在仅通过调用 fallback 函数发送以太币时推荐使用的方法。
然而,它并不是调用现有函数的推荐方式。
低级 call 不推荐的原因:
- 重置(Reverts)不会向上冒泡:如果调用失败,错误不会自动传递回调用者。
- 类型检查被绕过:没有进行类型安全检查,可能导致不安全的操作。
- 函数存在性检查被省略:调用时不会检查目标函数是否存在。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract Receiver {
event Received(address caller, uint256 amount, string message);
fallback() external payable {
emit Received(msg.sender, msg.value, "Fallback was called");
}
function foo(string memory _message, uint256 _x)
public
payable
returns (uint256)
{
emit Received(msg.sender, msg.value, _message);
return _x + 1;
}
}
contract Caller {
event Response(bool success, bytes data);
// Let's imagine that contract Caller does not have the source code for the
// contract Receiver, but we do know the address of contract Receiver and the function to call.
function testCallFoo(address payable _addr) public payable {
// You can send ether and specify a custom gas amount
(bool success, bytes memory data) = _addr.call{
value: msg.value,
gas: 5000
}(abi.encodeWithSignature("foo(string,uint256)", "call foo", 123));
emit Response(success, data);
}
// Calling a function that does not exist triggers the fallback function.
function testCallDoesNotExist(address payable _addr) public payable {
(bool success, bytes memory data) = _addr.call{value: msg.value}(
abi.encodeWithSignature("doesNotExist()")
);
emit Response(success, data);
}
}
6.35 35 Delegatecall
6.35.1 delegatecall
delegatecall 是一种低级函数,类似于 call。
当合约 A 执行 delegatecall 调用合约 B 时,B 的代码将在以下条件下执行:
- 使用合约 A 的存储
- 使用合约 A 的
msg.sender - 使用合约 A 的
msg.value
这种特性使得 delegatecall 在执行合约 B 的代码时,可以访问和修改合约 A 的状态。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
// NOTE: Deploy this contract first
contract B {
// NOTE: storage layout must be the same as contract A
uint256 public num;
address public sender;
uint256 public value;
function setVars(uint256 _num) public payable {
num = _num;
sender = msg.sender;
value = msg.value;
}
}
contract A {
uint256 public num;
address public sender;
uint256 public value;
function setVars(address _contract, uint256 _num) public payable {
// A's storage is set, B is not modified.
(bool success, bytes memory data) = _contract.delegatecall(
abi.encodeWithSignature("setVars(uint256)", _num)
);
}
}
6.36 36 Function selector 函数选择器
当调用一个函数时,前 4 个字节的 calldata 指定要调用的函数。
这 4 个字节被称为函数选择器。
例如,以下代码使用 call 在地址 addr 的合约上执行 transfer:
addr.call(abi.encodeWithSignature("transfer(address,uint256)", 0xSomeAddress, 123))
从 abi.encodeWithSignature(...) 返回的前 4 个字节就是函数选择器。
如果在代码中预先计算并内联函数选择器,可能会节省少量的 gas。
以下是函数选择器的计算方式。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract FunctionSelector {
/*
"transfer(address,uint256)"
0xa9059cbb
"transferFrom(address,address,uint256)"
0x23b872dd
*/
function getSelector(string calldata _func)
external
pure
returns (bytes4)
{
return bytes4(keccak256(bytes(_func)));
}
}
6.37 37 合约间调用
合约可以通过两种方式调用其他合约。
最简单的方法是直接调用,例如 A.foo(x, y, z)。
另一种方式是使用低级 call 方法。这种方法并不推荐使用。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract Callee {
uint256 public x;
uint256 public value;
function setX(uint256 _x) public returns (uint256) {
x = _x;
return x;
}
function setXandSendEther(uint256 _x)
public
payable
returns (uint256, uint256)
{
x = _x;
value = msg.value;
return (x, value);
}
}
contract Caller {
function setX(Callee _callee, uint256 _x) public {
uint256 x = _callee.setX(_x);
}
function setXFromAddress(address _addr, uint256 _x) public {
Callee callee = Callee(_addr);
callee.setX(_x);
}
function setXandSendEther(Callee _callee, uint256 _x) public payable {
(uint256 x, uint256 value) =
_callee.setXandSendEther{value: msg.value}(_x);
}
}
6.38 38 通过合约创建其他合约
契约可以通过new关键字由其他契约创建。从0.8.0开始,new关键字通过指定salt选项来支持create2特性。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract Car {
address public owner;
string public model;
address public carAddr;
constructor(address _owner, string memory _model) payable {
owner = _owner;
model = _model;
carAddr = address(this);
}
}
contract CarFactory {
Car[] public cars;
function create(address _owner, string memory _model) public {
Car car = new Car(_owner, _model);
cars.push(car);
}
function createAndSendEther(address _owner, string memory _model)
public
payable
{
Car car = (new Car){value: msg.value}(_owner, _model);
cars.push(car);
}
function create2(address _owner, string memory _model, bytes32 _salt)
public
{
Car car = (new Car){salt: _salt}(_owner, _model);
cars.push(car);
}
function create2AndSendEther(
address _owner,
string memory _model,
bytes32 _salt
) public payable {
Car car = (new Car){value: msg.value, salt: _salt}(_owner, _model);
cars.push(car);
}
function getCar(uint256 _index)
public
view
returns (
address owner,
string memory model,
address carAddr,
uint256 balance
)
{
Car car = cars[_index];
return (car.owner(), car.model(), car.carAddr(), address(car).balance);
}
}
6.39 39 try/catch
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
// External contract used for try / catch examples
contract Foo {
address public owner;
constructor(address _owner) {
require(_owner != address(0), "invalid address");
assert(_owner != 0x0000000000000000000000000000000000000001);
owner = _owner;
}
function myFunc(uint256 x) public pure returns (string memory) {
require(x != 0, "require failed");
return "my func was called";
}
}
contract Bar {
event Log(string message);
event LogBytes(bytes data);
Foo public foo;
constructor() {
// This Foo contract is used for example of try catch with external call
foo = new Foo(msg.sender);
}
// Example of try / catch with external call
// tryCatchExternalCall(0) => Log("external call failed")
// tryCatchExternalCall(1) => Log("my func was called")
function tryCatchExternalCall(uint256 _i) public {
try foo.myFunc(_i) returns (string memory result) {
emit Log(result);
} catch {
emit Log("external call failed");
}
}
// Example of try / catch with contract creation
// tryCatchNewContract(0x0000000000000000000000000000000000000000) => Log("invalid address")
// tryCatchNewContract(0x0000000000000000000000000000000000000001) => LogBytes("")
// tryCatchNewContract(0x0000000000000000000000000000000000000002) => Log("Foo created")
function tryCatchNewContract(address _owner) public {
try new Foo(_owner) returns (Foo foo) {
// you can use variable foo here
emit Log("Foo created");
} catch Error(string memory reason) {
// catch failing revert() and require()
emit Log(reason);
} catch (bytes memory reason) {
// catch failing assert()
emit LogBytes(reason);
}
}
}
6.40 40 import 导入
6.40.1 本地导入
文件夹结构如下:
├── Import.sol
└── Foo.sol
Foo.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
struct Point {
uint256 x;
uint256 y;
}
error Unauthorized(address caller);
function add(uint256 x, uint256 y) pure returns (uint256) {
return x + y;
}
contract Foo {
string public name = "Foo";
}
Import.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
// 从当前目录导入 Foo.sol
import "./Foo.sol";
// 导入特定符号并设置别名
import {Unauthorized, add as func, Point} from "./Foo.sol";
contract Import {
// 初始化 Foo.sol
Foo public foo = new Foo();
// 测试 Foo.sol,获取它的名称
function getFooName() public view returns (string memory) {
return foo.name();
}
}
6.40.2 外部导入
您也可以通过复制 URL 从 GitHub 导入合约:
// https://github.com/owner/repo/blob/branch/path/to/Contract.sol
import "https://github.com/owner/repo/blob/branch/path/to/Contract.sol";
// 示例:从 OpenZeppelin 合约库导入 ECDSA.sol,release-v4.5 分支
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v4.5/contracts/utils/cryptography/ECDSA.sol";
6.41 41 library
6.41.1 Solidity 中的库
库与合约相似,但不能声明任何状态变量,也无法接收以太币。
库的嵌入:如果所有库函数都是内部的,则库会被嵌入到合约中。
部署和链接:如果库函数是公共或外部的,则必须先部署库,然后在合约部署之前进行链接。
6.41.2 关键点
- 库用于重用代码,提供了可重用的功能,而不需要维护状态。
- 在使用库时,注意函数的访问级别,以确定是否需要链接。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
library Math {
function sqrt(uint256 y) internal pure returns (uint256 z) {
if (y > 3) {
z = y;
uint256 x = y / 2 + 1;
while (x < z) {
z = x;
x = (y / x + x) / 2;
}
} else if (y != 0) {
z = 1;
}
// else z = 0 (default value)
}
}
contract TestMath {
function testSquareRoot(uint256 x) public pure returns (uint256) {
return Math.sqrt(x);
}
}
// Array function to delete element at index and re-organize the array
// so that there are no gaps between the elements.
library Array {
function remove(uint256[] storage arr, uint256 index) public {
// Move the last element into the place to delete
require(arr.length > 0, "Can't remove from empty array");
arr[index] = arr[arr.length - 1];
arr.pop();
}
}
contract TestArray {
using Array for uint256[];
uint256[] public arr;
function testArrayRemove() public {
for (uint256 i = 0; i < 3; i++) {
arr.push(i);
}
arr.remove(1);
assert(arr.length == 2);
assert(arr[0] == 0);
assert(arr[1] == 2);
}
}
6.42 42 ABI Encode
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
interface IERC20 {
function transfer(address, uint256) external;
}
contract Token {
function transfer(address, uint256) external {}
}
contract AbiEncode {
function test(address _contract, bytes calldata data) external {
(bool ok,) = _contract.call(data);
require(ok, "call failed");
}
function encodeWithSignature(address to, uint256 amount)
external
pure
returns (bytes memory)
{
// Typo is not checked - "transfer(address, uint)"
return abi.encodeWithSignature("transfer(address,uint256)", to, amount);
}
function encodeWithSelector(address to, uint256 amount)
external
pure
returns (bytes memory)
{
// Type is not checked - (IERC20.transfer.selector, true, amount)
return abi.encodeWithSelector(IERC20.transfer.selector, to, amount);
}
function encodeCall(address to, uint256 amount)
external
pure
returns (bytes memory)
{
// Typo and type errors will not compile
return abi.encodeCall(IERC20.transfer, (to, amount));
}
}
6.43 43 ABI Decode
abi.Encode将数据编码成字节。
abi.decode将字节解码回数据。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract AbiDecode {
struct MyStruct {
string name;
uint256[2] nums;
}
function encode(
uint256 x,
address addr,
uint256[] calldata arr,
MyStruct calldata myStruct
) external pure returns (bytes memory) {
return abi.encode(x, addr, arr, myStruct);
}
function decode(bytes calldata data)
external
pure
returns (
uint256 x,
address addr,
uint256[] memory arr,
MyStruct memory myStruct
)
{
// (uint x, address addr, uint[] memory arr, MyStruct myStruct) = ...
(x, addr, arr, myStruct) =
abi.decode(data, (uint256, address, uint256[], MyStruct));
}
}
6.44 44 Hashing with Keccak256
6.44.1 keccak256 函数
keccak256 计算输入的 Keccak-256 哈希值。
用例包括: - 生成唯一标识符:从输入创建一个确定性的唯一 ID。 - 提交-揭示方案:用于各种加密协议中,确保参与者的隐私。 - 紧凑的加密签名:通过对哈希进行签名,而不是对较大的输入进行签名,节省存储和传输成本。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract HashFunction {
function hash(string memory _text, uint256 _num, address _addr)
public
pure
returns (bytes32)
{
return keccak256(abi.encodePacked(_text, _num, _addr));
}
// Example of hash collision
// Hash collision can occur when you pass more than one dynamic data type
// to abi.encodePacked. In such case, you should use abi.encode instead.
function collision(string memory _text, string memory _anotherText)
public
pure
returns (bytes32)
{
// encodePacked(AAA, BBB) -> AAABBB
// encodePacked(AA, ABBB) -> AAABBB
return keccak256(abi.encodePacked(_text, _anotherText));
}
}
contract GuessTheMagicWord {
bytes32 public answer =
0x60298f78cc0b47170ba79c10aa3851d7648bd96f2f8e46a19dbc777c36fb0c00;
// Magic word is "Solidity"
function guess(string memory _word) public view returns (bool) {
return keccak256(abi.encodePacked(_word)) == answer;
}
}
6.45 45 验证签名
消息可以在链下签名,然后使用智能合约在链上进行验证。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
/* Signature Verification
How to Sign and Verify
# Signing
1. Create message to sign
2. Hash the message
3. Sign the hash (off chain, keep your private key secret)
# Verify
1. Recreate hash from the original message
2. Recover signer from signature and hash
3. Compare recovered signer to claimed signer
*/
contract VerifySignature {
/* 1. Unlock MetaMask account
ethereum.enable()
*/
/* 2. Get message hash to sign
getMessageHash(
0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C,
123,
"coffee and donuts",
1
)
hash = "0xcf36ac4f97dc10d91fc2cbb20d718e94a8cbfe0f82eaedc6a4aa38946fb797cd"
*/
function getMessageHash(
address _to,
uint256 _amount,
string memory _message,
uint256 _nonce
) public pure returns (bytes32) {
return keccak256(abi.encodePacked(_to, _amount, _message, _nonce));
}
/* 3. Sign message hash
# using browser
account = "copy paste account of signer here"
ethereum.request({ method: "personal_sign", params: [account, hash]}).then(console.log)
# using web3
web3.personal.sign(hash, web3.eth.defaultAccount, console.log)
Signature will be different for different accounts
0x993dab3dd91f5c6dc28e17439be475478f5635c92a56e17e82349d3fb2f166196f466c0b4e0c146f285204f0dcb13e5ae67bc33f4b888ec32dfe0a063e8f3f781b
*/
function getEthSignedMessageHash(bytes32 _messageHash)
public
pure
returns (bytes32)
{
/*
Signature is produced by signing a keccak256 hash with the following format:
"\x19Ethereum Signed Message\n" + len(msg) + msg
*/
return keccak256(
abi.encodePacked("\x19Ethereum Signed Message:\n32", _messageHash)
);
}
/* 4. Verify signature
signer = 0xB273216C05A8c0D4F0a4Dd0d7Bae1D2EfFE636dd
to = 0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C
amount = 123
message = "coffee and donuts"
nonce = 1
signature =
0x993dab3dd91f5c6dc28e17439be475478f5635c92a56e17e82349d3fb2f166196f466c0b4e0c146f285204f0dcb13e5ae67bc33f4b888ec32dfe0a063e8f3f781b
*/
function verify(
address _signer,
address _to,
uint256 _amount,
string memory _message,
uint256 _nonce,
bytes memory signature
) public pure returns (bool) {
bytes32 messageHash = getMessageHash(_to, _amount, _message, _nonce);
bytes32 ethSignedMessageHash = getEthSignedMessageHash(messageHash);
return recoverSigner(ethSignedMessageHash, signature) == _signer;
}
function recoverSigner(
bytes32 _ethSignedMessageHash,
bytes memory _signature
) public pure returns (address) {
(bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature);
return ecrecover(_ethSignedMessageHash, v, r, s);
}
function splitSignature(bytes memory sig)
public
pure
returns (bytes32 r, bytes32 s, uint8 v)
{
require(sig.length == 65, "invalid signature length");
assembly {
/*
First 32 bytes stores the length of the signature
add(sig, 32) = pointer of sig + 32
effectively, skips first 32 bytes of signature
mload(p) loads next 32 bytes starting at the memory address p into memory
*/
// first 32 bytes, after the length prefix
r := mload(add(sig, 32))
// second 32 bytes
s := mload(add(sig, 64))
// final byte (first byte of the next 32 bytes)
v := byte(0, mload(add(sig, 96)))
}
// implicitly return (r, s, v)
}
}
6.46 46 节省 gas 的技巧
- 使用 calldata 替代 memory:对于函数参数,使用
calldata可以节省 gas。 - 将状态变量加载到 memory:频繁访问的状态变量可以先加载到 memory 中,以减少对存储的直接访问。
- 将
for循环的i++替换为++i:这种做法在大多数情况下可以减少 gas 消耗。 - 缓存数组元素:在循环中缓存数组元素,避免重复读取。
- 短路操作:在逻辑运算中使用短路特性,避免不必要的计算。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
// gas golf
contract GasGolf {
// start - 50908 gas
// use calldata - 49163 gas
// load state variables to memory - 48952 gas
// short circuit - 48634 gas
// loop increments - 48244 gas
// cache array length - 48209 gas
// load array elements to memory - 48047 gas
// uncheck i overflow/underflow - 47309 gas
uint256 public total;
// start - not gas optimized
// function sumIfEvenAndLessThan99(uint[] memory nums) external {
// for (uint i = 0; i < nums.length; i += 1) {
// bool isEven = nums[i] % 2 == 0;
// bool isLessThan99 = nums[i] < 99;
// if (isEven && isLessThan99) {
// total += nums[i];
// }
// }
// }
// gas optimized
// [1, 2, 3, 4, 5, 100]
function sumIfEvenAndLessThan99(uint256[] calldata nums) external {
uint256 _total = total;
uint256 len = nums.length;
for (uint256 i = 0; i < len;) {
uint256 num = nums[i];
if (num % 2 == 0 && num < 99) {
_total += num;
}
unchecked {
++i;
}
}
total = _total;
}
}
6.47 47 按位运算符 Bitwise Operators
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract BitwiseOps {
// x = 1110 = 8 + 4 + 2 + 0 = 14
// y = 1011 = 8 + 0 + 2 + 1 = 11
// x & y = 1010 = 8 + 0 + 2 + 0 = 10
function and(uint256 x, uint256 y) external pure returns (uint256) {
return x & y;
}
// x = 1100 = 8 + 4 + 0 + 0 = 12
// y = 1001 = 8 + 0 + 0 + 1 = 9
// x | y = 1101 = 8 + 4 + 0 + 1 = 13
function or(uint256 x, uint256 y) external pure returns (uint256) {
return x | y;
}
// x = 1100 = 8 + 4 + 0 + 0 = 12
// y = 0101 = 0 + 4 + 0 + 1 = 5
// x ^ y = 1001 = 8 + 0 + 0 + 1 = 9
function xor(uint256 x, uint256 y) external pure returns (uint256) {
return x ^ y;
}
// x = 00001100 = 0 + 0 + 0 + 0 + 8 + 4 + 0 + 0 = 12
// ~x = 11110011 = 128 + 64 + 32 + 16 + 0 + 0 + 2 + 1 = 243
function not(uint8 x) external pure returns (uint8) {
return ~x;
}
// 1 << 0 = 0001 --> 0001 = 1
// 1 << 1 = 0001 --> 0010 = 2
// 1 << 2 = 0001 --> 0100 = 4
// 1 << 3 = 0001 --> 1000 = 8
// 3 << 2 = 0011 --> 1100 = 12
function shiftLeft(uint256 x, uint256 bits)
external
pure
returns (uint256)
{
return x << bits;
}
// 8 >> 0 = 1000 --> 1000 = 8
// 8 >> 1 = 1000 --> 0100 = 4
// 8 >> 2 = 1000 --> 0010 = 2
// 8 >> 3 = 1000 --> 0001 = 1
// 8 >> 4 = 1000 --> 0000 = 0
// 12 >> 1 = 1100 --> 0110 = 6
function shiftRight(uint256 x, uint256 bits)
external
pure
returns (uint256)
{
return x >> bits;
}
// Get last n bits from x
function getLastNBits(uint256 x, uint256 n)
external
pure
returns (uint256)
{
// Example, last 3 bits
// x = 1101 = 13
// mask = 0111 = 7
// x & mask = 0101 = 5
uint256 mask = (1 << n) - 1;
return x & mask;
}
// Get last n bits from x using mod operator
function getLastNBitsUsingMod(uint256 x, uint256 n)
external
pure
returns (uint256)
{
// 1 << n = 2 ** n
return x % (1 << n);
}
// Get position of most significant bit
// x = 1100 = 12, most significant bit = 1000, so this function will return 3
function mostSignificantBit(uint256 x) external pure returns (uint256) {
uint256 i = 0;
while ((x >>= 1) > 0) {
++i;
}
return i;
}
// Get first n bits from x
// len = length of bits in x = position of most significant bit of x, + 1
function getFirstNBits(uint256 x, uint256 n, uint256 len)
external
pure
returns (uint256)
{
// Example
// x = 1110 = 14, n = 2, len = 4
// mask = 1100 = 12
// x & mask = 1100 = 12
uint256 mask = ((1 << n) - 1) << (len - n);
return x & mask;
}
}
6.48 48 数字溢出
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract UncheckedMath {
function add(uint256 x, uint256 y) external pure returns (uint256) {
// 22291 gas
// return x + y;
// 22103 gas
unchecked {
return x + y;
}
}
function sub(uint256 x, uint256 y) external pure returns (uint256) {
// 22329 gas
// return x - y;
// 22147 gas
unchecked {
return x - y;
}
}
function sumOfCubes(uint256 x, uint256 y) external pure returns (uint256) {
// Wrap complex math logic inside unchecked
unchecked {
uint256 x3 = x * x * x;
uint256 y3 = y * y * y;
return x3 + y3;
}
}
}
6.49 49 Assembly
在 Solidity 中,Assembly 是一种低级编程语言,允许开发者直接与以太坊虚拟机(EVM)交互。使用 Assembly,开发者可以更精确地控制合约的执行过程,通常用于优化性能或实现 Solidity 语言本身无法直接表达的功能。
6.49.1 Assembly 的特点
低级控制:开发者可以直接访问 EVM 指令和底层功能,提供更高的灵活性和性能。
性能优化:通过手动控制内存、存储和其他操作,开发者可以减少 gas 消耗,提升合约的效率。
使用场景:常见于需要高效处理复杂计算、优化存储或实现特定算法的场景。
6.49.2 示例
在 Solidity 中使用 Assembly 的基本语法如下:
assembly {
// 这里是 Assembly 代码
let x := add(1, 2) // 计算 1 + 2
}
6.49.3 注意事项
- 安全性:使用 Assembly 可能会增加代码的复杂性和潜在漏洞,因此应谨慎使用。
- 可读性:Assembly 代码通常比高级语言难以阅读和维护,建议仅在必要时使用。
总之,Assembly 是 Solidity 中一种强大的工具,但需要开发者具备较高的技术水平来有效利用其优势。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract AssemblyVariable {
function yul_let() public pure returns (uint256 z) {
assembly {
// Language used for assembly is called Yul
// Local variables
let x := 123
z := 456
}
}
}
6.49.4 Assembly Conditional Statements
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract AssemblyIf {
function yul_if(uint256 x) public pure returns (uint256 z) {
assembly {
// if condition = 1 { code }
// no else
// if 0 { z := 99 }
// if 1 { z := 99 }
if lt(x, 10) { z := 99 }
}
}
function yul_switch(uint256 x) public pure returns (uint256 z) {
assembly {
switch x
case 1 { z := 10 }
case 2 { z := 20 }
default { z := 0 }
}
}
}
6.49.5 Assembly Loop
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract AssemblyLoop {
function yul_for_loop() public pure returns (uint256 z) {
assembly {
for { let i := 0 } lt(i, 10) { i := add(i, 1) } { z := add(z, 1) }
}
}
function yul_while_loop() public pure returns (uint256 z) {
assembly {
let i := 0
for {} lt(i, 5) {} {
i := add(i, 1)
z := add(z, 1)
}
}
}
}
6.49.6 Assembly Error
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract AssemblyError {
function yul_revert(uint256 x) public pure {
assembly {
// revert(p, s) - end execution
// revert state changes
// return data mem[p…(p+s))
if gt(x, 10) { revert(0, 0) }
}
}
}
6.49.7 Assembly Math
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract AssemblyMath {
function yul_add(uint256 x, uint256 y) public pure returns (uint256 z) {
assembly {
z := add(x, y)
if lt(z, x) { revert(0, 0) }
}
}
function yul_mul(uint256 x, uint256 y) public pure returns (uint256 z) {
assembly {
switch x
case 0 { z := 0 }
default {
z := mul(x, y)
if iszero(eq(div(z, x), y)) { revert(0, 0) }
}
}
}
// Round to nearest multiple of b
function yul_fixed_point_round(uint256 x, uint256 b)
public
pure
returns (uint256 z)
{
assembly {
// b = 100
// x = 90
// z = 90 / 100 * 100 = 0, want z = 100
// z := mul(div(x, b), b)
let half := div(b, 2)
z := add(x, half)
z := mul(div(z, b), b)
// x = 90
// half = 50
// z = 90 + 50 = 140
// z = 140 / 100 * 100 = 100
}
}
}