初学solidity的一些笔记

  • undefined
  • 更新于 2024-08-20 18:59
  • 阅读 266

初学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));
    }

}
点赞 0
收藏 1
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
undefined
undefined
双非一本,在读大二,想要学习web3