Solidity
Solc
一般的话我不直接使用,基本都是用truffle或者remix来管理,除非说自己编译之后看些东西
solc-select:solc多版本管理工具
https://docs.soliditylang.org/en/latest/using-the-compiler.html
修饰符
view
vs pure
- view修饰非改变链上状态的函数
- pure修饰非改变链上状态也不读取链上状态的函数
总结:pure是view的更严格子集,对于直接web3调用来说,都是不消耗gas费的。
https://docs.soliditylang.org/en/latest/contracts.html#view-functions
public
vs external
- public修饰的函数,可以外部调用,也可以合约本身直接调用(
func(...)
)。 - external修饰的函数,只能外部调用,合约本身不能直接调用(但是能通过
this.func(...)
调用)。
总结建议:在合约内部如果非特殊情况,不要使用
this.func(...)
来调用自身合约函数(会耗费更多的gas费,因为是通过CALL(gas: 700)来调用,而非JUMP调用(gas: 8)。)基本上能用public用public,external在一些特殊场合使用(比如让代码更严谨)
1
2
3
4
5
6
7
8
9
10
11
12
13 > import "@openzeppelin/contracts/access/Ownable.sol";
> contract A is Ownable {
> /// 在这里public修饰符就没有什么作用了,除非owner权限是合约本身
> function abc() external onlyOwner {
> _abc();
> }
> function _abc() internal {
> }
> function def() public {
> _abc();
> }
> }
>
estimateGas估算gas
以太坊开发和普通开发不同的地方就是以太坊的每一步操作都是耗费gas的,一个好的合约,应该考虑到gas的消耗。
gas消耗
以太坊黄皮中的Fee Schedule介绍了不同指令的gas消耗
总结:SLOAD、SSTORE是我们经常使用的耗费gas操作,即读取链上数据和写入数据到链上。
节省gas
- 当链上数据读出来之后需要多次用到并计算。先读出来存为单独变量(栈内存),之后再用该变量进行计算。最后再写入链上。
动态调用
delegatecall
代理调用可以简单理解为将一个合约变成library之后进行调用,调用环境不变(调用者环境)。
1 | contract B { |
- B合约可以单独进行调用,也可以被A调用。
- B合约被代理调用时,所有的存储槽环境变成A的环境,即save的值不再是50,而是取决于A中save的值。在B的set被调用时,save值也会写到A的合约中,而B的save值不动。
- B合约被单独调用时,save变量为自己的存储值。
- abc在B中被定义,但是在A中未定义,也可以被修改,但是A合约中无法进行调用(但是如果相同槽位上有变量,则会修改对应存储槽的变量)
总结:B在被直接调用时,B合约存储值为自己的内容,当B被代理调用时,存储槽环境全部更换为代理者合约环境,期间所有的修改都不会影响B合约本身。
状态变量存储
https://docs.soliditylang.org/en/latest/internals/layout_in_storage.html
32 byte 为一个 solt(uint256, bytes32都各占一个solt)
address为20 byte
address
: Holds a 20 byte value (size of an Ethereum address)constant修饰的变量不占用存储槽
constant
for state variables: Disallows assignment (except initialisation), does not occupy storage slot.
new
当我们在一个合约中创建新的合约时,创建的新合约的opcode会在调用者合约中
1 | pragma solidity ^0.8.6; |
1 | ======= test.sol:A ======= |
合约的部署其实就是将opcode存储到链上
漏洞
闪电贷
delegatecall
调用攻击合约修改调用合约的存储信息