初学solidity的一些笔记
// SPDX-License-Identifier: MIT
public: 内部和外部都可以访问。
private: 仅合约内部可以访问。
external: 仅外部可以访问。
internal: 仅合约内部和继承的子合约可以访问。
storage: 用于存储长期保存的数据,数据在区块链上持久存在。
returns(string memory,
uint[] memory,
bytes memory,
Person memory,)
mapping 类型不能作为函数的返回类型
其中Person为自定义的结构体
memory: 用于存储临时数据,数据只在函数调用期间存在,函数结束后会被清除。
calldata: 用于存储函数参数的临时数据,只读且不可修改,适合传递大型数据。
calldata只能用于函数参数
calldata不能使用在构造函数的参数中
pragma solidity ^0.8.0;
// pragma solidity >=0.8.0 <0.9.0;
contract SimpleStorage {
uint256 myFavoriteNumber;
//结构体
struct Person {
uint256 favoriteNumber;
string name;
}
// uint256[] public anArray;
//数组
Person[] public listOfPeople;
//map集合 key->value
mapping(string => uint256) public nameToFavoriteNumber;
//函数
//virtual关键字表示该方法可以被重写
function store(uint256 _favoriteNumber) public virtual {
myFavoriteNumber = _favoriteNumber;
}
//view表示只读函数,
function retrieve() public view returns (uint256) {
return myFavoriteNumber;
}
// string,数组,结构体是特殊类型,需要指定memory calldata storage
//memory代表短时间存储可以修改值
//calldata代表短时间存储 不可用修改值
//storage代表 永久存储,只能在函数外使用
function addPerson(string memory _name, uint256 _favoriteNumber) public {
listOfPeople.push(Person(_favoriteNumber, _name));
nameToFavoriteNumber[_name] = _favoriteNumber;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
//导入整个solidity文件
//import "./SimpleStordage.sol"
//导入该文件下的部分合约
import {SimpleStorage} from "./SimpleStorage.sol";
contract StorageFactory{
//SimpleStorage1 public simpleStorage;
SimpleStorage[] public SimpleStorageList;
//通过一个合约部署其他合约
function creatSimpleStorageContract() public{
//simpleStorage=new SimpleStorage1();
//new 关键字表示部署该合约
SimpleStorage simpleStorage=new SimpleStorage();
SimpleStorageList.push(simpleStorage);
}
//通过该合约与其他合约交互
function store1(uint256 indexOfContract,uint256 number) public{
SimpleStorageList[indexOfContract].store(number);
}
//通过该合约与其他合约交互
function getNumber(uint256 indexOfContract) public view returns(uint256){
return SimpleStorageList[indexOfContract].retrieve();
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
import {SimpleStorage} from "./SimpleStorage.sol";
//is关键字用于合约继承
//拥有另一个合约所有的变量和函数
contract AddFive is SimpleStorage{
// override 关键字表示该方法为重写父类的方法
function store(uint256 number) public override {
myFavoriteNumber=number+5;
}
}
1e18Wei=2000e18usd
msg.value*price/1e18
1eth =2000. 000000000000000000 usd
msg.value= wei
1000000000000000000
1eth=1e18Wei
contract MyContract{
bool public b;
uint public u;
int public i;
int public minInt=type(int).min;
int public maxInt=type(int).max;
//地址类型
address public addr;
//字节类型
bytes32 public b32;
//pure表示该函数不能 读和写 状态变量
//也就是说不能对 链上有任何的读写操作
//external 表示该函数只能从外部调用
fucntion add(uint x,uint y) external pure returns (uint){
return x+y;
}
//view表示该函数为只读函数,可以读取状态变量
function globalVars() external view returns(address,uint,uint){
//调用该函数的 (用户或合约) 的地址
address sender=msg.sender;
//该区块的时间戳
uint timestamp=block.timestamp;
//区块编号
uint blockNum=block.number;
return (sender,timestamp,blockNum)
}
}
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0
//计数器
contract Counter {
uint public count;
//对count+1
function increase() external {
count+=1;
}
//对count-1
function decrease() external {
count-=1;
}
}
//基本数据类型的默认值
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0
//默认值
contract DefaultValues{
bool public b;// false
uint public u; // 0
int public i; // 0
address public a; //0x0000000000000000
byte32 public b32; //0x000000000000000000000000000000
}
// 常量 constant
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0
//常量constant
contract Constants {
// constant 关键字定义常量
// 可以节省一定的 gas
address public constant myAddress = 0x123455
}
// if else 语句 和三元运算符
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0
contract IfElse{
function example(uint x) external pure returns (uint){
//if else 语句
if(x < 10){
return 1;
} else if (x < 20){
return 2;
}else {
return 3;
}
}
function ternary(uint x) external pure returns (uint) {
//使用三元运算符
return x < 10 ? 1 :2;
}
}
// for循环 和 while循环
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0
// for循环 while循环
contract ForAndWhileLoop {
function For() external pure {
for(uint i = 0; i < 10 ; i++ ){
//continue;
//break;
}
uint j = 10;
while(j >= 0){
j--;
}
}
}
// require revert assert
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0
// require revert assert
// gas费的退还 和 状态变量回滚
contract Error{
//自定义错误
error MyError(address sender, string errorMessage);
function test(uint i) public view {
//require
require(i <= 10,"提示信息:i必须小于或等于10");
//revert
if(i >10){
revert("提示信息:i必须小于或等于10");
//使用自定义错误可以节省 gas费
revert MyError(msg.sender,"提示信息:i必须小于或等于10");
}
//使用assert 没有提示信息
assert(i <= 10);
}
}
//函数修改器
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0
//函数修改器modifier
//类比 切面编程
contract MyContract{
//该合约是否已停用
bool public paused;
//计数器
uint public count;
//基本使用
modifier checkPaused(){
require(!paused,"该合约已经停用");
_;
}
function increase() external checkPaused {
count += 1;
}
//传入参数
modifier cap(uint x){
require(x < 100,"x必须小于100");
// _; 表示调用使用该函数修改器的函数
_;
}
function decrease(uint x) external cap(x){
count += x;
}
//三明治切面
modifier sandWich(){
//code here
count += 2;
_;
//code here
count += 55;
}
function foo() external sandWich{
count += 1;
}
}
//构造函数constructor
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract MyContract {
//部署该合约的 账户或合约 的地址
address public owner;
uint public x;
//构造函数,只执行一次
//在部署该合约的时候就执行了构造函数
//需要传入参数 x
constructor(uint _x){
owner = msg.sender;
x = _x;
}
}
//return 返回值
contract Return {
function returnMany() public pure returns (uint,bool){
return (1,true);
}
function returnMany() public pure returns (uint x,bool b){
return (1,true);
}
function returnMany() public pure returns (uint x,bool b){
x = 1;
b = true;
}
//获取返回值
function getReturns() public pure{
(uint x,bool b) = returnMany();
//只获取自己需要的返回值
//是有必要的,因为可以节省一些 gas
(,bool b) = returnMany();
}
}
// 数组
contract Array {
//动态数组
uint[] public arr1 = [1,2,3,4];
//静态数组
uint[3] public arr2;
function example() public {
// push 向数组中增加元素
arr1.push(5); // [1,2,3,4,5]
// 利用索引获取数组元素
uint number = arr1[0]; // 1
// 修改数组元素
arr1[0] = 999; // [999,2,3,4,5]
// 删除
// 删除不会修改数组的长度,所以会将对应索引的元素修改为默认值
delete arr1[0];//[0,2,3,4,5]
// 弹出 pop 数组长度会-1
arr1.pop();
// 获取数组长度
uint len = arr1.length;
// 局部变量只能定义定长数组
uint[5] memory arr;
}
//返回数组
function returnArr() external view returns (uint[] memory){
return arr1;
}
}
// mapping映射
contract MyContract {
mapping(address => uint) public addressToBalance;
function example() public {
//增
addressToBalance[msg.sender]=123;
//删
//删除就是将其修改为默认值
delete addressToBalance[msg.sender];
//查
uint balance=addressToBalance[msg.sender];
}
}
// 结构体
contract MyContract {
struct Car {
string model;
uint year;
address owner;
}
function example() public {
//创建结构体变量
Car memory car1=Car("奔驰",1990,msg.sender);
Car memory car2=Car({year: 1980,model:"baoma",owner: msg.sender});
}
}
//枚举类型 enum
contract MyContract {
enum Status {
//第一个元素就是默认值
None,
Pending,
Shipped,
Completed,
Rejected,
Canceled
}
Status public status;
//设置状态
function setStatus(Status _status) external {
status = _status;
}
//将状态设置为Shipped
function ship() external {
status = Status.Shipped;
}
//重置默认值
function reset() external {
delete status;
}
}
// 事件
在Solidity中,event、emit 和 indexed 是用于在区块链上记录日志的关键字和功能。它们主要用于合约和外部应用之间的通信,通过事件将信息传递给区块链外部的监听者,如前端应用或外部服务。以下是它们的详细说明和使用示例。
1. event
概念: event 是一种声明日志的方式,用于记录合约中的特定操作。事件可以理解为在区块链上存储的日志,这些日志可以通过外部监听工具(如Web3.js或Ethers.js)捕捉到。
使用场景: 适用于在合约执行过程中向外部传递信息,如通知外部某个操作已经完成或记录某些关键数据。
2. emit
概念: emit 是触发事件的关键字。声明了一个事件后,需要通过emit关键字在合约的具体操作中触发该事件,从而将日志写入区块链。
使用场景: 当某个条件满足或某个操作完成时,通过emit触发事件,将信息传递给监听者。
3. indexed
概念: indexed 关键字用于标记事件中的某些参数,使这些参数可以被索引和过滤。最多可以有三个参数使用indexed,这有助于更高效地查询和过滤区块链日志。
使用场景: 适用于需要过滤查询特定事件参数的情况,比如查找特定地址触发的事件。
contract MyContract {
// 声明一个事件,包含三个参数,其中两个是索引的
event Transfer(address indexed from,address indexed to,uint value);
// 一个简单的代币转账函数
function transfer(address to,uint value) {
// 触发事件,记录转账信息
emit Transfer(msg.sender,to,value);
}
}
// 继承is 重写 virtual override
contract A {
//virtual关键字表示该方法可被重写
function func1() public virtual pure returns (string memory) {
return "a";
}
function func2() public pure returns (string memory) {
return "a";
}
}
// is 关键字 B合约拥有A合约所有的状态变量和方法
contract B is A{
//override关键字 表示该方法为重写方法
function func1() public pure override returns (string memory) {
return "b";
}
}
_____________________________________________________________________________
contract A {
string public text;
constructor(string memory _text) {
text = _text;
}
}
contract B {
uint public number;
constructor(uint _number) {
number = _number;
}
}
contract C is A,B {
//多线继承下的 构造函数
constructor(string memory text,uint number) A(text) B(number) {
}
}
// immutable不可变量
// 使用immutable还可以节省 gas费
// immutable适合 在编写代码时,不知道应该设置什么数值,但是在部署合约时就数据就固定了的场景
contract MyContract {
//在部署合约的时候进行赋值,一旦赋值不可改变
address public immutable owner;
constructor() {
owner = msg.sender;
}
}
// payable
// 回退函数 fallback receive
// 直接向合约发送以太坊主币 或 调用的合约函数不存在 的时候 就会调用回退函数
// 如果msg.data为空就会直接调用receive回退函数
// 如果msg.data不为空就会调用fallback回退函数
contract A {
//payable关键字表示该地址可以发送以太坊主币
address payable public owner;
constructor() {
// 必须使用payable()
// 默认msg.sender 的地址没有payable属性
owner = payable(msg.sender);
}
// 用 payable关键字标记函数 ,这个函数就能够接收以太坊主币
function func() external payable {}
// 获取该合约的余额
function getBalance() external view returns (uint) {
return address(this).balance;
}
event Log(string func,address sender,uint value,bytes data);
//fallback payable关键字表示能接收主币
fallback() external payable {
emit Log("fallback",msg.sender,msg.value,msg.data);
}
//receive回退函数专门用来接收主币
receive() external payable {
emit Log("fallback",msg.sender,msg.value,"");
}
}
// transfer send call 发送以太币
//发送以太币
contract SendEth {
constructor() payable {}
receive() external payable { }
//transfer
function transfer(address payable to) external payable {
to.transfer(1e18);
}
function send(address payable to) external payable {
//返回布尔值
bool success = to.send(123);
require(success,"send failed");
}
function call(address payable to) external payable {
//返回布尔值
//value属性为转账金额,空字符串""表示不调用合约函数
(bool success,) = to.call{value:123}("");
require(success,"send failed");
}
}
//接收以太币
contract EthReceiver {
//记录接收的金额,剩余gas
event Log(uint amount,uint gas);
receive() external payable {
//transfer和send剩余2260gas
//call会剩余更多
emit Log(msg.value,gasleft());
}
}
// 存币取币合约 只有管理员能取币
contract EtherWallet {
//定义一个有payable属性的不可变量管理员地址
address payable immutable public owner;
//部署该合约的账户地址就是管理员地址
constructor() payable {
owner=payable(msg.sender);
}
receive() external payable { }
//只有管理员能取eth
function withdraw(uint amount) external {
address payable sender=payable(msg.sender);
require(owner==sender,"only owner can withdraw");
require(address(this).balance > amount,"no have engough eth");
sender.transfer(amount);
}
function getBalance() external view returns(uint amount) {
return address(this).balance;
}
}
// 合约调用
contract CallContract {
function setX(address addr,uint x) external {
// 使用 TestContract(addr) 将地址变为合约类型
TestContract(addr).setX(x);
}
function setValue(TestContract contractAddr) external payable {
// 将该函数收到的以太币发送给下一个合约
contractAddr.setValue{value: msg.value}();
}
function getX(address addr) external view returns (uint) {
// TestContract(addr).x() 是调用 TestContract 合约的自动生成的 getter 函数 x(),
// 这个函数返回 x 的值,也就是一个 uint256 类型的数据。
return TestContract(addr).x();
}
function getValue(address addr) external view returns (uint) {
return TestContract(addr).value();
}
}
contract TestContract {
uint public x;
uint public value;
function setX(uint _x) public {
x=_x;
}
function setValue() public payable {
value=msg.value;
}
}
// 接口interface
在 Solidity 中,`interface` 是一种特殊的合约类型,它定义了合约中函数的外部接口,但不提供函数的实现。接口的作用类似于其他编程语言中的接口或抽象类,用于定义标准、分离实现和声明,以及实现多合约之间的交互。
### 接口的特点
- **没有状态变量**:接口不能包含任何状态变量。
- **没有构造函数**:接口不能有构造函数。
- **所有函数都是外部的**:接口中的所有函数都必须标记为 `external`,因为它们只能在合约外部被调用。
- **不能有函数实现**:接口中的所有函数只能声明,不能有实现。
- **不允许继承其他合约或接口**:接口不能继承其他合约,但可以继承其他接口。
### 接口的定义与使用
#### 1. 定义接口
pragma solidity ^0.8.0;
interface ITestContract {
function setX(uint _x) external;
function getX() external view returns (uint);
}
在这个例子中,`ITestContract` 是一个接口,它定义了两个函数 `setX` 和 `getX`。但是,它没有实现这些函数的具体逻辑。
#### 2. 使用接口
pragma solidity ^0.8.0;
contract TestContract {
uint public x;
function setX(uint _x) external {
x = _x;
}
function getX() external view returns (uint) {
return x;
}
}
contract CallContract {
function updateX(address contractAddress, uint _x) external {
ITestContract(contractAddress).setX(_x);
}
function fetchX(address contractAddress) external view returns (uint) {
return ITestContract(contractAddress).getX();
}
}
```
在这个例子中:
- **`TestContract`** 是一个实现了 `ITestContract` 接口的合约,提供了 `setX` 和 `getX` 函数的具体实现。
- **`CallContract`** 是另一个合约,它通过接口 `ITestContract` 与 `TestContract` 进行交互。
#### 3. 接口的用途
- **标准化**:接口允许开发者定义一组标准的函数,让不同的合约都可以实现这些标准,从而使得不同合约之间能够方便地互操作。
- **解耦合**:接口将函数的声明与具体实现分离,减少了合约之间的耦合度,使得系统设计更加灵活。
- **跨合约调用**:通过接口,可以方便地调用其他合约中的函数,而不需要知道它们的具体实现,这在构建复杂的智能合约系统时非常有用。
### 接口的继承
接口之间可以相互继承,类似于普通合约的继承。
pragma solidity ^0.8.0;
interface IBaseContract {
function getValue() external view returns (uint);
}
interface IExtendedContract is IBaseContract {
function setValue(uint _value) external;
}
在这个例子中,`IExtendedContract` 接口继承了 `IBaseContract` 接口,这意味着实现 `IExtendedContract` 的合约必须同时实现 `IBaseContract` 中的所有函数。
### 现实应用
- **ERC 标准**:以太坊的 ERC-20、ERC-721 等标准都是通过接口来定义的,这样不同的合约都可以实现这些标准,并且可以通过标准的接口与之交互。
- **去中心化金融(DeFi)**:在 DeFi 应用中,接口被广泛用于不同合约之间的交互,确保它们可以遵循统一的标准进行互操作。
// 低级call
contract SimpleStorage {
string public text;
uint public number;
address public sender;
event Log(string message);
//调用不存在的函数会执行 fallback函数
fallback() external payable {
emit Log("fallback function");
}
receive() external payable { }
function setText(string calldata _text) external payable {
sender=msg.sender;
text=_text;
}
function setNumber(uint _number) external {
number=_number;
}
}
contract TestCall {
function setText(address contractAddress,string calldata text) payable external {
// {value: ,gas: } 花括号中可以指定往这个地址转多少wei,和花费的gas limit
//abi.encodeWithSignature("函数名(参数类型,参数类型,...)",函数参数,函数参数,...)
(bool success,) = contractAddress.call{value: 1e18}
(abi.encodeWithSignature("setText(string)", text));
//使用 abi.encodeWithSelector
(bool success,) = contractAddress.call{value:1e18}
(abi.encodeWithSelector(SimpleStorage.setText.selector,text));
require(success,"call failed");
}
function callNoExit(address contractAddress) external {
(bool success,) = contractAddress.call(abi.encodeWithSignature("NoExitFunction"));
require(success,"call failed");
}
}
// 委托调用 delegatecall
`delegatecall` 是 Solidity 中一种特殊的低级函数调用方式,允许一个合约在另一个合约的上下文中执行代码。这意味着在使用 `delegatecall` 时,代码被调用方(被调用的合约)执行,但上下文(例如存储、余额、msg.sender 等)保持调用方的环境。`delegatecall` 是构建代理合约(proxy contract)和可升级合约(upgradeable contract)模式的核心。
### 基本概念
- **调用上下文**: 在 Solidity 中,每个合约都有自己的存储(状态变量)、余额和消息上下文(`msg.sender`、`msg.value`)。通常,合约 A 调用合约 B 时,B 会在自己的上下文中执行代码,修改 B 自己的状态变量。
- **`delegatecall` 的行为**: 使用 `delegatecall`,合约 A 可以调用合约 B 的代码,但代码执行时使用的是 A 的上下文。这意味着 B 的代码中对状态变量的修改、余额的操作等,实际影响的是 A 合约而不是 B 合约。
### `delegatecall` 的使用
```solidity
(bool success, bytes memory data) = target.delegatecall(
abi.encodeWithSignature("someFunction(uint256)", 123)
);
```
- `target`: 被调用的目标合约地址。
- `delegatecall`: 低级调用方法,返回一个布尔值 `success` 表示调用是否成功,以及一个字节数组 `data` 包含调用的返回值。
- `abi.encodeWithSignature`: 用于将函数签名和参数编码成 `delegatecall` 可以使用的字节数据。
### 例子:简单的 `delegatecall`
#### 1. 库合约(LogicContract)
```solidity
pragma solidity ^0.8.0;
contract LogicContract {
uint public x;
function setX(uint _x) public {
x = _x;
}
}
```
#### 2. 代理合约(ProxyContract)
```solidity
pragma solidity ^0.8.0;
contract ProxyContract {
uint public x; // 注意,这个 x 变量和 LogicContract 中的 x 对应
function delegateSetX(address logicContractAddr, uint _x) public {
(bool success, ) = logicContractAddr.delegatecall(
abi.encodeWithSignature("setX(uint256)", _x)
);
require(success, "Delegatecall failed");
}
}
```
在这个例子中,`ProxyContract` 使用 `delegatecall` 调用 `LogicContract` 的 `setX` 函数。虽然 `setX` 函数是在 `LogicContract` 中定义的,但实际修改的却是 `ProxyContract` 中的状态变量 `x`。
### 关键点
1. **存储和上下文**: `delegatecall` 执行时,使用的是调用方合约的存储布局。这意味着如果调用方和被调用方的存储布局不一致,可能会引发严重的错误。
2. **msg.sender 和 msg.value**: 在 `delegatecall` 中,`msg.sender` 和 `msg.value` 都保持调用方的上下文。这意味着 `msg.sender` 是外部调用者,而不是 `delegatecall` 的目标合约。
3. **返回值**: 使用 `delegatecall` 后,如果被调用的函数有返回值,可以通过低级调用返回的数据进行处理。
### 代理合约模式(Proxy Pattern)
`delegatecall` 是可升级合约的重要组成部分。通过代理合约模式,合约的逻辑可以通过更新逻辑合约地址来改变,而数据保存在代理合约中,这使得合约逻辑可以在不更改合约地址的情况下进行升级。
### 安全性注意事项
- **存储布局一致性**: 确保调用方和被调用方的合约有相同的存储布局,否则可能会导致数据混乱。
- **信任**: 被 `delegatecall` 调用的合约需要是可信的,因为它可以任意修改调用方合约的状态。
- **异常处理**: 处理好可能发生的异常,使用 `require` 确保调用成功。
### 总结
- **`delegatecall`** 是一种特殊的调用方法,允许一个合约在另一个合约的上下文中执行代码。
- **使用场景**: 通常用于构建可升级合约和代理合约。
- **注意点**: 使用时需要特别注意存储布局的一致性和代码的可信度,以避免安全漏洞。
`delegatecall` 提供了强大的功能,但由于其复杂性和潜在的安全问题,在使用时应格外小心。
// 工厂合约
contract Account {
uint public amount;
constructor ( ) payable {
amount = msg.value;
}
}
contract AccountFactory {
//合约数组
Account[] public accounts;
function createAccount() external payable {
// 使用new关键字创建合约
// 可以使用{}在创建合约的时候传入一定数量的以太币(Wei)
Account account=new Account{value: msg.value}();
accounts.push(account);
}
}
// 库合约 library
library Tool{
function find(uint[] storage arr,uint num) internal view returns (uint){
for(uint i=0;i<arr.length;i++){
if(arr[i]==num){
return i;
}
}
revert("no found");
}
}
contract MyContract {
// 将库合约应用到 uint[] 数组中
using Tool for uint[];
uint[] public arr;
function testFind() external view returns(uint){
return Tool.find(arr,3);
//可以通过 arr.find(3) 直接调用库合约中的函数
return arr.find(3);
}
}
// 哈希算法 keccak256
contract MyContract {
//哈希值的类型就是 byte32 是固定大小的 所以不需要使用memory
function getHashByPacked(string calldata str1,string calldata str2) external pure returns (bytes32) {
//使用encodePacked会容易参数哈希碰撞
// 如 str1: "AAAA" str2: "BBB"
// str1: "AAA" str2: "ABBB"
//产生的是相同的哈希
return keccak256(abi.encodePacked(str1,str2));
}
function getHash(string calldata str1,string calldata str2) external pure returns (bytes32) {
return keccak256(abi.encode(str1,str2));
}
}
// 验证签名
contract Verify {
function verify(string calldata message,bytes calldata signature,address signer) public pure returns (bool){
bytes32 messageHash = getMessageHash(message);
bytes32 ethSignedMessageHash = getEthSignedMessageHash(messageHash);
return recover(ethSignedMessageHash,signature)==signer;
}
// 生成消息哈希
function getMessageHash(string calldata message) public pure returns (bytes32) {
return keccak256(abi.encodePacked(message));
}
// 生成以太坊签名消息格式的哈希
function getEthSignedMessageHash(bytes32 messageHash) public pure returns (bytes32) {
return keccak256(abi.encodePacked(
"\x19Ethereum Signed Message:\n32",
messageHash));
}
// 使用 ecrecover 恢复签名者地址
function recover(bytes32 ethSignedMessageHash,bytes memory signature) public pure returns (address) {
(bytes32 r,bytes32 s,uint8 v) = splitSignature(signature);
return ecrecover(ethSignedMessageHash, v, r, s);
}
// 将签名拆分为 r, s, v 三部分
function splitSignature(bytes memory sig)
public
pure
returns (bytes32 r, bytes32 s, uint8 v)
{
require(sig.length == 65, "invalid signature length");
assembly {
r := mload(add(sig, 32))
s := mload(add(sig, 64))
v := byte(0, mload(add(sig, 96)))
}
return (r, s, v);
}
}
// 关于签名分割为 r s v
在以太坊中,签名是由三部分组成的:`r`、`s`、和`v`。这些部分都是从签名数据中提取的,用于验证消息签名。以下是详细解释:
### 签名的组成部分
1. **`r`**: 这是签名的一部分,它是签名的 `EcdsaSignature` 中的 `r` 值。它是一个 32 字节长的值,表示签名生成过程中某个椭圆曲线点的 x 坐标的模。
2. **`s`**: 这是签名的另一部分,它是签名的 `EcdsaSignature` 中的 `s` 值。同样是一个 32 字节长的值,表示生成签名时另一个椭圆曲线点的计算结果。
3. **`v`**: 这个值用于确定具体的椭圆曲线点。它是一个 1 字节长的值,表示签名的恢复参数。以太坊使用的 `v` 通常是 `27` 或 `28`,表示从两个可能的恢复点中选择一个。
### 签名拆分的原因
在 Solidity 中,签名通常是一个 65 字节长的字节数组(32 字节 `r`,32 字节 `s`,1 字节 `v`)。为了使用 `ecrecover` 函数验证签名,需要先将这个字节数组拆分成 `r`、`s`、和 `v` 三部分。
### 签名拆分的具体实现
以下是一个将签名拆分为 `r`、`s`、和 `v` 的示例代码:
```solidity
pragma solidity ^0.8.0;
contract VerifySignature {
// 从签名中拆分出 r, s, v
function splitSignature(bytes memory sig)
public
pure
returns (bytes32 r, bytes32 s, uint8 v)
{
require(sig.length == 65, "invalid signature length");
assembly {
// 加载签名的前32字节到r
r := mload(add(sig, 32))
// 加载签名的接下来的32字节到s
s := mload(add(sig, 64))
// 加载最后一个字节到v
v := byte(0, mload(add(sig, 96)))
}
return (r, s, v);
}
// 使用 ecrecover 恢复签名者地址
function recoverSigner(bytes32 _ethSignedMessageHash, bytes memory _signature)
public
pure
returns (address)
{
(bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature);
return ecrecover(_ethSignedMessageHash, v, r, s);
}
}
```
### 具体步骤说明
mload(p) 是一个EVM指令,用于从内存地址 p 读取32字节的数据。EVM中的每个内存访问操作(如mload)都是基于32字节(256位)进行的,因为EVM是一个基于字(word)的虚拟机,每个字是32字节。
在 Solidity 中,动态数组(如 bytes)的内存布局的前32字节用于存储数组的长度。
因此,当你从一个 bytes 数组中加载数据时,需要跳过前32字节的长度信息,这样才能正确读取实际数据。
1. **加载 `r` 值**:
- 使用 `mload(add(sig, 32))`,这意味着从 `sig` 的起始位置加 32 字节偏移处开始加载 32 字节的数据(因为 Solidity 的内存存储是 32 字节对齐的),将其赋值给 `r`。
2. **加载 `s` 值**:
- 使用 `mload(add(sig, 64))`,这意味着从 `sig` 的起始位置加 64 字节偏移处开始加载接下来的 32 字节的数据,赋值给 `s`。
3. **加载 `v` 值**:
- 使用 `byte(0, mload(add(sig, 96)))`,这意味着从 `sig` 的起始位置加 96 字节偏移处开始加载数据,并取出最前面的字节,即 `v` 值。
### 总结
- 签名包含了 `r`、`s`、`v` 三部分,它们共同表示签名的完整信息。
- 在 Solidity 中,通过 `splitSignature` 函数可以从 65 字节的签名数据中拆分出 `r`、`s`、和 `v` 三个部分。
- 这些拆分出来的部分可以用于通过 `ecrecover` 函数恢复出签名者的地址,从而验证签名的有效性。
理解这些基本概念后,你可以在智能合约中实现安全的签名验证。
// 自毁合约 selfdestruct
contract Kill {
constructor() payable {}
function kill() external {
//强制将合约中的余额转到一个账户地址 并且停用了该合约
selfdestruct(payable(msg.sender));
}
}
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!