@@ -1435,7 +1435,6 @@ contract interactBAYC {
1435
1435
1436
1436
// 其他函数的实现...
1437
1437
}
1438
-
1439
1438
```
1440
1439
使用这种方法,开发者不需要重复编写接口部分,只需要关注具体实现。这样代码更加简洁、规范,并且减少了错误的可能。
1441
1440
##### 测验结果
@@ -2592,7 +2591,8 @@ Contract x = new Contract{salt: _salt, value: _value}(params);
2592
2591
- 100/100
2593
2592
2594
2593
2595
- ### 2024.10.18 ` SelfDestruct ` 自毁
2594
+ ### 2024.10.18
2595
+ #### WTF Academy Solidity 102.26 ` SelfDestruct ` 自毁
2596
2596
` selfdestruct ` 是智能合约中的一种操作,用于销毁合约并将合约剩余的` ETH ` 余额转移到指定地址。这个功能设计的初衷是应对合约发生错误时的极端情况。起初它被命名为` suicide ` (自杀),但由于敏感性问题,后来改名为` selfdestruct ` 。
2597
2597
坎昆升级后,[ EIP-6780] ( https://eips.ethereum.org/EIPS/eip-6780 ) 对` selfdestruct ` 进行了功能限制。该提案是为了支持Verkle Tree,而限制` SELFDESTRUCT ` 操作码的功能,改变后它只能转移ETH,删除合约的功能只有在同一笔交易中创建和销毁合约时才会生效。
2598
2598
@@ -2674,33 +2674,299 @@ contract DeployContract {
2674
2674
##### 测验结果
2675
2675
- 100/100
2676
2676
2677
- #### WTF Academy Solidity 102.26
2677
+ ### 2024.10.25
2678
+ #### WTF Academy Solidity 102.27 ABI编码解码
2679
+ ##### ABI编码
2680
+ ###### ` abi.encode `
2681
+ ` abi.encode ` 可以对多个不同类型的数据进行编码,将其转换成动态大小的字节数组。编码后的数据包含类型信息,符合以太坊ABI标准中规定的编码规则。
2682
+ 适合用于复杂参数的数据传输,特别是涉及多个数据类型的情况。比如需要将数据传递给低级` call ` 函数或者在跨合约调用中传输大量信息时使用。
2678
2683
2679
- ##### 笔记
2684
+ ** 语法**
2685
+ ``` solidity
2686
+ bytes memory encodedData = abi.encode(x1, x2, ..., xn);
2687
+ ```
2688
+ - ` x1 ` , ` x2 ` , ..., ` xn ` :需要编码的数据,可以是多种类型。
2680
2689
2681
- ##### 测验结果
2690
+ ** 示例**
2691
+ ``` solidity
2692
+ pragma solidity ^0.8.0;
2682
2693
2683
- ##### 测验错题
2694
+ contract EncodeExample {
2695
+ function encodeData(uint256 a, string memory b) public pure returns (bytes memory) {
2696
+ return abi.encode(a, b);
2697
+ }
2698
+ }
2699
+ ```
2700
+ 其中,` encodeData ` 将` uint256 ` 和` string ` 类型的数据编码为字节数组。
2684
2701
2685
- ### 2024.10.19
2686
- #### WTF Academy Solidity 102.27
2702
+ ###### ` abi.encodePacked `
2703
+ ` abi.encodePacked ` 会将多个输入紧凑编码成最小字节大小,没有填充或分隔信息,因此比` abi.encode ` 生成的字节数少。
2704
+ 适合用于节省空间的编码方式,通常用于哈希计算(例如生成` keccak256 ` 哈希)。由于紧凑编码可能导致哈希碰撞,` abi.encodePacked ` 不适合对具有可变长度的数据进行编码。
2687
2705
2688
- ##### 笔记
2706
+ ** 语法**
2707
+ ``` solidity
2708
+ bytes memory packedData = abi.encodePacked(x1, x2, ..., xn);
2709
+
2710
+ ```
2711
+
2712
+ ** 示例**
2713
+ ``` solidity
2714
+ pragma solidity ^0.8.0;
2715
+
2716
+ contract PackedExample {
2717
+ function hashData(uint256 a, string memory b) public pure returns (bytes32) {
2718
+ return keccak256(abi.encodePacked(a, b));
2719
+ }
2720
+ }
2721
+
2722
+ ```
2723
+ 在此示例中,` hashData ` 使用` abi.encodePacked ` 对数据进行紧凑编码,并计算出一个` keccak256 ` 哈希值。
2724
+
2725
+ ###### ` abi.encodeWithSignature `
2726
+ ` abi.encodeWithSignature ` 接受一个函数签名字符串,并使用该签名和参数进行编码。签名包括函数名称和参数类型,生成的数据包含函数选择器。
2727
+ 适用于动态调用未知合约的函数或通过低级` call ` 方法直接调用其他合约。
2728
+
2729
+ ** 语法**
2730
+ ``` solidity
2731
+ bytes memory encodedWithSig = abi.encodeWithSignature("functionName(type1,type2,...)", x1, x2, ...);
2732
+
2733
+ ```
2734
+ - ` functionName ` :目标函数的名称。
2735
+ - ` type1 ` , ` type2 ` , ...:参数的类型,必须按照目标函数参数顺序书写。
2736
+ - ` x1 ` , ` x2 ` , ...:传入的实际参数。
2737
+
2738
+
2739
+ ** 示例**
2740
+
2741
+ ``` solidity
2742
+ pragma solidity ^0.8.0;
2743
+
2744
+ contract SignatureExample {
2745
+ function callFunctionWithSignature(address target, uint256 a, string memory b) public returns (bytes memory) {
2746
+ (bool success, bytes memory data) = target.call(abi.encodeWithSignature("targetFunction(uint256,string)", a, b));
2747
+ require(success, "Call failed");
2748
+ return data;
2749
+ }
2750
+ }
2751
+ ```
2752
+ 在此示例中,` callFunctionWithSignature ` 通过指定的函数签名编码数据,然后使用` call ` 来调用目标合约的函数。
2753
+
2754
+
2755
+ ###### ` abi.encodeWithSelector `
2756
+
2757
+ ` abi.encodeWithSelector ` 接受一个函数选择器和参数列表进行编码。选择器是由函数签名的前四个字节组成。
2758
+ 适用于在已知选择器情况下的合约调用,例如调用其他合约中的具体函数,适合低级` call ` 方法。
2759
+
2760
+ ** 语法**
2761
+ ``` solidity
2762
+ bytes memory encodedWithSelector = abi.encodeWithSelector(bytes4(keccak256("functionName(type1,type2,...)")), x1, x2, ...);
2763
+
2764
+ ```
2765
+ ** 示例**
2766
+ ``` solidity
2767
+ pragma solidity ^0.8.0;
2768
+
2769
+ contract SelectorExample {
2770
+ function callFunctionWithSelector(address target, uint256 a, string memory b) public returns (bytes memory) {
2771
+ (bool success, bytes memory data) = target.call(abi.encodeWithSelector(bytes4(keccak256("targetFunction(uint256,string)")), a, b));
2772
+ require(success, "Call failed");
2773
+ return data;
2774
+ }
2775
+ }
2776
+ ```
2777
+
2778
+ 这里的` callFunctionWithSelector ` 通过函数选择器编码函数调用参数并调用目标合约的函数。
2779
+
2780
+ ##### ABI解码
2781
+ ###### ` abi.decode `
2782
+ ` abi.decode ` 用于解码编码数据,将` abi.encode ` 或` abi.encodePacked ` 生成的字节数据还原为原始数据类型。由于编码后不包含类型信息,解码时必须提供明确的类型。
2783
+ 用于接收或处理低级调用时的返回数据,将字节数据还原为具体类型。
2784
+
2785
+ ** 语法**
2786
+
2787
+ ``` solidity
2788
+ (T1, T2, ..., Tn) = abi.decode(data, (T1, T2, ..., Tn));
2789
+
2790
+ ```
2791
+
2792
+ - ` data ` :要解码的字节数据。
2793
+ - ` (T1, T2, ..., Tn) ` :返回的类型。
2794
+
2795
+
2796
+ ** 示例**
2797
+
2798
+ ``` solidity
2799
+ pragma solidity ^0.8.0;
2800
+
2801
+ contract DecodeExample {
2802
+ function decodeData(bytes memory data) public pure returns (uint256, string memory) {
2803
+ return abi.decode(data, (uint256, string));
2804
+ }
2805
+ }
2806
+
2807
+ ```
2808
+
2809
+ 在这里,` decodeData ` 可以解码编码后的` uint256 ` 和` string ` 数据并将其还原为实际的类型和值。
2810
+
2811
+ ##### 总结表格
2812
+ | 编码函数 | 核心功能 | 主要特点 | 使用场景 |
2813
+ | --- | --- | --- | --- |
2814
+ | ` abi.encode ` | 标准编码 | 保持原始数据的完整类型信息 | 复杂的参数传递,跨合约调用 |
2815
+ | ` abi.encodePacked ` | 紧凑编码 | 更少的字节长度,去除填充 | 哈希计算(警惕碰撞风险) |
2816
+ | ` abi.encodeWithSignature ` | 带函数签名的编码 | 包含函数选择器 | 直接调用其他合约的已知函数 |
2817
+ | ` abi.encodeWithSelector ` | 带选择器的编码 | 直接使用选择器 | 在已知选择器的情况下调用函数 |
2818
+ | ` abi.decode ` | 解码 | 将字节数据转换回原始类型 | 解码低级调用返回的数据 |
2689
2819
2690
2820
##### 测验结果
2821
+ - 50/100
2822
+ - 100/100
2691
2823
2692
2824
##### 测验错题
2825
+ 1 . 下列有关ABI编码的函数中,返回值不可能当作调用智能合约的数据的是:
2826
+ - ` abi.encodePacked `
2827
+ ` abi.encodePacked ` 通常用于将数据紧凑编码,以节省字节数,比如用于哈希计算。由于` abi.encodePacked ` 去除了填充信息,因此在处理变长数据(例如字符串和字节数组)时可能会导致编码结果不唯一。这种不确定性容易引发哈希碰撞,尤其在涉及不同长度的参数时。
2828
+ 2 . 函数foo在智能合约中定义声明为
2829
+ ``` solidity
2830
+ function foo(uint256 a) public view
2831
+ ```
2832
+ 而字符串"foo(uint256)"的keccak256哈希值为:
2833
+ ```solidity
2834
+ 0x2fbebd3821c4e005fbe0a9002cc1bd25dc266d788dba1dbcb39cc66a07e7b38b
2835
+ ```
2836
+ 那么正确且最省gas的一项是:
2837
+ A. `abi.encodeWithSignature("foo(uint256)", a)`
2838
+ B. `abi.encodeWithSelector("foo(uint256)", a)`
2839
+ C. `abi.encodeWithSelector(bytes(keccak256("foo(uint256)")), a)`
2840
+ D. `abi.encodeWithSelector(bytes4(0x2fbebd38), a)`
2841
+ - D. `abi.encodeWithSelector(bytes4(0x2fbebd38), a)`
2842
+ 1. A. `abi.encodeWithSignature("foo(uint256)", a)` 正确但会计算签名的`keccak256`哈希值,会增加少量的gas消耗,不是最节省的方式。
2843
+ 2. B,C语法错误
2844
+ 3. 这是最简洁且节省gas的写法,直接使用已知的`keccak256`哈希前4字节(函数选择器),避免了不必要的计算。
2693
2845
2694
- ### 2024.10.20
2695
- #### WTF Academy Solidity 102.28
2846
+ #### WTF Academy Solidity 102.28 `Hash`
2847
+ 哈希函数(hash function)是一个密码学概念,它可以将任意长度的消息转换为一个固定长度的值,这个值也称作哈希(hash)。哈希函数有很多,其中的重要特性:
2848
+ - 单向性:从输入的消息到它的哈希的正向运算简单且唯一确定,而反过来非常难,只能靠暴力枚举。
2849
+ - 灵敏性:输入的消息改变一点对它的哈希改变很大。
2850
+ - 高效性:从输入的消息到哈希的运算高效。
2851
+ - 均一性:每个哈希值被取到的概率应该基本相等。
2852
+ - 抗碰撞性:
2853
+ - 弱抗碰撞性:给定一个消息`x`,找到另一个消息`x'`,使得`hash(x) = hash(x')`是困难的。
2854
+ - 强抗碰撞性:找到任意`x`和`x'`,使得`hash(x) = hash(x')`是困难的。
2696
2855
2697
- ##### 笔记
2856
+ ##### Keccak256
2857
+
2858
+ `Keccak256`函数是`Solidity`中最常用的哈希函数:
2859
+
2860
+ ```solidity
2861
+ 哈希 = keccak256(数据);
2862
+ ```
2863
+ ** 生成数据唯一标识**
2864
+
2865
+ 可以利用` keccak256 ` 来生成** 一些数据** 的唯一标识。
2866
+ 比如我们有几个不同类型的数据:` uint ` ,` string ` ,` address ` ,我们可以先用` abi.encodePacked ` 方法将他们打包编码,然后再用` keccak256 ` 来生成唯一标识:
2867
+
2868
+ ``` solidity
2869
+ function hash(
2870
+ uint _num,
2871
+ string memory _string,
2872
+ address _addr
2873
+ ) public pure returns (bytes32) {
2874
+ return keccak256(abi.encodePacked(_num, _string, _addr));
2875
+ }
2876
+ ```
2698
2877
2699
2878
##### 测验结果
2879
+ - 100/100
2700
2880
2701
- ##### 测验错题
2881
+ #### WTF Academy Solidity 102.29 函数选择器` Selector `
2882
+ ##### 函数选择器(Selector)
2883
+ ** 函数选择器(Selector)** 是一个4字节的哈希值,用于唯一标识合约中的某个函数。它通过对函数签名计算哈希后取前 4 个字节生成。它是 Solidity 中的一种标识方式,用于唯一标识智能合约中的某个函数。
2884
+
2885
+ ** 语法**
2886
+ - 通过` keccak256 ` 哈希算法对函数签名进行哈希计算,然后取前 4 个字节:
2887
+
2888
+ ``` solidity
2889
+ bytes4 selector = bytes4(keccak256("mint(address)"));
2890
+
2891
+ ```
2892
+ ##### `msg.data`
2893
+ **`msg.data`** 是 Solidity 中的一个全局变量,它表示调用合约时传入的完整 `calldata`。这个数据包含了:
2894
+ - **前 4 个字节**:函数选择器,用于告诉 EVM 要调用的目标函数。
2895
+ - **后续字节**:编码后的参数数据。
2896
+
2897
+ 假设合约中有一个函数 `mint(address to)`,调用 `mint(0x2c44b726ADF1963cA47Af88B284C06f30380fC78)` 时,生成的`msg.data`为:
2898
+
2899
+ ```solidity
2900
+ 0x6a6278420000000000000000000000002c44b726adf1963ca47af88b284c06f30380fc78
2901
+ ```
2902
+ - 前 4 字节 ` 0x6a627842 ` 是函数选择器;
2903
+ - 后面 32 字节 ` 0x0000000000000000000000002c44b726adf1963ca47af88b284c06f30380fc78 ` 是参数地址。
2904
+
2905
+ ##### 函数签名
2906
+ ** 语法**
2907
+ ` 函数名(参数类型1,参数类型2,...) ` (例如 ` mint(address) ` )。
2908
+
2909
+ ##### 不同类型参数的函数选择器
2910
+ - ** 基础类型参数** :如` uint256 ` 、` bool ` 、` address ` 等。
2911
+
2912
+ ``` solidity
2913
+ function elementaryParamSelector(uint256 param1, bool param2) external returns(bytes4 selectorWithElementaryParam){
2914
+ return bytes4(keccak256("elementaryParamSelector(uint256,bool)"));
2915
+ }
2916
+ ```
2917
+
2918
+ - **固定长度类型参数**:如`uint256[3]`。
2919
+
2920
+ ```solidity
2921
+ function fixedSizeParamSelector(uint256[3] memory param1) external returns(bytes4 selectorWithFixedSizeParam){
2922
+ return bytes4(keccak256("fixedSizeParamSelector(uint256[3])"));
2923
+ }
2924
+ ```
2925
+
2926
+ - **可变长度类型参数**:如`address[]`、`string`。
2927
+
2928
+ ```solidity
2929
+ function nonFixedSizeParamSelector(uint256[] memory param1, string memory param2) external returns(bytes4 selectorWithNonFixedSizeParam){
2930
+ return bytes4(keccak256("nonFixedSizeParamSelector(uint256[],string)"));
2931
+ }
2932
+ ```
2933
+
2934
+ - **映射类型参数**:如合约、枚举、结构体等。
2935
+
2936
+ ```solidity
2937
+ function mappingParamSelector(address demo, (uint256, bytes) memory user, uint256[] memory count, uint8 mySchool) external returns(bytes4 selectorWithMappingParam){
2938
+ return bytes4(keccak256("mappingParamSelector(address,(uint256,bytes),uint256[],uint8)"));
2939
+ }
2940
+ ```
2941
+
2942
+ ##### 使用函数选择器调用函数
2943
+ 可以使用 `abi.encodeWithSelector` 或 `abi.encodeWithSignature` 编码函数选择器和参数,然后通过 `call` 执行目标函数。
2944
+
2945
+ ###### 使用`abi.encodeWithSelector`调用函数
2946
+
2947
+ ```solidity
2948
+ function callWithSelector() external {
2949
+ bytes4 selector = bytes4(keccak256("elementaryParamSelector(uint256,bool)"));
2950
+ (bool success, ) = address(this).call(abi.encodeWithSelector(selector, 1, true));
2951
+ require(success, "Call failed");
2952
+ }
2953
+ ```
2954
+
2955
+ ###### 使用` abi.encodeWithSignature ` 调用函数
2956
+
2957
+ ` abi.encodeWithSignature ` 可以直接编码函数签名,无需手动生成选择器:
2958
+
2959
+ ``` solidity
2960
+ function callWithSignature() external {
2961
+ (bool success, ) = address(this).call(abi.encodeWithSignature("elementaryParamSelector(uint256,bool)", 1, true));
2962
+ require(success, "Call failed");
2963
+ }
2964
+ ```
2965
+
2966
+ ##### 测验结果
2967
+ - 100/100
2702
2968
2703
- ### 2024.10.21
2969
+ ### 2024.10.27
2704
2970
#### WTF Academy Solidity 102.29
2705
2971
2706
2972
##### 笔记
0 commit comments