Skip to content

Commit

Permalink
add learning contract basics
Browse files Browse the repository at this point in the history
  • Loading branch information
kaifei committed Jul 8, 2024
1 parent 455de33 commit 1b206b8
Show file tree
Hide file tree
Showing 4 changed files with 230 additions and 1 deletion.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- [24062501-variable_function.md](./docs/24062501-variable_function.md)
- [24062801-merkle_tree.md](./docs/24062801-merkle_tree.md)
- [24062802-ecdsa.md](./docs/24062802-ecdsa.md)
- [24070401-contract_basics.md](./docs/24070401-contract_basics.md)


## 其他相关知识
Expand Down
2 changes: 1 addition & 1 deletion docs/24062802-ecdsa.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ ethereum 中 ECDSA 对消息进行签名及验证,有以下特殊点:

### solidity 中使用 ECDSA

在合约中可以使用 ECDSA 算法,实现类似白名单空投的效果(本质就是验证调用者是否为项目方指定的地址):
在合约中可以使用 ECDSA 算法,实现类似白名单空投的效果(本质就是验证调用者是否为项目方指定的地址; 即是否有项目方的授权):
1. 调用者链下向项目方请求签名,如果调用者地址是属于白名单中,则项目方使用私钥,对该地址进行签名,并返回签名结果
2. 调用者调用链上领取空投的合约方法,并带上项目方给的签名作为参数
3. 领取空投方法根据计算出的消息哈希,以及参数中的签名,计算出 signer,并与合约中设置的项目方地址比较,一致则允许领取,否则不允许领取
Expand Down
162 changes: 162 additions & 0 deletions docs/24070401-contract_basics.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# Contract Basics

