// SPDX-License-Identifier: MIT pragma solidity ^0.8.10;
contract Primitives { bool public boo = true; /* uint stands for unsigned integer, meaning non negative integers different sizes are available uint8 ranges from 0 to 2 ** 8 - 1 uint16 ranges from 0 to 2 ** 16 - 1 ... uint256 ranges from 0 to 2 ** 256 - 1 */ // 无符号uint,无负数 // uintX X代表Bit 位,8bit= 1byte,uint256(32byte)默认就是一个Storage slot的长度 // uint 是uint256的别名 uint8 public u8 = 1; uint public u256 = 456; uint public u = 123; // uint is an alias for uint256 u
/* Negative numbers are allowed for int types. Like uint, different ranges are available from int8 to int256 int256 ranges from -2 ** 255 to 2 ** 255 - 1 int128 ranges from -2 ** 127 to 2 ** 127 - 1 */ // int可代表负数 // 可以定义的范围是:int8~int256,8为步长,也就是一个byte为步长 int8 public i8 = -1; int public i256 = 456; int public i = -123; // int is same as int256
// minimum and maximum of int int public minInt = type(int).min; int public maxInt = type(int).max;
address public addr = 0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c;
/* In Solidity, the data type byte represent a sequence of bytes. Solidity presents two type of bytes types : - fixed-sized byte arrays 固定长度字节数组 byteX - dynamically-sized byte arrays. 动态长度字节数组 bytes、byte[] The term bytes in Solidity represents a dynamic array of bytes. It’s a shorthand for byte[] . */ // bytes1 a = 0xb5; // [10110101] bytes1 b = 0x56; // [01010110] // 只声明,未赋值的变量都有默认值 // Default values // Unassigned variables have a default value bool public defaultBoo; // false uint public defaultUint; // 0 int public defaultInt; // 0 address public defaultAddr; // 0x0000000000000000000000000000000000000000 }
There are 3 types of variables in Solidity
从作用域、存储位置去区分
local 局部变量
declared inside a function
not stored on the blockchain
state 状态变量
declared outside a function
stored on the blockchain
global (provides information about the blockchain) 全局变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10;
contract Variables { // State variables are stored on the blockchain. string public text = "Hello"; uint public num = 123;
functiondoSomething() public { // Local variables are not saved to the blockchain. uint i = 456;
// Here are some global variables uint timestamp = block.timestamp; // Current block timestamp address sender = msg.sender; // address of the caller } }
State Variable声明的位置有点像py里的全局变量/class变量,但特点在于它是存储在blockchain上的,一旦存储在blockchain,那全世界都可以访问了,应该叫World State Variable😂
Constants
Constants are variables that cannot be modified.
Their value is hard coded and using constants can save gas cost.
不可修改、省gas
1 2 3 4 5 6 7 8 9
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10;
contract Constants { // 格式约定:对常量大写 // coding convention to uppercase constant variables address public constant MY_ADDRESS = 0x777788889999AaAAbBbbCcccddDdeeeEfFFfCcCc; uint public constant MY_UINT = 123; }
Immutable variables are like constants. Values of immutable variables can be set inside the constructor but cannot be modified afterwards.
跟constant的区别在于赋值的位置不一样。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10;
contract Immutable { // 格式约定:对常量大写 // coding convention to uppercase constant variables address public immutable MY_ADDRESS; uint public immutable MY_UINT;
To write or update a state variable you need to send a transaction.
On the other hand, you can read state variables, for free, without any transaction fee.
对状态变量的读写,读免费, 写/改变其状态的话,要发交易/要耗gas。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10;
contract SimpleStorage { // State variable to store a number uint public num;
// You need to send a transaction to write to a state variable. functionset(uint _num) public { num = _num; }
// You can read from a state variable without sending a transaction. functionget() public view returns (uint) { return num; } }
Transactions are paid with ether. 交易以ether单位计价
Similar to how one dollar is equal to 100 cent, one ether is equal to 1018 wei.
1美元=100美分,1Ether=1e18Wei,solidity自定义了自己的单位
1 2 3 4 5 6 7 8 9 10 11 12
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10;
contract EtherUnits { uint public oneWei = 1 wei; // 1 wei is equal to 1 bool public isOneWei = 1 wei == 1;
uint public oneEther = 1 ether; // 1 ether is equal to 10^18 wei bool public isOneEther = 1 ether == 1e18; }
Gas and Gas Price:
How much ether do you need to pay for a transaction?
You pay gas spent * gas price(数量*愿意出的单价) amount of ether, where
gas is a unit of computation 计算单位
gas spent is the total amount of gas used in a transaction
gas price is how much ether you are willing to pay per gas
| keyword | 含义 |
| — | — |— |
| gas | 计价单位 |
| gas spent | 交易里花费了多少份gas | |
| gas price | gas的单价 | |
Transactions with higher gas price have higher priority to be included in a block.
Unspent gas will be refunded.
有点像在拍卖东西,你愿意出的拍卖价(筹码数量 x 筹码价格)
A: 筹码数量10 x 筹码价格10=100
B: 筹码数量10 x 筹码价格20=200
C: 筹码数量20 x 筹码价格10=200
然后价高者得,优先处理你的交易。拍卖价的计算公式:gas spent * gas price ,为了加快交易,你可以提高筹码数量或每个筹码的价格,最终拍卖价变高就行。
There are 2 upper bounds to the amount of gas you can spend
gas limit (max amount of gas you’re willing to use for your transaction, set by you)
block gas limit (max amount of gas allowed in a block, set by the network)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10;
contract Gas { uint 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. functionforever() public { // Here we run a loop until all of the gas are spent // and the transaction fails while (true) { i += 1; } } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10;
contract IfElse { functionfoo(uint x) public pure returns (uint) { if (x < 10) { return0; } elseif (x < 20) { return1; } else { return2; } }
functionternary(uint _x) public pure returns (uint) { // if (_x < 10) { // return 1; // } // return 2;
// shorthand way to write if / else statement return _x < 10 ? 1 : 2; } }
For and While Loop:
Solidity supports for, while, and do while loops.
Don’t write loops that are unbounded as this can hit the gas limit, causing your transaction to fail.
For the reason above, while and do while loops are rarely used.
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10;
contract Loop { functionloop() public { // for loop for (uint 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 uint j; while (j < 10) { j++; } } }
Mapping
Maps are created with the syntax mapping(keyType => valueType). The keyType can be any built-in value type, bytes, string, or any contract.
TODO:哪些是类型可以做Key?常用用什么做key?
valueType can be any type including another mapping or an array.
Mappings are not iterable.
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10;
contract Mapping { // Mapping from address to uint mapping(address => uint) public myMap;
functionget(address _addr) public view returns (uint) { // Mapping always returns a value. // If the value was never set, it will return the default value. return myMap[_addr]; }
functionset(address _addr, uint _i) public { // Update the value at this address myMap[_addr] = _i; }
functionremove(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(uint => bool)) public nested;
functionget(address _addr1, uint _i) public view returns (bool) { // You can get values from a nested mapping // even when it is not initialized return nested[_addr1][_i]; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10;
contract Array { // Several ways to initialize an array // 多种方式初始化 uint[] public arr; uint[] public arr2 = [1, 2, 3]; // Fixed sized array, all elements initialize to 0 uint[10] public myFixedSizeArr;
// Solidity can return the entire array. 能返回整个数组,但避免能无限长的数组。 // But this function should be avoided for // arrays that can grow indefinitely in length. functiongetArr() public view returns (uint[] memory) { return arr; }
functionpush(uint i) public { // Append to array // This will increase the array length by 1. // 长度+1 arr.push(i); // 在尾端加入,追加 }
functionpop() public { // Remove last element from array // This will decrease the array length by 1 // 长度-1 arr.pop(); // 在尾端去除 }
functiongetLength() public view returns (uint) { return arr.length; }
functionremove(uint 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]; // }
functionexamples() external { // create array in memory, only fixed size can be created uint[] memory a = new uint[](5); } }
Examples of removing array element
Remove array element by shifting elements from right to left
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10; // 用最后一个元素 覆盖 要函数的元素 contract ArrayReplaceFromEnd { uint[] public arr;
// Deleting an element creates a gap in the array. // One trick to keep the array compact is to // move the last element into the place to delete. functionremove(uint index) public { // Move the last element into the place to delete arr[index] = arr[arr.length - 1]; // Remove the last element arr.pop(); }
Solidity supports enumerables and they are useful to model choice and keep track of state.
单选项,适合来表示状态,主要用于为uint分配名称,使程序易于阅读和维护
Enums can be declared outside of a contract.枚举类型可以在合约外定义
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10; // This is saved 'EnumDeclaration.sol'
enum Status { Pending, Shipped, Accepted, Rejected, Canceled } File that imports the enum above
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10;
import"./EnumDeclaration.sol";
contract Enum { Status public status; }
Structs:
You can define your own type by creating a struct.自定义类型,形式上与enum相似,但可以比enum定义更多的类型
They are useful for grouping together related data. 组织数据
Structs can be declared outside of a contract and imported in another contract.
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10;
contract Todos { struct Todo { string text; bool completed; }
// An array of 'Todo' structs Todo[] public todos;
functioncreate(string memory _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. functionget(uint _index) public view returns (string memory text, bool completed) { Todo storage todo = todos[_index]; return (todo.text, todo.completed); }
// update text functionupdate(uint _index, string memory _text) public { Todo storage todo = todos[_index]; todo.text = _text; }
// update completed functiontoggleCompleted(uint _index) public { Todo storage todo = todos[_index]; todo.completed = !todo.completed; } }
用index取值
导入struct:
Declaring and importing Struct File that the struct is declared in
1 2 3 4 5 6 7
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10;
struct Todo { string text; bool completed; }
File that imports the struct above
1 2 3 4 5 6 7 8 9
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10;
import"./StructDeclaration.sol";
contract Todos { // An array of 'Todo' structs Todo[] public todos; }
Data Locations - Storage, Memory and Calldata:
Variables are declared as either storage, memory or calldata to explicitly specify the location of the data.
定义数据存储的位置
storage - variable is a state variable (store on blockchain) World State变量😆,存在链上
memory - variable is in memory and it exists while a function is being called 函数里的临时变量
calldata - special data location that contains function arguments 用于函数参数
functionf() public { // call _f with state variables _f(arr, map, myStructs[1]);
// get a struct from a mapping // {Type} {Location} {VariableName} 😤😤😤 MyStruct storage myStruct = myStructs[1]; // create a struct in memory MyStruct memory myMemStruct = MyStruct(0); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10;
contract Function { // Functions can return multiple values. // 一次能返回多个数据 functionreturnMany() public pure returns ( uint, bool, uint ) { return (1, true, 2); }
// Return values can be named. // 返回值可以命名 functionnamed() public pure returns ( uint x, bool b, uint y ) { return (1, true, 2); }
// Return values can be assigned to their name. // In this case the return statement can be omitted. // 提前声明返回值,可省略返回语句,隐式返回 functionassigned() public pure returns ( uint x, bool b, uint y ) { x = 1; b = true; y = 2; }
// Use destructuring assignment when calling another // function that returns multiple values. // 非结构化赋值 functiondestructuringAssignments() public pure returns ( uint, bool, uint, uint, uint ) { (uint i, bool b, uint j) = returnMany();
// Values can be left out. // 非结构化赋值 // 空开 (uint x, , uint y) = (4, 5, 6);
return (i, b, j, x, y); }
// Cannot use map for either input or output // map不能作为输入或输出
// Can use array for input // 数组可作为输入 functionarrayInput(uint[] memory _arr) public {}
// Can use array for output // 数组可作为输出 uint[] public arr;
Getter functions can be declared view or pure. View function declares that no state will be changed. 没有world State变量会改变 Pure function declares that no state variable will be changed or read. 没有world State变量会改变或读取
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10;
contract Error { functiontestRequire(uint _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"); }
functiontestRevert(uint _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"); } }
uint public num;
functiontestAssert() 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); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10;
contract FunctionModifier { // We will use these variables to demonstrate how to use // modifiers. address public owner; uint public x = 10; bool public locked;
constructor() { // Set the transaction sender as the owner of the contract. owner = msg.sender; // 创建合约//发起一个创建合约的交易/创建人是 owner }
// 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. // 检测入参地址 非0 modifier validAddress(address _addr) { require(_addr != address(0), "Not valid address"); _; }
functionchangeOwner(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; }
functiondecrement(uint i) public noReentrancy { x -= i;
if (i > 1) { decrement(i - 1); } } }
Events:
Events allow logging to the Ethereum blockchain. Some use cases for events are:
Listening for events and updating user interface 可用作通知,前端可以监听并更新用户界面
A cheap form of storage 非必要存储的,不记录在storage里,记录在event里
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10;
contract Event { // Event declaration // Up to 3 parameters can be indexed. 最多3个indexed参数 // Indexed parameters helps you filter the logs by the indexed parameter 相当于标识符,索引,可以在日志里过滤出来 event Log(address indexed sender, string message); event AnotherLog();
A constructor is an optional function that is executed upon contract creation. 合约创建时,构造函数是可选的
Here are examples of how to pass arguments to constructors.
// 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") {} } // 始终按照继承顺序调用父构造函数
Inheritance:
Solidity supports multiple inheritance. 支持多重继承
Contracts can inherit other contract by using the is keyword.
Function that is going to be overridden by a child contract must be declared as virtual. 父类声明虚函数,可被重写
Function that is going to override a parent function must use the keyword override. 子类声明重载函数
Order of inheritance is important. 继承的顺序很重要
You have to list the parent contracts in the order from “most base-like” to “most derived”.
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10;
/* Graph of inheritance A / \ B C / \ / F D,E */
contract A { functionfoo() public pure virtual returns (string memory) { // virtual return"A"; } }
// Contracts inherit other contracts by using the keyword 'is'. contract B is A { // Override A.foo() functionfoo() public pure virtual override returns (string memory) { // virtual override return"B"; } }
contract C is A { // Override A.foo() functionfoo() public pure virtual override returns (string memory) { // virtual override 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() functionfoo() public pure override(B, C) returns (string memory) { returnsuper.foo(); } }
contract E is C, B { // E.foo() returns "B" // since B is the right most parent contract with function foo() functionfoo() public pure override(C, B) returns (string memory) { returnsuper.foo(); } }
/* Graph of inheritance A / \ B C / \ / F D,E */ // Inheritance must be ordered from “most base-like” to “most derived”. // Swapping the order of A and B will throw a compilation error. // 编译报错 TypeError: Linearization of inheritance graph impossible // 线性继承,声明顺序,类似: 钢笔is 物品,文具 大到小 总分结构 // 同名函数执行顺序,具体到抽象 文具foo()->物品foo() 小到大 分总结构 contract F is A, B { functionfoo() public pure override(A, B) returns (string memory) { returnsuper.foo(); } }
Shadowing Inherited State Variables:
Unlike functions, state variables cannot be overridden by re-declaring it in the child contract.
与函数不同,状态变量不能通过在子合约中重新声明来重写。
Let’s learn how to correctly override inherited state variables.
// 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"; // TODO: 只能在构造函数里声明吗?😨😨😨 }
// C.getName returns "Contract C" }
Calling Parent Contracts:
Parent contracts can be called directly, or by using the keyword super.
使用super关键词直接调用父类方法
By using the keyword super, all of the immediate parent contracts will be called.
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10;
/* 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);
functionfoo() public virtual { emit Log("A.foo called"); }
functionbar() public virtual { emit Log("A.bar called"); } }
contract B is A { functionfoo() public virtual override { emit Log("B.foo called"); A.foo(); }
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.
functionfoo() public override(B, C) { super.foo(); }
functionbar() public override(B, C) { super.bar(); } }
调用foo:
调用bar:
Visibility
Functions and state variables have to declare whether they are accessible by other contracts.
函数和状态变量必须声明它们是否可以被其他合约访问。
Functions can be declared as
public - any contract and account can call 当前合约、子合约、外部合约和账户
private - only inside the contract that defines the function 当前合约
internal - only inside contract that inherits an internal function 当前合约和子合约
external - only other contracts and accounts can call 除了当前合约不能访问,子合约、外部合约和账户可以访问
可见性,看不见、访问不到就没法子调用了。
public vs external ???
State variables can be declared as public, private, or internal but not external.
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10;
contract Base { // Private function can only be called // - inside this contract // Contracts that inherit this contract cannot call this function. functionprivateFunc() private pure returns (string memory) { return"private function called"; }
functiontestPrivateFunc() public pure returns (string memory) { returnprivateFunc(); }
// Internal function can be called // - inside this contract // - inside contracts that inherit this contract functioninternalFunc() internal pure returns (string memory) { return"internal function called"; }
functiontestInternalFunc() public pure virtual returns (string memory) { returninternalFunc(); }
// Public functions can be called // - inside this contract // - inside contracts that inherit this contract // - by other contracts and accounts functionpublicFunc() public pure returns (string memory) { return"public function called"; }
// External functions can only be called // - by other contracts and accounts functionexternalFunc() 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 call be called inside child contracts. functiontestInternalFunc() public pure override returns (string memory) { returninternalFunc(); } }
Interface:
You can interact with other contracts by declaring an Interface.
// Function to deposit Ether into this contract. // Call this function along with some Ether. // The balance of this contract will be automatically updated. functiondeposit() public payable {}
// Call this function along with some Ether. // The function will throw an error since this function is not payable. functionnotPayable() public {}
// Function to withdraw all Ether from this contract. functionwithdraw() public { // get the amount of Ether stored in this contract uint amount = address(this).balance;
// send all Ether to owner // Owner can receive Ether since the address of owner is payable (bool success, ) = owner.call{value: amount}(""); require(success, "Failed to send Ether"); }
// Function to transfer Ether from this contract to address from input functiontransfer(address payable _to, uint _amount) public { // Note that "to" is declared as payable (bool success, ) = _to.call{value: _amount}(""); require(success, "Failed to send Ether"); } }
Sending Ether - Transfer, Send, and Call:
How to send Ether?
You can send Ether to other contracts by
transfer (2300 gas, throws error)
send (2300 gas, returns bool)
call (forward all gas or set gas, returns bool)
How to receive Ether?
A contract receiving Ether must have at least one of the functions below
receive() external payable
fallback() external payable
receive() is called if msg.data is empty, otherwise fallback() is called.
Which method should you use?
call in combination with re-entrancy guard is the recommended method to use after December 2019.注意防重入
Guard against re-entrancy by
making all state changes before calling other contracts 在调用其他合约之前,执行完所有的状态变量更改
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10;
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 {}
functiongetBalance() public view returns (uint) { returnaddress(this).balance; } }
contract SendEther { functionsendViaTransfer(address payable _to) public payable { // This function is no longer recommended for sending Ether. // 不推荐 _to.transfer(msg.value); }
functionsendViaSend(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"); }
functionsendViaCall(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"); } }
Fallback:
fallback is a function that does not take any arguments and does not return anything.
一个不接受任何参数且不返回任何内容的函数
It is executed either when 在何时执行
a function that does not exist is called or 调用不存在的函数
Ether is sent directly to a contract but receive() does not exist or msg.data is not empty 以太币直接发送到合约但receive()不存在或msg.data不为空
fallback has a 2300 gas limit when called by transfer or send.
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10;
contract Fallback { event Log(uint gas);
// Fallback function must be declared as external. 回退函数的可见性必须声明为external
fallback() external payable { // send / transfer (forwards 2300 gas to this fallback function) // call (forwards all of the gas) emit Log(gasleft()); }
// Helper function to check the balance of this contract functiongetBalance() public view returns (uint) { returnaddress(this).balance; } }
functioncallFallback(address payable _to) public payable { (bool sent, ) = _to.call{value: msg.value}(""); require(sent, "Failed to send Ether"); } }
Call:
call is a low level function to interact with other contracts.
This is the recommended method to use when you’re just sending Ether via calling the fallback function.
However it is not the recommend way to call existing functions. 不是调用现有函数的推荐方法
// Let's imagine that contract B does not have the source code for // B合约未开源 // contract A, but we do know the address of A and the function to call. functiontestCallFoo(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. // 调用不存在的函数会触发回退函数。 functiontestCallDoesNotExist(address _addr) public { (bool success, bytes memory data) = _addr.call( abi.encodeWithSignature("doesNotExist()") );
emit Response(success, data); } }
Delegatecall:
delegatecall is a low level function similar to call.
When contract A executes delegatecall to contract B, B’s code is excutedwith contractA's storage, msg.senderandmsg.value`.
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10;
// NOTE: Deploy this contract first contract B { // NOTE: storage layout must be the same as contract A uint public num; address public sender; uint public value;
functionsetVars(uint _num) public payable { num = _num; sender = msg.sender; value = msg.value; } }
contract A { uint public num; address public sender; uint public value;
functionsetVars(address _contract, uint _num) public payable { // A's storage is set, B is not modified. (bool success, bytes memory data) = _contract.delegatecall( abi.encodeWithSignature("setVars(uint256)", _num) ); } }
Function Selector:
When a function is called, the first 4 bytes of calldata specifies which function to call.
This 4 bytes is called a function selector.
调用函数时,前 4 个字节calldata指定调用哪个函数。这 4 个字节称为函数选择器。像路由。🤨🤨🤨
Take for example, this code below. It uses call to execute transfer on a contract at the address addr.
The first 4 bytes returned from abi.encodeWithSignature(....) is the function selector.
Perhaps you can save a tiny amount of gas if you precompute and inline the function selector in your code?
如果您在代码中预先计算并内联函数选择器,也许您可以节省少量gas?
Here is how the function selector is computed.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10;
Contract can call other contracts in 2 ways. 可以通过 2 种方式调用其他合约。(不是4种吗?)
The easiest way to is to just call it, like A.foo(x, y, z).
Another way to call other contracts is to use the low-level call. 不推荐
This method is not recommended.
Contract that Creates other Contracts
Contracts can be created by other contracts using the new keyword. Since 0.8.0, new keyword supports create2 feature by specifying salt options.
// Example import ECDSA.sol from openzeppelin-contract repo, release-v4.5 branch // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v4.5/contracts/utils/cryptography/ECDSA.sol import"https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v4.5/contracts/utils/cryptography/ECDSA.sol";
Library:
Libraries are similar to contracts, but you can’t declare any state variable and you can’t send ether.
A library is embedded into the contract if all library functions are internal.
Otherwise the library must be deployed and then linked before the contract is deployed.
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10;
library SafeMath { functionadd(uint x, uint y) internal pure returns (uint) { uint z = x + y; require(z >= x, "uint overflow");
return z; } }
library Math { functionsqrt(uint y) internal pure returns (uint z) { if (y > 3) { z = y; uint x = y / 2 + 1; while (x < z) { z = x; x = (y / x + x) / 2; } } elseif (y != 0) { z = 1; } // else z = 0 (default value) } }
contract TestSafeMath { using SafeMathfor uint;
uint public MAX_UINT = 2**256 - 1;
functiontestAdd(uint x, uint y) public pure returns (uint) { return x.add(y); }
functiontestSquareRoot(uint x) public pure returns (uint) { returnMath.sqrt(x); } }
// Array function to delete element at index and re-organize the array // so that their are no gaps between the elements. library Array { functionremove(uint[] storage arr, uint 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 Arrayfor uint[];
uint[] public arr;
functiontestArrayRemove() public { for (uint i = 0; i < 3; i++) { arr.push(i); }
// 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. // abi.encodePacked 紧打包可能会有哈希碰撞问题 // 在这种情况下,建议用abi.encode functioncollision(string memory _text, string memory _anotherText) public pure returns (bytes32) { // encodePacked(AAA, BBB) -> AAABBB // encodePacked(AA, ABBB) -> AAABBB // Oh,哈希碰撞 returnkeccak256(abi.encodePacked(_text, _anotherText)); } }
contract GuessTheMagicWord { bytes32 public answer = 0x60298f78cc0b47170ba79c10aa3851d7648bd96f2f8e46a19dbc777c36fb0c00;
// Magic word is "Solidity" functionguess(string memory _word) public view returns (bool) { returnkeccak256(abi.encodePacked(_word)) == answer; } }
Verifying Signature:
Messages can be signed off chain and then verified on chain using a smart contract.
消息可以在链下签名,然后使用智能合约在链上进行验证。
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10;
/* 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" */ functiongetMessageHash( address _to, uint _amount, string memory _message, uint _nonce ) public pure returns (bytes32) { returnkeccak256(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 */ functiongetEthSignedMessageHash(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) ); }
functionsplitSignature(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))) }