前言
版权声明:
表示合约采用MIT许可证:(补充:MIT 许可证是一种宽松的开源许可证,允许用户自由使用、修改和再发布代码,只需要包含原始许可声明即可。)
//SPDX-License-Indentifier:MIT
版本声明:(用来指示编译器按照solidity的哪一个版本来编译智能合约)
pragma solidity ^
0.8.0;
//指定固定
版本: 可以使用固定的
版本号来声明 Solidity 的
版本
//指定兼容
版本范围: 有时候我们希望指定一个兼容
版本的范围,可以使用 ^ 符号,表示与指定
版本兼容的范围以下例子(
>=0.8.0,
<0.9.0)
//指定
版本范围: 除了使用 ^ 符号外,还可以使用范围符号(
>=,
<=,
>,
<)来指定一个
版本范围,
pragma solidity >=0.6.0 <0.9.0;
版权声明和版本声明在智能合约里面都是必不可少的,一般版权声明如上不改变,版本声明可根据自己需要进行修改
智能合约是在区块链上执行的自动化合约(大致模块)
它们由 Solidity 或其他智能合约编程语言编写,用于定义和执行特定的交易规则。智能合约通常是以太坊或其他区块链平台上的一个重要组成部分,可以实现各种去中心化应用程序(DApp)。
1.声明合约: 首先,智能合约会声明合约名字及
版本。例如
2.状态变量: 在合约内部,会声明状态变量来
存储合约的状态信息。状态变量的值会被永久性地
存储在区块链上,并且可以被读取和
修改。
3.
构造函数: 合约可以包含一个构造函数,在合约
部署时被调用,用于初始化合约的状态。构造函数只会被调用一次。
4.
函数: 智能合约会包含各种函数,用于定义合约的行为。这些函数可以被外部调用,执行特定的
操作并返回结果。
function myFunction() public returns (uint) {
5.事件: 智能合约可以定义
事件,用于记录合约中的重要状态变化,以便外部
应用程序监听并做出相应的响应。
event MyEvent(address indexed _from, uint _value);
function myFunction() public {
emit MyEvent(msg.sender, 100);
数据类型:值类型和引用类型
值类型
值类型是直接存储数据的实际值,而不是指向存储位置的引用。当使用值类型时,会将数据的副本传递给其他变量或函数
1.布尔类型(bool):表示真(true)或假(false)。
bool myBool =
true;
2.整数类型(int、uint):表示有符号和无符号整数,可以指定位数(如 int8、uint256)(但是指定的位数都必须是8的倍数)。
3.定长字节数组(bitesN):定长字节数组是一个具有固定长度的字节数组。你可以用 bytesN 来声明定长字节数组,其中 N 是字节数。
bytes
32 myBytes
= 0xabcdef
1234567890;
4.地址类型(address):存储20字节的以太坊地址
address myAddress = 0x7aDf8dA6CCE4D2bACd8C969e6e4EaF2D15614474;
function sendEther(uint256 _amount) public payable {
require(msg.value == _amount, "Incorrect amount sent");
address payable rec
IPient
= payable(myAddress);
rec
IPient.transfer(_amount);
//在 sendEther() 函数中,我们向合约发送以太币,并要求发送的以太币数量与指定的 _amount 参数相等。然后,我们使用 payable 将 myAddress 转换为可
支付地址类型,并使用 transfer() 方法将指定数量的以太币发送到该
地址
属性:
balance:返回
地址对应账户的余额。
address myAddress = 0x1234567890123456789012345678901234567890;
uint256 balance = myAddress.balance;
方法:
transfer(uint256 amount):向
地址发送指定数量的以太币。如果发送失败,则会回滚所有状态更改。
address payable rec
IPient
= payable(
0x
1234567890123456789012345678901234567890);
uint256 amountToSend = 1 ether;
rec
IPient.transfer(amountToSend);
send(uint256 amount) returns (bool):向
地址发送指定数量的以太币,返回一个布尔值表示发送是否成功。
address payable rec
IPient
= payable(
0x
1234567890123456789012345678901234567890);
uint256 amountToSend = 1 ether;
bool success
= rec
IPient.
send(amountToSend);
5.枚举类型(enum):定义了一组明明常量
enum MyEnum { VALUE1, VALUE2, VALUE3 }
MyEnum myEnum = MyEnum.VALUE2;//使用枚举型声明变量并给它赋值
//枚举型由一组预定义常量组成,枚举成员会按照定义的顺序从 0 开始依次递增。例如MyEnum.VALUE1=0;MyEnum.VALUE1=1依次递增;
//在 Solidity 中,枚举类型可以隐式地转换为整数类型(uint8)。
//枚举类型通常在全局作用域里面声明(有时候也在结构体但是比较少),不能在函数里面声明
引用类型(Reference Types)
引用类型存储数据的引用(即存储位置),而不是实际的数据值。在处理引用类型时,操作的是数据在存储器中的位置,而不是数据本身
1.动态数组(dynamic ary):在 Solidity 中,数组是引用类型,可以根据需要动态调整大小。
//myArray就是定义的变量名,动态数组的定义比较独特
2.映射(mapping):Mapping 类型类似于哈希表,它将键映射到值。Mapping 可以用来创建键值对的存储结构,并在 O(1) 的时间复杂度下查找、插入和删除键值对。
mapping(
address => uint) balances;
3.结构体(struct):自定义数据结构,可以包含多个不同类型的数据成员
变量
状态变量的可见性:
public:状态变量被声明为 public 后,Solidity 会自动生成一个对应的 getter 函数,使得该变量可以被外部合约或外部调用者读取。如果合约是其他合约的基类,那么继承合约可以
访问父合约中声明为 public 的状态变量。
uint256 public myPublicVar;
private:状态变量被声明为 private 后,只有当前合约内部可以
访问该变量,外部合约无法直接
访问。
uint256 private myPrivateVar;
internal:状态变量被声明为 internal 后,只有当前合约及其派生合约(继承合约)可以
访问该变量,外部合约无法直接
访问。
uint256 internal myInternalVar;
变量的默认值:
bool defaultBool = false;
// address 类型变量默认值为0X000...(40个0)
address defaultAddress = address(0);
// bytes类型变量默认值为0x000...(64个0)
bytes32 defaultBytes = 0x0;
string defaultString = "";
enum MyEnum { First, Second, Third }
MyEnum defaultEnum = MyEnum.First;
复合类型变量的默认值:
又每一项变量的默认值组成;
delete操作符:
在 Solidity 中用于将变量重置为其类型的默认值。它可以应用于各种类型的变量,包括基本类型、数组、映射和结构体等。使用 delete 操作符可以将变量恢复到其初始状态,这对于清理合约状态或重置变量非常有用
常量constant和immutable:
常量的命名规则和变量相同,但通常使用大写字母表示,单词之间用下划线”_”连接,
相同点:都能限制对状态变量的修改。
常量 (constant):常量是在
编译时确定并
存储在
代码中的值。常量可以在合约内部或函数内部声明,并且作用域仅限于声明所在的作用域。常量在声明时必须进行初始化,并且不能被
修改。
contract ConstantExample {
uint constant internal MY_CONSTANT = 42;
function getConstant() public pure returns (uint) {
uint myValue = MY_CONSTANT;
不可变 (immutable):不可变变量是在合约
部署时确定并
存储在区块链上的值。不可变变量在声明时必须进行初始化,并且只能在构造函数中赋值一次。不可变变量的值可以是在
编译时可以确定的常量,也可以是在
部署时可以确定的值
contract ImmutableExample {
uint immutable public MY_IMMUTABLE;
MY_IMMUTABLE = block.timestamp;
function getImmutable() public view returns (uint) {
ps:常量声明时需要直接赋值,且不能在构造函数中给常量赋值;而不可变变量通常在构造函数中初始化,可以在构造函数中给不可变变量赋值
区别两个方面:
1.初始化时机:constant修饰的状态变量必须在声明时就立即显示赋值,之后就不在允许修改。Immutable修饰的状态变量既可以在声明时显示赋值,又可以在构造的函数中赋值。
2.Constant可以修饰任何数据类型,immutable只能修饰值类型(int ,uint, bool, address)
Ps:使用常量的好处
代码可读性
代码重用预防错误节省Gas
函数:
function 函数名称(<参数列表>)<可见性><状态可变性> return(<返回值类型>){
return(变量1,变量2);//多个返回值时候适用
1.函数的语法:
function 函数名(<参数列表>)<可见性> <状态可变性> return (<返回值类型>){
}
命名
规则:
1.函数是由字母,数字下划线组成的。
2.以小写字母或者下划线开头。
3.采用驼峰形式。
可见性:
函数的可见性分为四种:
private:只能在所属(本地)的智能合约内部调用public修饰的函数可以从任何地方调用,既可以在智能合约的内部调用也可以在智能合约的外部调用internal可在所属的只能合约里调用,也可以在继承的合约里调用external只能在智能合约的外部调用不能在智能合约的内部调用
函数的返回值:
在solidity中函数可以没有返回值,也可以有一个或者多个返回值。Ps有多个返回值的时候需要用括号包裹全部返回值变量,
函数状态可变性:
1.pure(指状态函数不会修改和读取合约的状态(数据))
function sum() public pure returns(uint){
}
//该函数并没有使用任何状态变量因此其不会读取区块链的状态,也不会改变合约的状态。即该函数不会和区块阿里区块链发生任何的
关系。
//如果函数中出现以下语句则被视为读取了状态
数据,就不能使用pure函数否则无法通过
编译
2..view(函数会读取合约的状态但是并会修改,即函数会读取链上的数据但是并不会对数据进行修改)
function test(uint num) public view returns(uint){
return num * a;//读取并使用了状态变量a
//函数中存在以下语句则被视为
修改了状态
数据,就不能使用可见性view了
3.payable(表明这个函数可以接收以太币)
function test(uint ID ) public payable{//标记为payable,表示它可以接收以太币
//这些以太币市调用者在调用函数时
支付的,由于payble函数接受了以太币,所以它是改变合约状态的
4.未标记 (意味着这个函数是要改变合约状态的,即修改合约的状态变量或者向其它合约发送交易等)
function test(uint num) public view returns(uint){
return num
* a;
//读取并
修改了状态变量a
小结:函数的状态可变性的
设计目的是为了确保合约的
安全性,可靠性,和互
操作性
1.安全性:编译时对函数的行为进行检验, 帮助开发者避免不经意间修改合约的状态或者方位未经授权的外部合约,从而减少潜在的安全漏洞。
2.可靠性:通过明确函数的状态可变性,合约的使用者可以更好地理解和预测函数的行为。
3.互操作性:通过标记函数的状态可变性,可以提供给其他合约和工具有关函数的重要信息。
ps:为了减少gas费用尽量使用pure和view函数。
特殊函数:构造函数constructor和receive函数
1.构造函数constructor:
可以在构造函数中传递参数来初始化合约的状态变量。这些参数可以在
部署合约时提供。
*4.命名与可见性: 构造函数的名字必须与合约名相同,并且不能有返回类型或任何函数修饰符(如public、external等)。构造函数默认为internal可见性,不需要显式声明可见性。
// SPDX-License-Identifier: MIT
constructor(uint _number) {
//构造函数的可见性默认为public可以被任何人调用
//构造函数的可变性不能
设置为pure或者view,但可以为payable属性。因为构造函数通常是用来初始化变量,它是会
修改合约状态的。
receive函数:
补充知识:账户类型
1.外部账户 EOA,
例如小狐狸里面的账户
账户地址是以"0x"开头长度为20字节的十六进制数
外部账户都有对应的私钥,只有持有私钥的人才能对交易进行签名, 所以外部账户适用于资金管理的身份验证。
2.内部账户 CA:
在以太坊
部署一个合约之后都会产生一个对应的账户称为合约账户,合约账户只要用于托管只能合约,它里面包含着智能合约的二进制
代码和状态消息,合约账户也会有一个账户
地址是以"0x"开头长度为20字节的十六进制数。合约账户没有私钥只能由合约
代码中的逻辑
控制。在一定条件下可以存入ETH(以太币)。
只有定义了receive函数或者fallback函数才能接收以太币
、receive() external payable {
在 Solidity 中,事件(Event)是一种特殊的合约结构,用于记录合约中的重要状态变化或触发的操作。事件可以被合约内部的函数触发,并且可以被外部应用程序监听和响应。
事件是 Solidity 中用于记录重要状态变化和操作的机制,可以方便地与外部应用程序进行交互,提供更多的可观察性和可追溯性。
事件语法
event EventName(arg1Type arg1, arg2Type arg2, ...);
//EventName:
事件的名称,使用驼峰命名法。
//arg
1Type、arg
2Type:
事件参数的类型。
触发
事件:
emit
EventName(arg1Value, arg2Value, ...);
event NewUser(address indexed userAddress, string username);
function registerUser(string memory username) public {
emit NewUser(msg.sender, username);
这是一个简单示例展示了如何定义触发一个事件
在上述示例中,合约 EventExample 定义了一个名为 NewUser 的事件。NewUser 事件有两个参数:userAddress(用户地址)和 username(用户名)。当调用 registerUser 函数进行用户注册时,会触发 NewUser 事件,并传递相应的参数值。
require
在 Solidity 中,require 是一种异常处理机制,用于在函数执行过程中验证条件是否满足。如果条件不满足,require 会抛出异常并中止函数执行,同时回滚所有状态更改。通常用于输入参数验证、前置条件检查和合约内部状态验证等场景。
语法
require(condition, errorMessage);
//condition:要
验证的条件表达式,如果为
false,则触发异常。
//errorMessage:可选参数,用于指定异常信息,将在异常发生时显示
使用背景:
1.输入参数验证
function deposit(uint256 amount) public {
require(amount > 0, "Deposit amount must be greater than 0");
2.前置条件检查:
function transfer(address to, uint256 amount) public {
require(
to !
= address(
0),
"Invalid recIPient address");
require(amount <= balances[msg.sender], "Insufficient balance");
3.合约内部状态验证:
function withdraw(uint256 amount) public {
require(amount <= address(this).balance, "Insufficient contract balance");
4.复杂条件检查:
function buyToken(uint256 amount) public {
require(amount >= minPurchase && amount <= maxPurchase, "Invalid purchase amount");
修饰器:
在 Solidity 中,修饰器(Modifier)是一种特殊的函数,用于修改函数的行为。通过使用修饰器,可以在执行函数之前或之后添加额外的逻辑或条件检查,以确保函数按照预期方式运行。
修饰器通常用于以下情况:
权限控制: 通过修饰器可以实现
权限控制,限制只有特定账户可以调用某个函数。
状态检查: 在函数执行之前进行状态检查,确保满足特定条件才能执行函数。
日志记录: 可以在函数执行前后记录
事件,用于
日志记录或跟踪
功能。
代码复用: 可以将常用的逻辑包装在修饰器中,然后在多个函数中重复使用。
修饰器由关键字 modifier 定义,可以在函数定义前使用 modifier 进行修饰。修饰器可以接收参数,并且可以通过 _; 在修饰器内部标记函数应该被执行的位置。
下面是一个简单的示例,演示了如何在 Solidity 中定义和使用修饰器:
require(msg.sender == owner, "Only owner can call this function");
function changeOwner(address _newOwner) public onlyOwner {
在上面的示例中,onlyOwner 是一个修饰器,用于限制 changeOwner 函数只能被合约的所有者调用。修饰器首先检查调用者是否是合约的所有者,如果是则继续执行函数,否则中断函数执行并抛出异常。
solidity运算符:
算术运算符:
+:加法-:减法*:乘法/:除法%:取模(取余)
比较运算符:
==:等于!=:不等于<:小于>:大于<=:小于等于>=:大于等于
逻辑运算符:
&&:逻辑与(and)||:逻辑或(or)!:逻辑非(not)
位运算符:
&:按位与|:按位或^:按位异或~:按位取反<<:左移>>:右移
赋值运算符:
=:赋值+=:加后赋值-=:减后赋值*=:乘后赋值/=:除后赋值%=:取模后赋值&=:按位与后赋值|=:按位或后赋值^=:按位异或后赋值<<=:左移后赋值>>=:右移后赋值
三元条件运算符:
condition ? value1 : value2:如果条件成立,则返回value1,否则返回value2。
流程控制语句:
条件语句(if、else if、else):
if语句用于在满足指定条件时执行特定
代码块。else if可以在前面的if条件不满足时再次进行条件判断。else用于处理所有其它情况,即前面所有条件都不满足时执行的
代码块。
循环语句(for、while、do-while):
for循环用于按照指定条件重复执行一段
代码。while循环在每次迭代之前检查条件是否成立,如果条件成立则执行
代码块。do-while循环先执行一次
代码块,然后在每次迭代之前检查条件是否成立。
for (uint i = 0; i < 10; i++) {
异常处理语句(try/catch):
try语句用于包裹可能抛出异常的
代码块。catch语句用于捕获并处理try
代码块中抛出的异常。
} catch (ExceptionType variableName) {