- [Contract Basics](#contract-basics)
- [调用合约](#调用合约)
- [生成合约变量](#生成合约变量)
- [call](#call)
- [delegateCall](#delegatecall)
- [总结](#总结)
- [合约的创建与删除](#合约的创建与删除)
- [创建合约](#创建合约)
- [create](#create)
- [create2](#create2)
- [what is create2](#what-is-create2)
- [why need create2](#why-need-create2)
- [how to use create2](#how-to-use-create2)
- [删除合约](#删除合约)
- [ABI 编码与解码](#abi-编码与解码)
- [what is ABI](#what-is-abi)
- [ABI 编码方法](#abi-编码方法)
- [ABI 解码方法](#abi-解码方法)
- [哈希函数](#哈希函数)
- [keccak256 与 sha3](#keccak256-与-sha3)
- [try-catch](#try-catch)


## 调用合约

### 生成合约变量

在已知被调用合约的源码(接口)和地址的情况下,可以采取生成合约变量的方式来调用已部署的合约,有以下方式可用用来生成合约变量:

- 传入合约地址: `OtherContract(contractAddr).f(p1)``OtherContract(contractAddr).f{value: msg.value}(p1)`(调用时发送 eth)
- 传入合约变量: 调用方法定义时声明一个 `OtherContract c` 的参数,在方法体中采用 `c.f(p1);` 来调用
- 创建合约变量: 调用方法中 `OtherContract o = OtherContract(contractAddr); o.f(p1);`

> 总结: `contractName(contractAddr)` 来生成合约变量
具体代码实例详见: [](../example/contracts/ContractBasic.sol)

### call

在不知道合约源码或 `ABI` 的情况下,可以采用 `call` 来调用其他合约,具体方式如下:

- `contractAddr.call(bytecode)`
- - 其中 `bytecode=abi.encodeWithSignature("函数签名", 逗号分隔的参数)`,函数签名: `函数名(逗号分隔的参数类型)`
- - 调用时发送 ETH 及指定 gas: `contractAddr.call{value: x, gas: x}(bytecode)`

### delegateCall

deleteCall 的调用方式如下:

- `contractAddr.delegateCall(bytecode)`
- - `bytecode = abi.encodeWithSignature("函数签名", 逗号分隔的参数)`,函数签名为: `函数名(逗号分隔的参数类型)`

关系 `call``delegateCall` 的区别详见: [./24061701-delegate_call.md](./24061701-delegate_call.md)

### 总结

| 调用合约的方式 | 适应场景 | 语法 |
| --- | --- | --- |
| 生成合约变量 | 已知合约源码或 ABI | `ContractName(contractAddr)` |
| call | 不知道合约与源码及 ABI | `contractAddr.call(abi.encodeWithSignature("函数名(逗号分隔的参数类型)", 逗号分隔的参数))` |
| delegateCall | 不知道合约与源码及 ABI | `contractAddr.delegateCall(abi.encodeWithSignature("函数名(逗号分隔的参数类型)", 逗号分隔的参数))` |

## 合约的创建与删除

### 创建合约

#### create

在合约中创建新的合约,语法如下:

```solidity
ContractName x = new ContractName{value: _value}(params);
```

说明:
- 如果合约是 `payable` 修饰的,则可以在创建合约时传递 value 值

#### create2

##### what is create2
create2 的作用就是能够在合约未部署之前预测合约的地址

##### why need create2
满足在合约未部署之前,需要事先计算出合约地址的场景

合约地址的计算

| 创建合约方法 | 合约地址计算方法 | 说明 |
| --- | --- | --- |
| `create` | `contractAddr = hash(creatorAddr, nonce)` | <li> `creatorAddr`: 部署合约的钱包地址或者是合约地址 <li> `nonce`: 创建者地址的 nonce,由于是可变的,且不能准确预测,所以使用 `create` 方法创建出来的合约地址,是不可预测的 |
| `create2` | `contractAddr = hash("0xFF", creatorAddr, salt, initCode)` | <li> `0xFF`: 常量,用于区分 `create` 方法 <li> `creatorAddr`: 部署合约的钱包或合约地址 <li> `salt`: 一个创建者指定的 `byte32` 的值,目的是用来影响创建合约的地址 <li> `initcode`: 新合约的初始字节码(合约的 `creation code` 和构造函数的参数) |

##### how to use create2

create2 创建合约语法如下:

```solidity
ContractName x = new ContractName{salt: _salt, value: _value}(params)
```

说明:
- `salt`: 盐值
- `value`: 如果创建的合约时 `payable` 的,则允许创建时向其转账
- `params`: 新合约构造函数的参数

### 删除合约

## ABI 编码与解码

### what is ABI

`ABI`: application binary interface, 应用程序二进制接口,是与合约交互的标准。其数据也是通过了该类型进行编码,由于编码时只包含了数据,在解码时,要申明返回值的类型

### ABI 编码方法

| 方法名 | 备注 |
| --- | --- |
| `abi.encode(a, b, c)` | <li> 功能: 将每个参数编码成 32 字节的数据并拼接在一起 <li> 使用场景: 当要与合约进行交互时,就要采用该方法来编码参数 |
| `abi.encodePacked()` | <li> 功能: 将给定参数按其所需最低空间进行编码,功能类似 `abi.encode()`,只不过会省略多余填充的 0 <li> 使用场景: 不需要与合约交互,想节省一些空间,如计算 hash |
| `abi.encideWithSignature()` | <li> 功能: 与 `abi.encode` 功能类似,只不过第一个参数为函数签名(functionName(逗号分隔的参数类型)) <li> 使用场景: 在合约中调用其他合约时使用 |
| `abi.encodeWithSelector()` | <li> 功能: 与 `abi.encodeWithSelector` 类似,只不过第一个参数为函数选择器(bytes4(keccak256(functionName(逗号分隔的参数类型)))) <li> 使用场景: 调用其他合约时使用,其编码结果与 `abi.encideWithSignature()` 完全一致 |

### ABI 解码方法

`abi.decode` 用于解码 `abi.encode` 编码的二进制数据,将它还原成原本的参数,用法如下:

```solidity
function decode(bytes memory data) public pure returns(uint dx, address daddr, string memory dname, uint[2] memory darray) {
(dx, daddr, dname, darray) = abi.decode(data, (uint, address, string, uint[2]));
}
```

## 哈希函数

solidity 中常用的哈希函数是 `keccak256`,其用法如下:

```solidity
hash = keccak256(数据)
```

### keccak256 与 sha3

`keccak256``sha3` 的区别和联系:
- 联系: `sha3``keccak256` 标准化而来,很多场景可以同义
- 区别: 在 `sha3` 标准化完成时,更改了其内部算法,导致最终结果与 `keccak256` 不一致;以太坊在 `sha3` 标准化之前开发出来,所以以太坊的哈希函数是 `keccak256`

## try-catch

在 solidity 中 try catch 语法,只能用于外部函数或创建合约时 construct 的调用。基本语法如下

```solidity
try externalContract.f() returns(returnType val) {
// call 成功时执行
} catch {
// call 失败时执行
}
```

备注:
- `this.f()` 也可被视为外部函数调用
66 changes: 66 additions & 0 deletions example/contracts/ContractBasic.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity ^0.8.24;

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

contract OtherContract is Ownable {
uint256 public x;

// define err
error notEnoughBalanceToWithdraw(uint256 currentBalance, uint256 withdrawAmount);

// define events
event Received(address indexed depositor, uint256 amount);
event Withdrawal(address indexed withdrawer, uint256 amount);


constructor(uint256 p1) Ownable(msg.sender) {
x = p1;
}

function setValue(uint p1) public payable {
x = p1;
if (msg.value > 0) {
emit Received(msg.sender, msg.value);
}
}

function getValue() public view returns(uint256) {
return x;
}

function withdraw(uint256 amount) onlyOwner public {
require(amount > 0, "withdraw amount must gt 0");
if (address(this).balance < amount) {
revert notEnoughBalanceToWithdraw(address(this).balance, amount);
}

payable(msg.sender).transfer(amount);
emit Withdrawal(msg.sender, amount);
}

receive() external payable {
emit Received(msg.sender, msg.value);
}
}

contract Caller {

// 传入合约地址调用合约
function setValueAndTransferETH(address payable contractAddr, uint256 x) public payable {
OtherContract(contractAddr).setValue{value: msg.value}(x);
}

// 实例化合约变量调用合约
function getValue(address payable contractAddr) public view returns(uint256) {
OtherContract oa = OtherContract(contractAddr);
uint256 x = oa.getValue();
return x;
}

// 传入合约变量调用合约
function getValue2(OtherContract c) public view returns(uint256) {
uint256 x = c.getValue();
return x;
}
}

0 comments on commit 1b206b8

Please sign in to comment.