Contact
Similar to class, it can be: abstract, inherited and called by other contracts.
Typical use:
- Create a new contract:
new MyContract(...)
- Use the deployed contract:
MyContract($address)
visibility
visibility | similar | apply to | externally accessible | Subcontracts are accessible |
---|---|---|---|---|
external | function | √ | ||
public | public | function + state variable | √ | √ |
internal | protected | function + state variable | √ | |
private | private | function + state variable |
Note: For public variables, the corresponding getter will be automatically generated (see: Ethers.js non-authoritative development guide (continued) for details ).
Key elements
element | illustrate | example |
---|---|---|
State variables | Stored permanently on the chain, it takes gas | uint data; |
function | There are two types of read/write, and the write method needs to consume gas; it can exist inside and outside the contract | function func() public {...} |
fallback() | Cannot be called directly from the outside, it will be executed when there is no function in the request contract | fallback() external { ... } |
receive() | Cannot be directly called externally, it is executed when receiving eth | receive() external payable {...} |
modifier | Reusable declarative constraints, executed before function calls. | Declaration: modifier onlyOwner(){...} use:function func() public onlyOwner {...} |
event | The execution log on the chain can be queried in the future. | emit Event1(data); |
structure | custom type | struct MyType { uint item1; bool item2; } |
error | custom exception | Declaration: error MyError(unit reason); use:revert MyError(200); |
enumerate | Best choice for finite constant values | enum State { Created, Locked, Inactive } |
Note:
-
payable, the function receiving eth must add
-
view or pure, indicating that the function will not change the Ethereum state
-
fallback and receive functions
- Neither can have a function name, so there are no
function
keywords. - must use
external
- The fallback function can also be payable, but it is recommended to use the receive function first.
- The payable fallback and receive functions can consume up to 2300 gas, which needs to be tested here.
- Ordinary fallback does not have this limitation, as long as the gas is sufficient, any complex operation can be performed.
- Neither can have a function name, so there are no
For details on how to use event and query logs in dapps, see: Ethers.js Non-Authoritative Development Guide (Part 2)
Interface
Similar to interfaces in other languages, you can:
- Inherit other interfaces
- Only method declarations, nothing else
- All methods are external
Library
Similar to contract, but:
- cannot use state variables
- cannot inherit or be inherited
- cannot receive eth
- It cannot be executed independently and must be referenced by other contracts.
The relationship between the two is similar: contract, executable file; library, dynamic link library
type of data
Value Types and Reference Types
similar | |
---|---|
value type | bool、uint / int、address、byte、enum |
reference type | Array (such as bytes / string), structure, mapping |
Note:
-
byte arrays and strings
keccak256(abi.encodePacked(s1)) == keccak256(abi.encodePacked(s2))
bytes bs = 'bytes';
string str = 'string';
byte1 b1 = 'a';
byte2 b2 = 256;
, 256 exceeds the single-byte size- Fixed-length byte array: byte1 ~ byte32
- Dynamic byte data: bytes and string, where string is encoded in utf-8.
- Solidity does not provide a method for string comparison, but it can be done with the help of a hash function.
-
array
-
int[] age = [10, 20, 30, 40, 50];
-
int[] age = new int[](5);
-
int[5] age = [10, 20, 30, 40, 50];
-
Fixed-length arrays cannot be initialized with new
-
Dynamic arrays can use new or initial assignment at the same time
-
There are two types of address:
address
andaddress payable
, compared with the former, the latter has more transfer functions.
storage location
similar | example | |
---|---|---|
storage | Persistence, global memory in the contract | State variables |
memory | Function's local memory, non-persistent | function input |
calldata | Function input parameters, non-persistent | function input |
stack | EVM call stack |
Relevant rules:
-
Prioritize calldata because it can avoid copying and cannot be modified
-
Function local variables:
mapping (uint => address) storage localNames = names;
- When it is storage, it needs to point to the external state variable
- value type, memory
- Reference type, the default is storage, but it can be specified as memory.
- mapping, storage, always point to external state variables. The following example
names
is a state variable defined in a contract, and its type is also mapping.
-
Assignment rules
-
value type, making an independent copy
-
reference type, copy reference
-
Assignment between storage and memory / calldata always produces an independent copy.
-
Assignment between memory variables
-
storage Assigns a value to a local storage variable, copying the reference.
-
Other storage assignments always generate independent copies.
Global Variables and Methods
illustrate | example | |
---|---|---|
eth unit | wei, wei, ether | 1 gwei |
time unit | seconds、minutes、hours、days、weeks | 1 minutes |
block | block object | |
blockhash() | If the input parameter is one of the latest 256 blocks, it will be its hash. Otherwise, 0. | |
msg | msg object | |
tx | tx object | |
gasleft() | remaining gas | |
abi | abi object | |
address | address object | |
this | The current contract object, which can be explicitly converted to address | address(this).balance |
type() | type information | |
addmod | (a + b) % k | |
mulmod | (a * b) % k | |
hash function | keccak256、sha256、ripemd160 | |
ecrecover | Recover address from signature |
详见:Units and Globally Available Variables — Solidity 0.8.18 documentation
Note:
-
The difference between tx.orgin and msg.sender
- tx.orgin is the first account to initiate tx, and its value is always eoa.
- msg.sender is the direct calling account of the current function, which may be eoa or contract address.
-
Make sure that the first parameter of ecrecover is a valid eth message signature hash, which can be done with the help of openzepplin's ecdsa tool class.
-
It is used first
address.transfer
, and when it fails,transfer
it throws an exception andsender
returnsfalse
. -
The low-level methods (call, delegatecall, staticcall, send, transfer) on address have two sides:
-
They are cheap to execute due to the lack of runtime checks like type, existence, etc.
-
Therefore, it is not safe.
-
The use of call, delegatecall, and staticcall on address is similar, but the application scenarios are different:
-
call, applied to the contract
-
delegatecall, applied to library
-
staticcall, applied to contract read-only methods, namely view or pure methods, otherwise an exception will be thrown.
-
A typical address.call call:
bytes memory payload = abi.encodeWithSignature("register(string)", "MyName");
(bool success, bytes memory returnData) = address(nameReg).call(payload);
require(success);
- If you need to adjust gas and send eth, then:
address(nameReg).call{gas: 1000000, value: 1 ether}(abi.encodeWithSignature("register(string)", "MyName"));
exception handling
Exception type:
- Panic, internal error, such as division by zero.
- Error, general exception.
After the contract throws an exception, the state is rolled back. There are currently 3 ways:
-
require(expression), if the expression is false, an exception is thrown, and unused gas is returned
- It is suitable for the input parameter of the verification function and throws Error.
- The current version of require cannot be used with custom Error types. If necessary, use the combination of "conditional statement + revert".
-
assert(expression), same as above, but the unused gas will not be refunded and will be consumed in full
-
Suitable for verifying internal state and throwing Panic.
-
revert(), throw Error directly or customize Error, similar to throw in other languages.
Example of a try...catch statement:
try feed.getData(token) returns (uint v) {
return (v, true);
} catch Error(string memory /*reason*/) {
// require 导致
errorCount++;
return (0, false);
} catch Panic(uint /*errorCode*/) {
// assert 导致
errorCount++;
return (0, false);
} catch (bytes memory /*lowLevelData*/) {
// revert 导致
errorCount++;
return (0, false);
}