Solidity 智能合约如何节省GAS、提升安全
目录
Solidity是一种用于编写智能合约的编程语言,它在Ethereum虚拟机(EVM)上运行。Solidity具有类似于JavaScript、C++和Python的语法,并且它引入了一些特殊的关键字来支持区块链上的交易和智能合约功能。
文章先介绍Solidity常用修饰符的类别以及作用,并举实际例子说明在什么场景下,如何声明变量或者函数能够节省GAS、提升合约安全等方面进行详细介绍。
常用修饰符介绍
1,变量类型修饰符
-
bool:布尔类型,表示真或假。
-
int / uint:有符号和无符号整型,后面可以跟位数,如uint256。
-
address:用于储存以太坊地址。
-
bytes1: … bytes32:固定大小的字节序列。
-
bytes:动态大小的字节序列。
-
string:动态大小的字符串。
-
enum:枚举类型,定义一组命名的常数。
-
struct:结构体类型,定义一个包含多个不同类型的成员的复合类型。
-
public:可以用于状态变量和函数,用来修饰变量,对于状态变量,它会自动生成一个getter函数;例如:
//在Solidity中,如果你声明了一个公共(public)状态变量,编译器会为这个变量自动生成一个getter函数。对于public状态变量,Solidity不会自动生成setter函数,只有getter函数,因为变量的赋值通常是需要被严格控制的,以防止意外更改合约的重要状态。 contract ExampleContract { //声明变量 address public immutable override token0; //自动生成的getter函数将大致如下(虽然你实际上无法看到这个函数的源代码,因为它是由编译器生成的) function token0() public view returns (address token0) { return token0; } }
-
internal:用于状态变量和函数时,internal关键字意味着它们只能在当前合约及继承自该合约的子合约内部访问。但是,
internal
修饰符不能直接用于局部变量,仅用于状态变量和函数。以下是用
internal
关键字修饰状态变量的一个例子:// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract MyContract { // internal状态变量,可以被当前合约以及继承它的合约访问 internal uint256 internalCounter; constructor() { internalCounter = 0; } // internal函数,可以被当前合约以及继承它的合约访问 function incrementCounter() internal { internalCounter += 1; } // 这个函数可以被外部调用,它会调用internal函数来增加计数器 function increment() public { incrementCounter(); } // 获取当前计数器值的函数,任何合约外部的人都可以调用 function getCounter() public view returns (uint256) { return internalCounter; } } // 继承自MyContract的子合约 contract DerivedContract is MyContract { // 这个函数可以调用父合约的internal函数incrementCounter function derivedIncrement() public { incrementCounter(); } }
请注意,尽管
internal
修饰符可以应用于状态变量和函数,但它不适用于函数参数、返回变量或局部变量;这些变量的可见性由它们所在的上下文决定。如果一个函数或变量没有指定可见性,internal
是状态变量的默认可见性,而函数的默认可见性是public
(但这一点在Solidity 0.5.0以及之后的版本中发生了变化,现在函数必须显式声明可见性)。
2,数据存储关键字
- mapping:映射类型,类似于高级语言中的字典或哈希表。
- array:数组类型,可以是固定大小或动态大小。
3,函数修饰关键字
- public:函数是公开的,可以在合约内外部调用。
- private:函数是私有的,只能在合约内部调用。
- internal:函数是内部的,只能在内部或通过继承的合约调用。
- external:函数是外部的,只能从合约外部调用。它不能被合约内部函数调用。
- view:声明函数不会修改合约状态。只会读取他们。view函数允许读取状态变量,但是不能修改他们。
- pure:这个修饰符指定函数不会读取或修改状态变量。也就是说,它不回读取合约的状态,也不会写入合约状态。
- payable:这个修饰符表示函数可以接收Ether。此修饰符对应的函数在被调用的时可以进行Ether交易。
- virtual: 表示该方法可以在继承它的子合约中被重新定义。子合约可以使用override关键字来覆盖父合约中的virtual函数。(Solidity 0.6.0之后的版本,必须适用override关键字,避免发生意外错误,提高代码可读性。)
总结:
以上修饰符中,pure,view,payable 用于函数的可执行性;internal,external,public,private 表示函数的可见性。
以上修饰符可以组合适用,但有些组合没有实际意义,比如pure和payable 就不能同时使用,因为一个pure函数不能改变状态,而接收Ether 必然改变合约的状态。
在Solidity中,虚函数(使用virtual关键字标记)的可见性不必一定是public,它可以是public、private、internal或external。然而,如果你想要在继承的合约中重写这个函数,则函数必须声明为public或者external。private函数不能在继承中被重写。external函数可以被重写,但是重写后的函数应为external。
最后,所有的子合约无论如何修改重写函数的可见性,最终还是要满足里氏替换原则,即改变不了原有函数的可见性。
4,状态修饰关键字
- constant:在Solidity较早的版本中,
constant
用于状态变量,指示变量的值在编译时已知且在合约的整个生命周期内不会改变。但是在Solidity 0.5.0及以后的版本中,这一关键字已经被immutable
和view
取代。 - immutable:
immutable
修饰符被用于状态变量,表示这个变量在构造函数中被设置一次,并且在整个合约生命周期中保持不变。它不适用于函数。 - storage:表明变量永久存储在区块链上。
- memory:表明变量是临时的,仅在外部函数调用期间存在。
5,控制结构关键字
- if / else:条件语句。
- for:循环语句。
- while / do:循环语句。
6,错误处理关键字
- require:用来确保函数调用前某些条件满足,不满足时撤销状态改变。
- revert:当执行失败时撤销所有状态改变并返回错误。
- assert:与require类似,但用于检查代码不应该到达的状态,错误时消耗所有gas。
7,自定义修饰符
开发者可以自定义修饰符,以用于特殊场景下的验证和控制逻辑。自定义修饰符需要用到modifier关键字声明,例如:
//用于对函数进行业务逻辑校验
modifier ensure(uint deadline) {
require(deadline >= block.timestamp, 'UniswapV2Router: EXPIRED');
_;
}
使用:
//实际函数声明中,对deadline进行校验
function addLiquidity(
address tokenA,
address tokenB,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin,
address to,
uint deadlin