diff --git a/changes.txt b/changes.txt index 36948858..fce862ed 100644 --- a/changes.txt +++ b/changes.txt @@ -23,3 +23,19 @@ docs/ethers-101/01_HelloVitalik/HelloVitalik.js docs/ethers-101/02_Provider/Prov Changes in docs/solidity-104: docs/ethers-101/01_HelloVitalik/HelloVitalik.js docs/ethers-101/02_Provider/Provider.js docs/ethers-101/03_ReadContract/ReadContract.js docs/ethers-101/04_SendETH/SendETH.js docs/ethers-101/05_WriteContract/WriteContract.js docs/ethers-101/06_DeployContract/DeployContract.js docs/ethers-101/07_Event/Event.js docs/ethers-101/08_ContractListener/ContractListener.js docs/ethers-101/09_EventFilter/EventFilter.js docs/ethers-101/10_Units/Units.js docs/ethers-102/11_StaticCall/readme.md docs/ethers-102/11_StaticCall/staticCall.js docs/ethers-102/12_ERC721Check/ERC721Check.js docs/ethers-102/12_ERC721Check/readme.md docs/ethers-102/13_EncodeCalldata/EncodeCalldata.js docs/ethers-102/14_HDwallet/HDwallet.js docs/ethers-102/14_HDwallet/readme.md docs/ethers-102/15_MultiTransfer/MultiTransfer.js docs/ethers-102/15_MultiTransfer/readme.md docs/ethers-102/16_MultiCollect/MultiCollect.js docs/ethers-102/16_MultiCollect/readme.md docs/ethers-102/17_MerkleTree/MerkleTree.js docs/ethers-102/18_Signature/Signature.js docs/ethers-102/18_Signature/readme.md docs/ethers-102/19_Mempool/Mempool.js docs/ethers-102/19_Mempool/readme.md docs/ethers-102/20_DecodeTx/DecodeTx.js docs/ethers-102/20_DecodeTx/img/20-3.png docs/ethers-102/20_DecodeTx/img/20-4.png docs/ethers-102/20_DecodeTx/readme.md docs/ethers-102/21_VanityAddress/readme.md docs/ethers-102/23_Frontrun/Frontrun.sol docs/ethers-102/23_Frontrun/frontrun.js docs/ethers-102/23_Frontrun/img/23-2.png docs/ethers-102/23_Frontrun/img/23-3.png docs/ethers-102/23_Frontrun/readme.md docs/ethers-102/24_ERC20Check/readme.md docs/ethers-102/25_Flashbots/Flashbots.js docs/ethers-102/25_Flashbots/img/25-4.png docs/ethers-102/25_Flashbots/img/25-5.png docs/ethers-102/25_Flashbots/readme.md docs/solidity-101/01_HelloWeb3/HelloWeb3.sol docs/solidity-101/01_HelloWeb3/img/1-2.png docs/solidity-101/01_HelloWeb3/readme.md docs/solidity-101/02_ValueTypes/ValueTypes.sol docs/solidity-101/02_ValueTypes/readme.md docs/solidity-101/03_Function/Function.sol docs/solidity-101/03_Function/readme.md docs/solidity-101/04_Return/Return.sol docs/solidity-101/04_Return/readme.md docs/solidity-101/05_DataStorage/DataStorage.sol docs/solidity-101/05_DataStorage/readme.md docs/solidity-101/06_ArrayAndStruct/ArrayAndStruct.sol docs/solidity-101/06_ArrayAndStruct/readme.md docs/solidity-101/07_Mapping/Mapping.sol docs/solidity-101/07_Mapping/readme.md docs/solidity-101/08_InitialValue/InitialValue.sol docs/solidity-101/08_InitialValue/readme.md docs/solidity-101/09_Constant/Constant.sol docs/solidity-101/09_Constant/readme.md docs/solidity-101/10_InsertionSort/InsertionSort.sol docs/solidity-101/10_InsertionSort/readme.md docs/solidity-101/11_Modifier/Owner.sol docs/solidity-101/11_Modifier/img/11-1.jpg docs/solidity-101/11_Modifier/img/11-2.jpg docs/solidity-101/11_Modifier/img/11-3.jpg docs/solidity-101/11_Modifier/readme.md docs/solidity-101/12_Event/Event.sol docs/solidity-101/12_Event/readme.md docs/solidity-101/13_Inheritance/Inheritance.sol docs/solidity-101/13_Inheritance/ModifierInheritance.sol docs/solidity-101/13_Inheritance/readme.md docs/solidity-101/14_Interface/Interface.sol docs/solidity-101/14_Interface/img/14-2.png docs/solidity-101/14_Interface/readme.md docs/solidity-101/15_Errors/Error.sol docs/solidity-101/15_Errors/readme.md docs/solidity-102/16_Overloading/Overloading.sol docs/solidity-102/16_Overloading/readme.md docs/solidity-102/17_Library/Library.sol docs/solidity-102/17_Library/readme.md docs/solidity-102/18_Import/Yeye.sol docs/solidity-102/18_Import/readme.md docs/solidity-102/19_Fallback/Fallback.sol docs/solidity-102/19_Fallback/readme.md docs/solidity-102/20_SendETH/SendETH.sol docs/solidity-102/20_SendETH/readme.md docs/solidity-102/21_CallContract/CallContract.sol docs/solidity-102/21_CallContract/readme.md docs/solidity-102/22_Call/Call.sol docs/solidity-102/22_Call/readme.md docs/solidity-102/23_Delegatecall/Delegatecall.sol docs/solidity-102/23_Delegatecall/readme.md docs/solidity-102/24_Create/Create.sol docs/solidity-102/24_Create/readme.md docs/solidity-102/25_Create2/create2test.js docs/solidity-102/25_Create2/readme.md docs/solidity-102/26_DeleteContract/DeleteContract.sol docs/solidity-102/26_DeleteContract/img/26-1.png docs/solidity-102/26_DeleteContract/img/26-2.png docs/solidity-102/26_DeleteContract/readme.md docs/solidity-102/27_ABIEncode/ABIEncode.sol docs/solidity-102/27_ABIEncode/readme.md docs/solidity-102/28_Hash/Hash.sol docs/solidity-102/28_Hash/readme.md docs/solidity-102/29_Selector/Selector.sol docs/solidity-102/29_Selector/img/29-2.png docs/solidity-102/29_Selector/img/29-3.png docs/solidity-102/29_Selector/readme.md docs/solidity-102/30_TryCatch/TryCatch.sol docs/solidity-102/30_TryCatch/readme.md docs/solidity-103/31_ERC20/ERC20.sol docs/solidity-103/31_ERC20/IERC20.sol docs/solidity-103/31_ERC20/readme.md docs/solidity-103/32_Faucet/Faucet.sol docs/solidity-103/32_Faucet/IERC20.sol docs/solidity-103/32_Faucet/readme.md docs/solidity-103/33_Airdrop/Airdrop.sol docs/solidity-103/33_Airdrop/IERC20.sol docs/solidity-103/33_Airdrop/readme.md docs/solidity-103/34_ERC721/ERC721.sol docs/solidity-103/34_ERC721/String.sol docs/solidity-103/34_ERC721/WTFApe.sol docs/solidity-103/34_ERC721/readme.md docs/solidity-103/35_DutchAuction/DutchAuction.sol docs/solidity-103/35_DutchAuction/readme.md docs/solidity-103/36_MerkleTree/MerkleTree.sol docs/solidity-103/36_MerkleTree/readme.md docs/solidity-103/37_Signature/Signature.sol docs/solidity-103/37_Signature/readme.md docs/solidity-103/38_NFTSwap/NFTSwap.sol docs/solidity-103/38_NFTSwap/readme.md docs/solidity-103/39_Random/Random.sol docs/solidity-103/39_Random/RandomNumberConsumer.sol docs/solidity-103/39_Random/img/39-2.png docs/solidity-103/39_Random/img/39-3.png docs/solidity-103/39_Random/img/39-4.png docs/solidity-103/39_Random/img/39-5.png docs/solidity-103/39_Random/img/39-6.png docs/solidity-103/39_Random/img/39-7.png docs/solidity-103/39_Random/readme.md docs/solidity-103/40_ERC1155/BAYC1155.sol docs/solidity-103/40_ERC1155/readme.md docs/solidity-103/41_WETH/readme.md docs/solidity-103/42_PaymentSplit/PaymentSplit.sol docs/solidity-103/42_PaymentSplit/readme.md docs/solidity-103/43_TokenVesting/readme.md docs/solidity-103/44_TokenLocker/readme.md docs/solidity-103/45_Timelock/Timelock.sol docs/solidity-103/45_Timelock/readme.md docs/solidity-103/46_ProxyContract/ProxyContract.sol docs/solidity-103/46_ProxyContract/readme.md docs/solidity-103/47_Upgrade/Upgrade.sol docs/solidity-103/47_Upgrade/readme.md docs/solidity-103/48_TransparentProxy/TransparentProxy.sol docs/solidity-103/48_TransparentProxy/readme.md docs/solidity-103/49_UUPS/UUPS.sol docs/solidity-103/49_UUPS/readme.md docs/solidity-103/50_MultisigWallet/MultisigWallet.sol docs/solidity-103/50_MultisigWallet/readme.md docs/solidity-104/S01_ReentrancyAttack/ReentrancyAttack.sol docs/solidity-104/S01_ReentrancyAttack/readme.md docs/solidity-104/S02_SelectorClash/SelectorClash.sol docs/solidity-104/S02_SelectorClash/readme.md docs/solidity-104/S03_Centralization/Centralization.sol docs/solidity-104/S03_Centralization/readme.md docs/solidity-104/S04_AccessControlExploit/AccessControlExploit.sol docs/solidity-104/S04_AccessControlExploit/readme.md docs/solidity-104/S05_Overflow/Overflow.sol docs/solidity-104/S05_Overflow/readme.md docs/solidity-104/S06_SignatureReplay/SingatureReplay.sol docs/solidity-104/S06_SignatureReplay/readme.md docs/solidity-104/S07_BadRandomness/BadRandomness.sol docs/solidity-104/S07_BadRandomness/readme.md docs/solidity-104/S08_ContractCheck/ContractCheck.sol docs/solidity-104/S08_ContractCheck/readme.md docs/solidity-104/S09_DoS/DoS.sol docs/solidity-104/S09_DoS/readme.md docs/solidity-104/S10_Honeypot/Honeypot.sol docs/solidity-104/S10_Honeypot/readme.md docs/solidity-104/S11_Frontrun/Frontrun.sol docs/solidity-104/S11_Frontrun/frontrun.js docs/solidity-104/S11_Frontrun/readme.md docs/solidity-104/S12_TxOrigin/readme.md docs/solidity-104/S13_UncheckedCall/UncheckedCall.sol docs/solidity-104/S13_UncheckedCall/readme.md docs/solidity-104/S14_TimeManipulation/readme.md docs/solidity-104/S14_TimeManipulation/src/TimeManipulation.sol docs/solidity-104/S14_TimeManipulation/test/TimeManipulation.t.sol docs/solidity-104/S15_OracleManipulation/readme.md docs/solidity-104/S15_OracleManipulation/src/Oracle.sol docs/solidity-104/S15_OracleManipulation/test/Oracle.t.sol docs/solidity-104/S16_NFTReentrancy/NFTReentrancy.sol docs/solidity-104/S16_NFTReentrancy/readme.md docs/ethers-102/21_VanityAddress/BulkVanityAddress.js docs/solidity-101/05_DataStorage/img/5-5.png docs/solidity-101/05_DataStorage/img/5-6.png docs/solidity-101/06_ArrayAndStruct/img/6-4.png docs/solidity-101/06_ArrayAndStruct/img/6-5.png docs/solidity-101/11_Modifier/img/11-4.jpg docs/solidity-101/12_Event/img/12-4.png docs/solidity-101/13_Inheritance/DiamondInheritance.sol docs/solidity-101/14_Interface/AbstractDemo.sol docs/solidity-101/14_Interface/InterfaceDemo.sol docs/solidity-102/18_Import/Import.sol docs/solidity-102/25_Create2/Create2.sol docs/solidity-102/26_DeleteContract/DeployContract.sol docs/solidity-102/26_DeleteContract/img/26-3.png docs/solidity-102/26_DeleteContract/img/26-4.png docs/solidity-102/26_DeleteContract/img/26-5.png docs/solidity-102/26_DeleteContract/img/26-6.png docs/solidity-103/39_Random/img/39-10.png docs/solidity-103/39_Random/img/39-6-1.png docs/solidity-103/39_Random/img/39-8.png docs/solidity-103/39_Random/img/39-9.png docs/solidity-103/51_ERC4626/ docs/solidity-103/52_EIP712/ docs/solidity-103/53_ERC20Permit/ docs/solidity-103/54_CrossChainBridge/ docs/solidity-103/55_MultiCall/ docs/solidity-103/56_DEX/ docs/solidity-103/57_Flashloan/ docs/solidity-104/S17_CrossReentrancy/ +-------------------------------- +Sync started at Sun Nov 10 01:09:30 UTC 2024 + + +Changes in docs/solidity-101: +docs/solidity-101/02_ValueTypes/readme.md docs/solidity-101/03_Function/readme.md docs/solidity-101/04_Return/readme.md docs/solidity-101/05_DataStorage/readme.md docs/solidity-101/07_Mapping/readme.md docs/solidity-101/09_Constant/readme.md docs/solidity-101/10_InsertionSort/readme.md docs/solidity-101/12_Event/readme.md docs/solidity-101/13_Inheritance/Inheritance.sol docs/solidity-101/13_Inheritance/readme.md + +Changes in docs/solidity-102: +docs/solidity-101/02_ValueTypes/readme.md docs/solidity-101/03_Function/readme.md docs/solidity-101/04_Return/readme.md docs/solidity-101/05_DataStorage/readme.md docs/solidity-101/07_Mapping/readme.md docs/solidity-101/09_Constant/readme.md docs/solidity-101/10_InsertionSort/readme.md docs/solidity-101/12_Event/readme.md docs/solidity-101/13_Inheritance/Inheritance.sol docs/solidity-101/13_Inheritance/readme.md docs/solidity-102/17_Library/readme.md docs/solidity-102/23_Delegatecall/readme.md docs/solidity-102/24_Create/readme.md docs/solidity-102/26_DeleteContract/readme.md docs/solidity-102/27_ABIEncode/readme.md docs/solidity-102/30_TryCatch/readme.md docs/solidity-102/24_Create/img/24-4.png + +Changes in docs/solidity-103: +docs/solidity-101/02_ValueTypes/readme.md docs/solidity-101/03_Function/readme.md docs/solidity-101/04_Return/readme.md docs/solidity-101/05_DataStorage/readme.md docs/solidity-101/07_Mapping/readme.md docs/solidity-101/09_Constant/readme.md docs/solidity-101/10_InsertionSort/readme.md docs/solidity-101/12_Event/readme.md docs/solidity-101/13_Inheritance/Inheritance.sol docs/solidity-101/13_Inheritance/readme.md docs/solidity-102/17_Library/readme.md docs/solidity-102/23_Delegatecall/readme.md docs/solidity-102/24_Create/readme.md docs/solidity-102/26_DeleteContract/readme.md docs/solidity-102/27_ABIEncode/readme.md docs/solidity-102/30_TryCatch/readme.md docs/solidity-103/34_ERC721/ERC721.sol docs/solidity-103/34_ERC721/readme.md docs/solidity-103/35_DutchAuction/readme.md docs/solidity-103/36_MerkleTree/img/36-1.png docs/solidity-103/36_MerkleTree/img/36-2.png docs/solidity-103/36_MerkleTree/readme.md docs/solidity-103/37_Signature/readme.md docs/solidity-103/38_NFTSwap/NFTSwap.sol docs/solidity-103/38_NFTSwap/readme.md docs/solidity-103/40_ERC1155/ERC1155.sol docs/solidity-103/40_ERC1155/readme.md docs/solidity-103/46_ProxyContract/readme.md docs/solidity-103/47_Upgrade/readme.md docs/solidity-103/48_TransparentProxy/readme.md docs/solidity-103/49_UUPS/readme.md docs/solidity-103/50_MultisigWallet/MultisigWallet.sol docs/solidity-103/50_MultisigWallet/readme.md docs/solidity-103/51_ERC4626/readme.md docs/solidity-103/52_EIP712/readme.md docs/solidity-103/53_ERC20Permit/readme.md docs/solidity-103/54_CrossChainBridge/crosschain.js docs/solidity-103/54_CrossChainBridge/readme.md docs/solidity-103/56_DEX/readme.md docs/solidity-102/24_Create/img/24-4.png docs/solidity-103/56_DEX/img/56-10.jpg docs/solidity-103/56_DEX/img/56-11.jpg docs/solidity-103/56_DEX/img/56-2.jpg docs/solidity-103/56_DEX/img/56-3.jpg docs/solidity-103/56_DEX/img/56-4.jpg docs/solidity-103/56_DEX/img/56-5.jpg docs/solidity-103/56_DEX/img/56-6.jpg docs/solidity-103/56_DEX/img/56-7.jpg docs/solidity-103/56_DEX/img/56-8.jpg docs/solidity-103/56_DEX/img/56-9.jpg + +Changes in docs/solidity-104: +docs/solidity-101/02_ValueTypes/readme.md docs/solidity-101/03_Function/readme.md docs/solidity-101/04_Return/readme.md docs/solidity-101/05_DataStorage/readme.md docs/solidity-101/07_Mapping/readme.md docs/solidity-101/09_Constant/readme.md docs/solidity-101/10_InsertionSort/readme.md docs/solidity-101/12_Event/readme.md docs/solidity-101/13_Inheritance/Inheritance.sol docs/solidity-101/13_Inheritance/readme.md docs/solidity-102/17_Library/readme.md docs/solidity-102/23_Delegatecall/readme.md docs/solidity-102/24_Create/readme.md docs/solidity-102/26_DeleteContract/readme.md docs/solidity-102/27_ABIEncode/readme.md docs/solidity-102/30_TryCatch/readme.md docs/solidity-103/34_ERC721/ERC721.sol docs/solidity-103/34_ERC721/readme.md docs/solidity-103/35_DutchAuction/readme.md docs/solidity-103/36_MerkleTree/img/36-1.png docs/solidity-103/36_MerkleTree/img/36-2.png docs/solidity-103/36_MerkleTree/readme.md docs/solidity-103/37_Signature/readme.md docs/solidity-103/38_NFTSwap/NFTSwap.sol docs/solidity-103/38_NFTSwap/readme.md docs/solidity-103/40_ERC1155/ERC1155.sol docs/solidity-103/40_ERC1155/readme.md docs/solidity-103/46_ProxyContract/readme.md docs/solidity-103/47_Upgrade/readme.md docs/solidity-103/48_TransparentProxy/readme.md docs/solidity-103/49_UUPS/readme.md docs/solidity-103/50_MultisigWallet/MultisigWallet.sol docs/solidity-103/50_MultisigWallet/readme.md docs/solidity-103/51_ERC4626/readme.md docs/solidity-103/52_EIP712/readme.md docs/solidity-103/53_ERC20Permit/readme.md docs/solidity-103/54_CrossChainBridge/crosschain.js docs/solidity-103/54_CrossChainBridge/readme.md docs/solidity-103/56_DEX/readme.md docs/solidity-104/S01_ReentrancyAttack/readme.md docs/solidity-104/S02_SelectorClash/SelectorClash.sol docs/solidity-104/S05_Overflow/readme.md docs/solidity-104/S06_SignatureReplay/readme.md docs/solidity-104/S08_ContractCheck/readme.md docs/solidity-104/S09_DoS/readme.md docs/solidity-104/S10_Honeypot/readme.md docs/solidity-104/S13_UncheckedCall/readme.md docs/solidity-104/S15_OracleManipulation/readme.md docs/solidity-104/S15_OracleManipulation/test/Oracle.t.sol docs/solidity-102/24_Create/img/24-4.png docs/solidity-103/56_DEX/img/56-10.jpg docs/solidity-103/56_DEX/img/56-11.jpg docs/solidity-103/56_DEX/img/56-2.jpg docs/solidity-103/56_DEX/img/56-3.jpg docs/solidity-103/56_DEX/img/56-4.jpg docs/solidity-103/56_DEX/img/56-5.jpg docs/solidity-103/56_DEX/img/56-6.jpg docs/solidity-103/56_DEX/img/56-7.jpg docs/solidity-103/56_DEX/img/56-8.jpg docs/solidity-103/56_DEX/img/56-9.jpg + diff --git a/docs/solidity-101/02_ValueTypes/readme.md b/docs/solidity-101/02_ValueTypes/readme.md index 79830557..241c7d5b 100755 --- a/docs/solidity-101/02_ValueTypes/readme.md +++ b/docs/solidity-101/02_ValueTypes/readme.md @@ -70,14 +70,14 @@ bool public _bool5 = _bool != _bool1; // 不相等 ```solidity // 整型 int public _int = -1; // 整数,包括负数 -uint public _uint = 1; // 正整数 -uint256 public _number = 20220330; // 256位正整数 +uint public _uint = 1; // 无符号整数 +uint256 public _number = 20220330; // 256位无符号整数 ``` 常用的整型运算符包括: - 比较运算符(返回布尔值): `<=`, `<`,`==`, `!=`, `>=`, `>` -- 算数运算符: `+`, `-`, `*`, `/`, `%`(取余),`**`(幂) +- 算术运算符: `+`, `-`, `*`, `/`, `%`(取余),`**`(幂) ```solidity // 整数运算 @@ -119,7 +119,7 @@ bytes32 public _byte32 = "MiniSolidity"; bytes1 public _byte = _byte32[0]; ``` -在上述代码中,`MiniSolidity` 变量以字节的方式存储进变量 `_byte32`。如果把它转换成 `16 进制`,就是:`0x4d696e69536f6c69646974790000000000000000000000000000000000000000` +在上述代码中,字符串 `MiniSolidity` 以字节的方式存储进变量 `_byte32`。如果把它转换成 `16 进制`,就是:`0x4d696e69536f6c69646974790000000000000000000000000000000000000000` `_byte` 变量的值为 `_byte32` 的第一个字节,即 `0x4d`。 @@ -134,7 +134,7 @@ enum ActionSet { Buy, Hold, Sell } ActionSet action = ActionSet.Buy; ``` -枚举可以显式地和 `uint` 相互转换,并会检查转换的正整数是否在枚举的长度内,否则会报错: +枚举可以显式地和 `uint` 相互转换,并会检查转换的无符号整数是否在枚举的长度内,否则会报错: ```solidity // enum可以和uint显式的转换 @@ -143,7 +143,7 @@ function enumToUint() external view returns(uint){ } ``` -`enum` 是一个比较冷门的变量,几乎没什么人用。 +`enum` 是一个比较冷门的数据类型,几乎没什么人用。 ## 在 Remix 上运行 diff --git a/docs/solidity-101/03_Function/readme.md b/docs/solidity-101/03_Function/readme.md index 17981a3f..60249533 100755 --- a/docs/solidity-101/03_Function/readme.md +++ b/docs/solidity-101/03_Function/readme.md @@ -27,7 +27,8 @@ Solidity语言的函数非常灵活,可以进行各种复杂操作。在本教 我们先看一下 Solidity 中函数的形式: ```solidity -function () {internal|external|public|private} [pure|view|payable] [returns ()] +function ([parameter types[, ...]]) {internal|external|public|private} [pure|view|payable] [virtual|override] [] +[returns ()]{ } ``` 看着有一些复杂,让我们从前往后逐个解释(方括号中的是可写可不 @@ -37,7 +38,7 @@ function () {internal|external|public|private} [ 2. ``:函数名。 -3. `()`:圆括号内写入函数的参数,即输入到函数的变量类型和名称。 +3. `([parameter types[, ...]])`:圆括号内写入函数的参数,即输入到函数的变量类型和名称。 4. `{internal|external|public|private}`:函数可见性说明符,共有4种。 @@ -48,11 +49,17 @@ function () {internal|external|public|private} [ **注意 1**:合约中定义的函数需要明确指定可见性,它们没有默认值。 - **注意 2**:`public|private|internal` 也可用于修饰状态变量。`public`变量会自动生成同名的`getter`函数,用于查询数值。未标明可见性类型的状态变量,默认为`internal`。 + **注意 2**:`public|private|internal` 也可用于修饰状态变量(定义可参考[WTF Solidity 第5讲的相关内容]([../05_DataStorage/readme.md#1-状态变量](https://github.com/AmazingAng/WTF-Solidity/tree/main/05_DataStorage#1-%E7%8A%B6%E6%80%81%E5%8F%98%E9%87%8F)))。`public`变量会自动生成同名的`getter`函数,用于查询数值。未标明可见性类型的状态变量,默认为`internal`。 5. `[pure|view|payable]`:决定函数权限/功能的关键字。`payable`(可支付的)很好理解,带着它的函数,运行的时候可以给合约转入 ETH。`pure` 和 `view` 的介绍见下一节。 -6. `[returns ()]`:函数返回的变量类型和名称。 +6. `[virtual|override]`: 方法是否可以被重写,或者是否是重写方法。`virtual`用在父合约上,标识的方法可以被子合约重写。`override`用在自合约上,表名方法重写了父合约的方法。 + +7. ``: 自定义的修饰器,可以有0个或多个修饰器。 + +8. `[returns ()]`:函数返回的变量类型和名称。 + +9. ``: 函数体。 ## 到底什么是 `Pure` 和`View`? diff --git a/docs/solidity-101/04_Return/readme.md b/docs/solidity-101/04_Return/readme.md index dea92a30..79273459 100755 --- a/docs/solidity-101/04_Return/readme.md +++ b/docs/solidity-101/04_Return/readme.md @@ -43,7 +43,7 @@ function returnMultiple() public pure returns(uint256, bool, uint256[3] memory){ ## 命名式返回 -我们可以在 `returns` 中标明返回变量的名称。Solidity 会初始化这些变量,并且自动返回这些函数的值,无需使用 `return`。 +我们可以在 `returns` 中标明返回变量的名称。Solidity 会初始化这些变量,并且自动返回这些变量的值,无需使用 `return`。 ```solidity // 命名式返回 diff --git a/docs/solidity-101/05_DataStorage/readme.md b/docs/solidity-101/05_DataStorage/readme.md index 270301c1..9cb0b110 100755 --- a/docs/solidity-101/05_DataStorage/readme.md +++ b/docs/solidity-101/05_DataStorage/readme.md @@ -27,7 +27,7 @@ tags: ## 数据位置 -Solidity数据存储位置有三类:`storage`,`memory`和`calldata`。不同存储位置的`gas`成本不同。`storage`类型的数据存在链上,类似计算机的硬盘,消耗`gas`多;`memory`和`calldata`类型的临时存在内存里,消耗`gas`少。大致用法: +Solidity数据存储位置有三类:`storage`,`memory`和`calldata`。不同存储位置的`gas`成本不同。`storage`类型的数据存在链上,类似计算机的硬盘,消耗`gas`多;`memory`和`calldata`类型的临时存在内存里,消耗`gas`少。整体消耗`gas`从多到少依次为:`storage` > `memory` > `calldata`。大致用法: 1. `storage`:合约里的状态变量默认都是`storage`,存储在链上。 @@ -70,7 +70,7 @@ function fCalldata(uint[] calldata _x) public pure returns(uint[] calldata){ ![5-2.png](./img/5-2.png) - `memory`赋值给`memory`,会创建引用,改变新变量会影响原变量。 -- 其他情况下,赋值创建的是本体的副本,即对二者之一的修改,并不会同步到另一方 +- 其他情况下,赋值创建的是本体的副本,即对二者之一的修改,并不会同步到另一方。这有时会涉及到开发中的问题,比如从`storage`中读取数据,赋值给`memory`,然后修改`memory`的数据,但如果没有将`memory`的数据赋值回`storage`,那么`storage`的数据是不会改变的。 ## 变量的作用域 @@ -127,7 +127,7 @@ function global() external view returns(address, uint, bytes memory){ 在上面例子里,我们使用了3个常用的全局变量:`msg.sender`,`block.number`和`msg.data`,他们分别代表请求发起地址,当前区块高度,和请求数据。下面是一些常用的全局变量,更完整的列表请看这个[链接](https://learnblockchain.cn/docs/solidity/units-and-global-variables.html#special-variables-and-functions): -- `blockhash(uint blockNumber)`: (`bytes32`) 给定区块的哈希值 – 只适用于256最近区块, 不包含当前区块。 +- `blockhash(uint blockNumber)`: (`bytes32`) 给定区块的哈希值 – 只适用于最近的256个区块, 不包含当前区块。 - `block.coinbase`: (`address payable`) 当前区块矿工的地址 - `block.gaslimit`: (`uint`) 当前区块的gaslimit - `block.number`: (`uint`) 当前区块的number diff --git a/docs/solidity-101/07_Mapping/readme.md b/docs/solidity-101/07_Mapping/readme.md index 3e700cb9..e287d338 100755 --- a/docs/solidity-101/07_Mapping/readme.md +++ b/docs/solidity-101/07_Mapping/readme.md @@ -63,7 +63,7 @@ mapping(address => address) public swapPair; // 币对的映射,地址到地 - **原理1**: 映射不储存任何键(`Key`)的资讯,也没有length的资讯。 -- **原理2**: 映射使用`keccak256(abi.encodePacked(key, slot))`当成offset存取value,其中`slot`是映射变量定义所在的插槽位置。 +- **原理2**: 对于映射使用`keccak256(h(key) . slot)`计算存取value的位置。感兴趣的可以去阅读 [WTF Solidity 内部规则: 映射存储布局](https://github.com/WTFAcademy/WTF-Solidity-Internals/tree/master/tutorials/02_MappingStorage) - **原理3**: 因为Ethereum会定义所有未使用的空间为0,所以未赋值(`Value`)的键(`Key`)初始值都是各个type的默认值,如uint的默认值是0。 diff --git a/docs/solidity-101/09_Constant/readme.md b/docs/solidity-101/09_Constant/readme.md index ed179d2e..28b005c0 100755 --- a/docs/solidity-101/09_Constant/readme.md +++ b/docs/solidity-101/09_Constant/readme.md @@ -40,13 +40,14 @@ address constant CONSTANT_ADDRESS = 0x0000000000000000000000000000000000000000; ### immutable -`immutable`变量可以在声明时或构造函数中初始化,因此更加灵活。在`Solidity v8.0.21`以后,`immutable`变量不需要显式初始化。反之,则需要显式初始化。 +`immutable`变量可以在声明时或构造函数中初始化,因此更加灵活。在`Solidity v0.8.21`以后,`immutable`变量不需要显式初始化,未显式初始化的`immutable`变量将使用数值类型的初始值(见 [8. 变量初始值](https://github.com/AmazingAng/WTF-Solidity/blob/main/08_InitialValue/readme.md#%E5%8F%98%E9%87%8F%E5%88%9D%E5%A7%8B%E5%80%BC))。反之,则需要显式初始化。 若`immutable`变量既在声明时初始化,又在constructor中初始化,会使用constructor初始化的值。 ``` solidity // immutable变量可以在constructor里初始化,之后不能改变 uint256 public immutable IMMUTABLE_NUM = 9999999999; -address public immutable IMMUTABLE_ADDRESS; +// 在`Solidity v8.0.21`以后,下列变量数值暂为初始值 +address public immutable IMMUTABLE_ADDRESS; uint256 public immutable IMMUTABLE_BLOCK; uint256 public immutable IMMUTABLE_TEST; ``` diff --git a/docs/solidity-101/10_InsertionSort/readme.md b/docs/solidity-101/10_InsertionSort/readme.md index dc2f2541..f9e299c3 100755 --- a/docs/solidity-101/10_InsertionSort/readme.md +++ b/docs/solidity-101/10_InsertionSort/readme.md @@ -147,7 +147,7 @@ Remix decoded output 出现错误内容 ### 正确的Solidity插入排序 -花了几个小时,在`Dapp-Learning`社群一个朋友的帮助下,终于找到了`bug`所在。`Solidity`中最常用的变量类型是`uint`,也就是正整数,取到负值的话,会报`underflow`错误。而在插入算法中,变量`j`有可能会取到`-1`,引起报错。 +花了几个小时,在`Dapp-Learning`社群一个朋友的帮助下,终于找到了`bug`所在。`Solidity`中最常用的变量类型是`uint`,也就是无符号整数,取到负值的话,会报`underflow`错误。而在插入算法中,变量`j`有可能会取到`-1`,引起报错。 这里,我们需要把`j`加1,让它无法取到负值。正确代码: diff --git a/docs/solidity-101/12_Event/readme.md b/docs/solidity-101/12_Event/readme.md index 9feb691b..ca39dea6 100755 --- a/docs/solidity-101/12_Event/readme.md +++ b/docs/solidity-101/12_Event/readme.md @@ -82,6 +82,8 @@ keccak256("Transfer(address,address,uint256)") `indexed`标记的参数可以理解为检索事件的索引“键”,方便之后搜索。每个 `indexed` 参数的大小为固定的256比特,如果参数太大了(比如字符串),就会自动计算哈希存储在主题中。 +这里其实会引入一个新的问题,根据Solidity的[官方文档](https://docs.soliditylang.org/en/v0.8.27/abi-spec.html#encoding-of-indexed-event-parameters), 对于非值类型的参数(如arrays, bytes, strings), Solidity不会直接存储,而是会将`Keccak-256`哈希存储在主题中,从而导致数据信息的丢失。这对于某些依赖于链上事件的DAPP(跨链,用户注册等等)来说,可能会导致事件检索困难,需要解析哈希值。 + ### 数据 `data` 事件中不带 `indexed`的参数会被存储在 `data` 部分中,可以理解为事件的“值”。`data` 部分的变量不能被直接检索,但可以存储任意大小的数据。因此一般 `data` 部分可以用来存储复杂的数据结构,例如数组和字符串等等,因为这些数据超过了256比特,即使存储在事件的 `topics` 部分中,也是以哈希的方式存储。另外,`data` 部分的变量在存储上消耗的gas相比于 `topics` 更少。 diff --git a/docs/solidity-101/13_Inheritance/Inheritance.sol b/docs/solidity-101/13_Inheritance/Inheritance.sol index 83ad1098..b2c1f131 100755 --- a/docs/solidity-101/13_Inheritance/Inheritance.sol +++ b/docs/solidity-101/13_Inheritance/Inheritance.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.21; contract Yeye { event Log(string msg); - // 定义3个function: hip(), pop(), man(),Log值为Yeye。 + // 定义3个function: hip(), pop(), yeye(),Log值为Yeye。 function hip() public virtual{ emit Log("Yeye"); } diff --git a/docs/solidity-101/13_Inheritance/readme.md b/docs/solidity-101/13_Inheritance/readme.md index a462cb4e..f0ce425e 100755 --- a/docs/solidity-101/13_Inheritance/readme.md +++ b/docs/solidity-101/13_Inheritance/readme.md @@ -46,7 +46,7 @@ mapping(address => uint256) public override balanceOf; contract Yeye { event Log(string msg); - // 定义3个function: hip(), pop(), man(),Log值为Yeye。 + // 定义3个function: hip(), pop(), yeye(),Log值为Yeye。 function hip() public virtual{ emit Log("Yeye"); } diff --git a/docs/solidity-102/17_Library/readme.md b/docs/solidity-102/17_Library/readme.md index eb07be78..8774f3a9 100755 --- a/docs/solidity-102/17_Library/readme.md +++ b/docs/solidity-102/17_Library/readme.md @@ -36,7 +36,7 @@ tags: 3. 不能接收以太币 4. 不可以被销毁 -需要注意的是,库合约重的函数可见性如果被设置为`public`或者`external`,则在调用函数时会触发一次`delegatecall`。而如果被设置为`internal`,则不会引起。对于设置为`private`可见性的函数来说,其仅能在库合约中可见,在其他合约中不可用。 +需要注意的是,库合约中的函数可见性如果被设置为`public`或者`external`,则在调用函数时会触发一次`delegatecall`。而如果被设置为`internal`,则不会引起。对于设置为`private`可见性的函数来说,其仅能在库合约中可见,在其他合约中不可用。 ## Strings库合约 @@ -105,7 +105,7 @@ library Strings { } ``` -他主要包含两个函数,`toString()`将`uint256`转为`string`,`toHexString()`将`uint256`转换为`16进制`,在转换为`string`。 +它主要包含两个函数,`toString()`将`uint256`转换为10进制的`string`,`toHexString()`将`uint256`转换为16进制的`string`。 ### 如何使用库合约 diff --git a/docs/solidity-102/23_Delegatecall/readme.md b/docs/solidity-102/23_Delegatecall/readme.md index 65333167..34909ef1 100755 --- a/docs/solidity-102/23_Delegatecall/readme.md +++ b/docs/solidity-102/23_Delegatecall/readme.md @@ -85,7 +85,8 @@ contract C { ### 发起调用的合约B -首先,合约`B`必须和目标合约`C`的变量存储布局必须相同,两个变量,并且顺序为`num`和`sender` +首先,合约`B`必须和目标合约`C`的变量存储布局必须相同 —— 即存在两个 `public` 变量且变量类型顺序为 `uint256` 和 `address` +> **注意:** 变量名称可以不同 ```solidity contract B { diff --git a/docs/solidity-102/24_Create/img/24-4.png b/docs/solidity-102/24_Create/img/24-4.png new file mode 100644 index 00000000..ca142f43 Binary files /dev/null and b/docs/solidity-102/24_Create/img/24-4.png differ diff --git a/docs/solidity-102/24_Create/readme.md b/docs/solidity-102/24_Create/readme.md index d1ec8d99..fd8a0fc4 100755 --- a/docs/solidity-102/24_Create/readme.md +++ b/docs/solidity-102/24_Create/readme.md @@ -94,7 +94,7 @@ contract PairFactory{ } ``` -工厂合约(`PairFactory`)有两个状态变量`getPair`是两个代币地址到币对地址的`map`,方便根据代币找到币对地址;`allPairs`是币对地址的数组,存储了所有代币地址。 +工厂合约(`PairFactory`)有两个状态变量`getPair`是两个代币地址到币对地址的`map`,方便根据代币找到币对地址;`allPairs`是币对地址的数组,存储了所有币对地址。 `PairFactory`合约只有一个`createPair`函数,根据输入的两个代币地址`tokenA`和`tokenB`来创建新的`Pair`合约。其中 @@ -114,15 +114,21 @@ BSC链上的PEOPLE地址: 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c 1. 使用`WBNB`和`PEOPLE`的地址作为参数调用`createPair`,得到`Pair`合约地址:0xD3e2008b4Da2cD6DEAF73471590fF30C86778A48 ![24-1](./img/24-1.png) -2. 查看`Pair`合约变量 + +2. 将 Contract 改为 `Pair`,然后在 At Address 输入框输入 `Pair` 合约地址,创建一个前端接口用于调用已部署的合约。 + + ![24-4](./img/24-4.png) + +3. 查看`Pair`合约变量 ![24-2](./img/24-2.png) -3. Debug查看`create`操作码 + +4. Debug查看`create`操作码 ![24-3](./img/24-3.png) ## 总结 -这一讲,我们用极简`Uniswap`的例子介绍了如何使用`create`方法再合约里创建合约,下一讲我们将介绍如何使用`create2`方法来实现极简`Uniswap`。 +这一讲,我们用极简`Uniswap`的例子介绍了如何使用`create`方法在合约里创建合约,下一讲我们将介绍如何使用`create2`方法来实现极简`Uniswap`。 \ No newline at end of file diff --git a/docs/solidity-102/26_DeleteContract/readme.md b/docs/solidity-102/26_DeleteContract/readme.md index 2d4fe7f5..006a0d36 100755 --- a/docs/solidity-102/26_DeleteContract/readme.md +++ b/docs/solidity-102/26_DeleteContract/readme.md @@ -36,7 +36,7 @@ tags: `selfdestruct`使用起来非常简单: ```solidity -selfdestruct(_addr); +selfdestruct(_addr); ``` 其中`_addr`是接收合约中剩余`ETH`的地址。`_addr` 地址不需要有`receive()`或`fallback()`也能接收`ETH`。 @@ -149,4 +149,4 @@ contract DeployContract { `selfdestruct`是智能合约的紧急按钮,销毁合约并将剩余`ETH`转移到指定账户。当著名的`The DAO`攻击发生时,以太坊的创始人们一定后悔过没有在合约里加入`selfdestruct`来停止黑客的攻击吧。在坎昆升级后,`selfdestruct`的作用也逐渐发生了改变,什么都不是一成不变的,还是要保持学习。 - + \ No newline at end of file diff --git a/docs/solidity-102/27_ABIEncode/readme.md b/docs/solidity-102/27_ABIEncode/readme.md index 23959222..f3d8d62a 100755 --- a/docs/solidity-102/27_ABIEncode/readme.md +++ b/docs/solidity-102/27_ABIEncode/readme.md @@ -47,11 +47,23 @@ function encode() public view returns(bytes memory result) { } ``` -编码的结果为`0x000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000007a58c0be72be218b41c608b7fe7c5bb630736c7100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000043078414100000000000000000000000000000000000000000000000000000000`,由于`abi.encode`将每个数据都填充为32字节,中间有很多`0`。 +编码的结果为`0x000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000007a58c0be72be218b41c608b7fe7c5bb630736c7100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000043078414100000000000000000000000000000000000000000000000000000000`,详细解释下编码的细节: + +``` +000000000000000000000000000000000000000000000000000000000000000a // x +0000000000000000000000007a58c0be72be218b41c608b7fe7c5bb630736c71 // addr +00000000000000000000000000000000000000000000000000000000000000a0 // name 参数的偏移量 +0000000000000000000000000000000000000000000000000000000000000005 // array[0] +0000000000000000000000000000000000000000000000000000000000000006 // array[1] +0000000000000000000000000000000000000000000000000000000000000004 // name 参数的长度为4字节 +3078414100000000000000000000000000000000000000000000000000000000 // name +``` + +其中 `name` 参数被转换为UTF-8的字节值 `0x30784141`,在 abi 编码规范中,string 属于动态类型 ,动态类型的参数需要借助偏移量进行编码,可以参考[动态类型的使用](https://learnblockchain.cn/docs/solidity/abi-spec.html#id9)。由于 abi.encode 会将每个参与编码的参数元素(包括偏移量,长度)都填充为32字节(evm字长为32字节),所以可以看到编码后的数据中有很多填充的 0 。 ### `abi.encodePacked` -将给定参数根据其所需最低空间编码。它类似 `abi.encode`,但是会把其中填充的很多`0`省略。比如,只用1字节来编码`uint8`类型。当你想省空间,并且不与合约交互的时候,可以使用`abi.encodePacked`,例如算一些数据的`hash`时。 +将给定参数根据其所需最低空间编码。它类似 `abi.encode`,但是会把其中填充的很多`0`省略。比如,只用1字节来编码`uint8`类型。当你想省空间,并且不与合约交互的时候,可以使用`abi.encodePacked`,例如算一些数据的`hash`时。需要注意,`abi.encodePacked`因为不会做填充,所以不同的输入在拼接后可能会产生相同的编码结果,导致冲突,这也带来了潜在的安全风险。 ```solidity function encodePacked() public view returns(bytes memory result) { diff --git a/docs/solidity-102/30_TryCatch/readme.md b/docs/solidity-102/30_TryCatch/readme.md index e8a415a8..8108aa70 100755 --- a/docs/solidity-102/30_TryCatch/readme.md +++ b/docs/solidity-102/30_TryCatch/readme.md @@ -25,7 +25,7 @@ tags: ## `try-catch` -在`Solidity`中,`try-catch`只能被用于`external`函数或创建合约时`constructor`(被视为`external`函数)的调用。基本语法如下: +在`Solidity`中,`try-catch`只能被用于`external`函数或`public`函数或创建合约时`constructor`(被视为`external`函数)的调用。基本语法如下: ```solidity try externalContract.f() { diff --git a/docs/solidity-103/34_ERC721/ERC721.sol b/docs/solidity-103/34_ERC721/ERC721.sol index d43b3351..f087891c 100644 --- a/docs/solidity-103/34_ERC721/ERC721.sol +++ b/docs/solidity-103/34_ERC721/ERC721.sol @@ -9,7 +9,7 @@ import "./IERC721Metadata.sol"; import "./String.sol"; contract ERC721 is IERC721, IERC721Metadata{ - using Strings for uint256; // 使用String库, + using Strings for uint256; // 使用Strings库, // Token名称 string public override name; @@ -21,7 +21,7 @@ contract ERC721 is IERC721, IERC721Metadata{ mapping(address => uint) private _balances; // tokenID 到 授权地址 的授权映射 mapping(uint => address) private _tokenApprovals; - // owner地址。到operator地址 的批量授权映射 + // owner地址 到 operator地址 的批量授权映射 mapping(address => mapping(address => bool)) private _operatorApprovals; // 错误 无效的接收者 diff --git a/docs/solidity-103/34_ERC721/readme.md b/docs/solidity-103/34_ERC721/readme.md index 7edb8e14..c27d8058 100644 --- a/docs/solidity-103/34_ERC721/readme.md +++ b/docs/solidity-103/34_ERC721/readme.md @@ -210,7 +210,7 @@ import "./IERC721Metadata.sol"; import "./String.sol"; contract ERC721 is IERC721, IERC721Metadata{ - using Strings for uint256; // 使用String库, + using Strings for uint256; // 使用Strings库, // Token名称 string public override name; @@ -222,7 +222,7 @@ contract ERC721 is IERC721, IERC721Metadata{ mapping(address => uint) private _balances; // tokenID 到 授权地址 的授权映射 mapping(uint => address) private _tokenApprovals; - // owner地址。到operator地址 的批量授权映射 + // owner地址 到 operator地址 的批量授权映射 mapping(address => mapping(address => bool)) private _operatorApprovals; // 错误 无效的接收者 @@ -590,7 +590,7 @@ interface ERC721Metadata /* is ERC721 */ { IERC721Metadata.name.selector ^ IERC721Metadata.symbol.selector ^ IERC721Metadata.tokenURI.selector ``` -solamte实现的ERC721.sol是怎么完成这些ERC165要求的特性的呢? +solmate实现的[ERC721.sol](https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC721.sol)是怎么完成这些ERC165要求的特性的呢? ```solidity function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) { diff --git a/docs/solidity-103/35_DutchAuction/readme.md b/docs/solidity-103/35_DutchAuction/readme.md index d563d7e9..31e79a36 100644 --- a/docs/solidity-103/35_DutchAuction/readme.md +++ b/docs/solidity-103/35_DutchAuction/readme.md @@ -54,7 +54,7 @@ contract DutchAuction is Ownable, ERC721 { 合约中一共有`9`个状态变量,其中有`6`个和拍卖相关,他们是: -- `COLLECTOIN_SIZE`:NFT总量。 +- `COLLECTION_SIZE`:NFT总量。 - `AUCTION_START_PRICE`:荷兰拍卖起拍价,也是最高价。 - `AUCTION_END_PRICE`:荷兰拍卖结束价,也是最低价/地板价。 - `AUCTION_TIME`:拍卖持续时长。 @@ -62,7 +62,7 @@ contract DutchAuction is Ownable, ERC721 { - `auctionStartTime`:拍卖起始时间(区块链时间戳,`block.timestamp`)。 ```solidity - uint256 public constant COLLECTOIN_SIZE = 10000; // NFT总数 + uint256 public constant COLLECTION_SIZE = 10000; // NFT总数 uint256 public constant AUCTION_START_PRICE = 1 ether; // 起拍价(最高价) uint256 public constant AUCTION_END_PRICE = 0.1 ether; // 结束价(最低价/地板价) uint256 public constant AUCTION_TIME = 10 minutes; // 拍卖时间,为了测试方便设为10分钟 @@ -82,7 +82,7 @@ contract DutchAuction is Ownable, ERC721 { - 设定拍卖起始时间:我们在构造函数中会声明当前区块时间为起始时间,项目方也可以通过`setAuctionStartTime()`函数来调整: ```solidity - constructor() ERC721("WTF Dutch Auctoin", "WTF Dutch Auctoin") { + constructor() ERC721("WTF Dutch Auction", "WTF Dutch Auction") { auctionStartTime = block.timestamp; } @@ -132,7 +132,7 @@ contract DutchAuction is Ownable, ERC721 { "sale has not started yet" ); // 检查是否设置起拍时间,拍卖是否开始 require( - totalSupply() + quantity <= COLLECTOIN_SIZE, + totalSupply() + quantity <= COLLECTION_SIZE, "not enough remaining reserved for auction to support desired mint amount" ); // 检查是否超过NFT上限 diff --git a/docs/solidity-103/36_MerkleTree/img/36-1.png b/docs/solidity-103/36_MerkleTree/img/36-1.png index 965c2fa9..ea817988 100644 Binary files a/docs/solidity-103/36_MerkleTree/img/36-1.png and b/docs/solidity-103/36_MerkleTree/img/36-1.png differ diff --git a/docs/solidity-103/36_MerkleTree/img/36-2.png b/docs/solidity-103/36_MerkleTree/img/36-2.png index acbbba25..dd49a1cd 100644 Binary files a/docs/solidity-103/36_MerkleTree/img/36-2.png and b/docs/solidity-103/36_MerkleTree/img/36-2.png differ diff --git a/docs/solidity-103/36_MerkleTree/readme.md b/docs/solidity-103/36_MerkleTree/readme.md index 1cf2d5b4..bda7d585 100644 --- a/docs/solidity-103/36_MerkleTree/readme.md +++ b/docs/solidity-103/36_MerkleTree/readme.md @@ -27,7 +27,7 @@ tags: ![Merkle Tree](./img/36-1.png) -`Merkle Tree`允许对大型数据结构的内容进行有效和安全的验证(`Merkle Proof`)。对于有`N`个叶子结点的`Merkle Tree`,在已知`root`根值的情况下,验证某个数据是否有效(属于`Merkle Tree`叶子结点)只需要`ceil(log₂N)`个数据(也叫`proof`),非常高效。如果数据有误,或者给的`proof`错误,则无法还原出`root`根植。 +`Merkle Tree`允许对大型数据结构的内容进行有效和安全的验证(`Merkle Proof`)。对于有`N`个叶子节点的`Merkle Tree`,在已知`root`根值的情况下,验证某个数据是否有效(属于`Merkle Tree`叶子节点)只需要`ceil(log₂N)`个数据(也叫`proof`),非常高效。如果数据有误,或者给的`proof`错误,则无法还原出`root`根值。 下面的例子中,叶子`L1`的`Merkle proof`为`Hash 0-1`和`Hash 1`:知道这两个值,就能验证`L1`的值是不是在`Merkle Tree`的叶子中。为什么呢? 因为通过叶子`L1`我们就可以算出`Hash 0-0`,我们又知道了`Hash 0-1`,那么`Hash 0-0`和`Hash 0-1`就可以联合算出`Hash 0`,然后我们又知道`Hash 1`,`Hash 0`和`Hash 1`就可以联合算出`Top Hash`,也就是root节点的hash。 @@ -38,7 +38,7 @@ tags: 我们可以利用[网页](https://lab.miguelmota.com/merkletreejs/example/)或者Javascript库[merkletreejs](https://github.com/miguelmota/merkletreejs)来生成`Merkle Tree`。 -这里我们用网页来生成`4`个地址作为叶子结点的`Merkle Tree`。叶子结点输入: +这里我们用网页来生成`4`个地址作为叶子节点的`Merkle Tree`。叶子节点输入: ```solidity [ @@ -63,7 +63,7 @@ tags: ![生成Merkle Tree](./img/36-3.png) ## `Merkle Proof`验证 -通过网站,我们可以得到`地址0`的`proof`如下,即图2中蓝色结点的哈希值: +通过网站,我们可以得到`地址0`的`proof`如下,即图2中蓝色节点的哈希值: ```solidity [ "0x999bf57501565dbd2fdcea36efa2b9aef8340a8901e3459f4a4c926275d36cdb", @@ -140,8 +140,9 @@ contract MerkleTree is ERC721 { { require(_verify(_leaf(account), proof), "Invalid merkle proof"); // Merkle检验通过 require(!mintedAddress[account], "Already minted!"); // 地址没有mint过 - _mint(account, tokenId); // mint + mintedAddress[account] = true; // 记录mint过的地址 + _mint(account, tokenId); // mint } // 计算Merkle树叶子的哈希值 @@ -170,7 +171,7 @@ contract MerkleTree is ERC721 { ### 函数 合约中共有4个函数: - 构造函数:初始化`NFT`的名称和代号,还有`Merkle Tree`的`root`。 -- `mint()`函数:利用白名单铸造`NFT`。参数为白名单地址`account`,铸造的`tokenId`,和`proof`。首先验证`address`是否在白名单中,验证通过则把序号为`tokenId`的`NFT`铸造给该地址,并将它记录到`mintedAddress`。此过程中调用了`_leaf()`和`_verify()`函数。 +- `mint()`函数:利用白名单铸造`NFT`。参数为白名单地址`account`,铸造的`tokenId`,和`proof`。首先验证`address`是否在白名单中,然后验证该地址是否还未铸造,验证通过则先把该地址记录到`mintedAddress`中防止[重入攻击](https://github.com/AmazingAng/WTF-Solidity/blob/main/S01_ReentrancyAttack/readme.md),然后把序号为`tokenId`的`NFT`铸造给该地址。此过程中调用了`_leaf()`和`_verify()`函数。 - `_leaf()`函数:计算了`Merkle Tree`的叶子地址的哈希。 - `_verify()`函数:调用了`MerkleProof`库的`verify()`函数,进行`Merkle Tree`验证。 @@ -185,7 +186,7 @@ merkleroot = 0xeeefd63003e0e702cb41cd0043015a6e26ddb38073cc6ffeb0ba3e808ba8c097 ![部署MerkleTree合约](./img/36-5.png) -接下来运行`mint`函数给地址0铸造`NFT`,`3`个参数分别为: +接下来运行`mint`函数给`地址0`铸造`NFT`,`3`个参数分别为: ```solidity account = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 @@ -195,7 +196,7 @@ proof = [ "0x999bf57501565dbd2fdcea36efa2b9aef8340a8901e3459f4a4c926275d36cdb" ![白名单mint](./img/36-6.png) -我们可以用`ownerOf`函数验证`tokenId`为0的`NFT`已经铸造给了地址0,合约运行成功! +我们可以用`ownerOf`函数验证`tokenId`为0的`NFT` 已经铸造给了`地址0`,合约运行成功! ![tokenId为0的持有者改变,合约运行成功!](./img/36-7.png) diff --git a/docs/solidity-103/37_Signature/readme.md b/docs/solidity-103/37_Signature/readme.md index 4f6fa1bd..bba4ec54 100644 --- a/docs/solidity-103/37_Signature/readme.md +++ b/docs/solidity-103/37_Signature/readme.md @@ -137,7 +137,7 @@ print(f"签名:{signed_message['signature'].hex()}") 为了验证签名,验证者需要拥有`消息`,`签名`,和签名使用的`公钥`。我们能验证签名的原因是只有`私钥`的持有者才能够针对交易生成这样的签名,而别人不能。 -**4. 通过签名和消息恢复公钥:**`签名`是由数学算法生成的。这里我们使用的是`rsv签名`,`签名`中包含`r, s, v`三个值的信息。而后,我们可以通过`r, s, v`及`以太坊签名消息`来求得`公钥`。下面的`recoverSigner()`函数实现了上述步骤,它利用`以太坊签名消息 _msgHash`和`签名 _signature`恢复`公钥`(使用了简单的内联汇编): +**4. 通过签名和消息恢复公钥:**`签名`是由数学算法生成的。这里我们使用的是`rsv签名`,`签名`中包含`r, s, v`三个值的信息,长度分别为32 bytes,32 bytes,1 byte。而后,我们可以通过`r, s, v`及`以太坊签名消息`来求得`公钥`。下面的`recoverSigner()`函数实现了上述步骤,它利用`以太坊签名消息 _msgHash`和`签名 _signature`恢复`公钥`(使用了简单的内联汇编): ```solidity // @dev 从_msgHash和签名_signature中恢复signer地址 @@ -171,6 +171,9 @@ print(f"签名:{signed_message['signature'].hex()}") _msgHash:0xb42ca4636f721c7a331923e764587e98ec577cea1a185f60dfcc14dbb9bd900b _signature:0x390d704d7ab732ce034203599ee93dd5d3cb0d4d1d7c600ac11726659489773d559b12d220f99f41d17651b0c1c6a669d346a397f8541760d6b32a5725378b241c ``` + +需要注意的是,这里需要对输入参数`_signature`的长度进行检查,确保其长度为65bytes,否则会产生签名重放问题。具体问题可以参考[BlazCTF中的Cyber Cartel](https://github.com/DeFiHackLabs/blazctf-2024-writeup/blob/main/writeup/cyber-cartel.md). + ![通过签名和消息恢复公钥](./img/37-8.png) **5. 对比公钥并验证签名:** 接下来,我们只需要比对恢复的`公钥`与签名者公钥`_signer`是否相等:若相等,则签名有效;否则,签名无效: diff --git a/docs/solidity-103/38_NFTSwap/NFTSwap.sol b/docs/solidity-103/38_NFTSwap/NFTSwap.sol index 0483d73c..56de4ca4 100644 --- a/docs/solidity-103/38_NFTSwap/NFTSwap.sol +++ b/docs/solidity-103/38_NFTSwap/NFTSwap.sol @@ -46,7 +46,7 @@ contract NFTSwap is IERC721Receiver { require(_nft.getApproved(_tokenId) == address(this), "Need Approval"); // 合约得到授权 require(_price > 0); // 价格大于0 - Order storage _order = nftList[_nftAddr][_tokenId]; //设置NF持有人和价格 + Order storage _order = nftList[_nftAddr][_tokenId]; //设置NFT持有人和价格 _order.owner = msg.sender; _order.price = _price; // 将NFT转账到合约 @@ -67,14 +67,17 @@ contract NFTSwap is IERC721Receiver { // 将NFT转给买家 _nft.safeTransferFrom(address(this), msg.sender, _tokenId); - // 将ETH转给卖家,多余ETH给买家退款 + // 将ETH转给卖家 payable(_order.owner).transfer(_order.price); - payable(msg.sender).transfer(msg.value - _order.price); - - delete nftList[_nftAddr][_tokenId]; // 删除order + // 多余ETH给买家退款 + if (msg.value > _order.price) { + payable(msg.sender).transfer(msg.value - _order.price); + } // 释放Purchase事件 emit Purchase(msg.sender, _nftAddr, _tokenId, _order.price); + + delete nftList[_nftAddr][_tokenId]; // 删除order } // 撤单: 卖家取消挂单 diff --git a/docs/solidity-103/38_NFTSwap/readme.md b/docs/solidity-103/38_NFTSwap/readme.md index f578561c..78ccaf47 100644 --- a/docs/solidity-103/38_NFTSwap/readme.md +++ b/docs/solidity-103/38_NFTSwap/readme.md @@ -20,7 +20,7 @@ tags: --- -`Opensea`是以太坊上最大的`NFT`交易平台,总交易总量达到了`$300亿`。`Opensea`在交易中抽成`2.5%`,因此它通过用户交易至少获利了`$7.5亿`。另外,它的运作并不去中心化,且不准备发币补偿用户。`NFT`玩家苦`Opensea`久已,今天我们就利用智能合约搭建一个零手续费的去中心化`NFT`交易所:`NFTSwap`。 +`OpenSea`是以太坊上最大的`NFT`交易平台,总交易总量达到了`$300亿`。`OpenSea`在交易中抽成`2.5%`,因此它通过用户交易至少获利了`$7.5亿`。另外,它的运作并不去中心化,且不准备发币补偿用户。`NFT`玩家苦`OpenSea`久已,今天我们就利用智能合约搭建一个零手续费的去中心化`NFT`交易所:`NFTSwap`。 ## 设计逻辑 @@ -31,31 +31,36 @@ tags: ## `NFTSwap`合约 ### 事件 + 合约包含`4`个事件,对应挂单`list`、撤单`revoke`、修改价格`update`、购买`purchase`这四个行为: + ``` solidity - event List(address indexed seller, address indexed nftAddr, uint256 indexed tokenId, uint256 price); - event Purchase(address indexed buyer, address indexed nftAddr, uint256 indexed tokenId, uint256 price); - event Revoke(address indexed seller, address indexed nftAddr, uint256 indexed tokenId); - event Update(address indexed seller, address indexed nftAddr, uint256 indexed tokenId, uint256 newPrice); +event List(address indexed seller, address indexed nftAddr, uint256 indexed tokenId, uint256 price); +event Purchase(address indexed buyer, address indexed nftAddr, uint256 indexed tokenId, uint256 price); +event Revoke(address indexed seller, address indexed nftAddr, uint256 indexed tokenId); +event Update(address indexed seller, address indexed nftAddr, uint256 indexed tokenId, uint256 newPrice); ``` ### 订单 + `NFT`订单抽象为`Order`结构体,包含挂单价格`price`和持有人`owner`信息。`nftList`映射记录了订单是对应的`NFT`系列(合约地址)和`tokenId`信息。 + ```solidity - // 定义order结构体 - struct Order{ - address owner; - uint256 price; - } - // NFT Order映射 - mapping(address => mapping(uint256 => Order)) public nftList; +// 定义order结构体 +struct Order{ + address owner; + uint256 price; +} +// NFT Order映射 +mapping(address => mapping(uint256 => Order)) public nftList; ``` ### 回退函数 -在`NFTSwap`中,用户使用`ETH`购买`NFT`。因此,合约需要实现`fallback()`函数来接收`ETH`。 + +在`NFTSwap`合约中,用户使用`ETH`购买`NFT`。因此,合约需要实现`fallback()`函数来接收`ETH`。 ```solidity - fallback() external payable{} +fallback() external payable{} ``` ### onERC721Received @@ -63,8 +68,7 @@ tags: `ERC721`的安全转账函数会检查接收合约是否实现了`onERC721Received()`函数,并返回正确的选择器`selector`。用户下单之后,需要将`NFT`发送给`NFTSwap`合约。因此`NFTSwap`继承`IERC721Receiver`接口,并实现`onERC721Received()`函数: ```solidity -contract NFTSwap is IERC721Receiver{ - +contract NFTSwap is IERC721Receiver { // 实现{IERC721Receiver}的onERC721Received,能够接收ERC721代币 function onERC721Received( address operator, @@ -74,6 +78,7 @@ contract NFTSwap is IERC721Receiver{ ) external override returns (bytes4){ return IERC721Receiver.onERC721Received.selector; } +} ``` ### 交易 @@ -82,14 +87,14 @@ contract NFTSwap is IERC721Receiver{ - 挂单`list()`:卖家创建`NFT`并创建订单,并释放`List`事件。参数为`NFT`合约地址`_nftAddr`,`NFT`对应的`_tokenId`,挂单价格`_price`(**注意:单位是`wei`**)。成功后,`NFT`会从卖家转到`NFTSwap`合约中。 -```solidity + ```solidity // 挂单: 卖家上架NFT,合约地址为_nftAddr,tokenId为_tokenId,价格_price为以太坊(单位是wei) function list(address _nftAddr, uint256 _tokenId, uint256 _price) public{ IERC721 _nft = IERC721(_nftAddr); // 声明IERC721接口合约变量 require(_nft.getApproved(_tokenId) == address(this), "Need Approval"); // 合约得到授权 require(_price > 0); // 价格大于0 - Order storage _order = nftList[_nftAddr][_tokenId]; //设置NF持有人和价格 + Order storage _order = nftList[_nftAddr][_tokenId]; //设置NFT持有人和价格 _order.owner = msg.sender; _order.price = _price; // 将NFT转账到合约 @@ -98,72 +103,81 @@ contract NFTSwap is IERC721Receiver{ // 释放List事件 emit List(msg.sender, _nftAddr, _tokenId, _price); } -``` + ``` + - 撤单`revoke()`:卖家撤回挂单,并释放`Revoke`事件。参数为`NFT`合约地址`_nftAddr`,`NFT`对应的`_tokenId`。成功后,`NFT`会从`NFTSwap`合约转回卖家。 -```solidity - // 撤单: 卖家取消挂单 - function revoke(address _nftAddr, uint256 _tokenId) public { - Order storage _order = nftList[_nftAddr][_tokenId]; // 取得Order - require(_order.owner == msg.sender, "Not Owner"); // 必须由持有人发起 - // 声明IERC721接口合约变量 - IERC721 _nft = IERC721(_nftAddr); - require(_nft.ownerOf(_tokenId) == address(this), "Invalid Order"); // NFT在合约中 - - // 将NFT转给卖家 - _nft.safeTransferFrom(address(this), msg.sender, _tokenId); - delete nftList[_nftAddr][_tokenId]; // 删除order + + ```solidity + // 撤单: 卖家取消挂单 + function revoke(address _nftAddr, uint256 _tokenId) public { + Order storage _order = nftList[_nftAddr][_tokenId]; // 取得Order + require(_order.owner == msg.sender, "Not Owner"); // 必须由持有人发起 + // 声明IERC721接口合约变量 + IERC721 _nft = IERC721(_nftAddr); + require(_nft.ownerOf(_tokenId) == address(this), "Invalid Order"); // NFT在合约中 - // 释放Revoke事件 - emit Revoke(msg.sender, _nftAddr, _tokenId); - } -``` + // 将NFT转给卖家 + _nft.safeTransferFrom(address(this), msg.sender, _tokenId); + delete nftList[_nftAddr][_tokenId]; // 删除order + + // 释放Revoke事件 + emit Revoke(msg.sender, _nftAddr, _tokenId); + } + ``` + - 修改价格`update()`:卖家修改`NFT`订单价格,并释放`Update`事件。参数为`NFT`合约地址`_nftAddr`,`NFT`对应的`_tokenId`,更新后的挂单价格`_newPrice`(**注意:单位是`wei`**)。 -```solidity - // 调整价格: 卖家调整挂单价格 - function update(address _nftAddr, uint256 _tokenId, uint256 _newPrice) public { - require(_newPrice > 0, "Invalid Price"); // NFT价格大于0 - Order storage _order = nftList[_nftAddr][_tokenId]; // 取得Order - require(_order.owner == msg.sender, "Not Owner"); // 必须由持有人发起 - // 声明IERC721接口合约变量 - IERC721 _nft = IERC721(_nftAddr); - require(_nft.ownerOf(_tokenId) == address(this), "Invalid Order"); // NFT在合约中 - - // 调整NFT价格 - _order.price = _newPrice; - - // 释放Update事件 - emit Update(msg.sender, _nftAddr, _tokenId, _newPrice); - } -``` -- 购买`purchase`:买家支付`ETH`购买挂单的`NFT`,并释放`Purchase`事件。参数为`NFT`合约地址`_nftAddr`,`NFT`对应的`_tokenId`。成功后,`ETH`将转给卖家,`NFT`将从`NFTSwap`合约转给买家。 -```solidity - // 购买: 买家购买NFT,合约为_nftAddr,tokenId为_tokenId,调用函数时要附带ETH - function purchase(address _nftAddr, uint256 _tokenId) payable public { - Order storage _order = nftList[_nftAddr][_tokenId]; // 取得Order - require(_order.price > 0, "Invalid Price"); // NFT价格大于0 - require(msg.value >= _order.price, "Increase price"); // 购买价格大于标价 - // 声明IERC721接口合约变量 - IERC721 _nft = IERC721(_nftAddr); - require(_nft.ownerOf(_tokenId) == address(this), "Invalid Order"); // NFT在合约中 - - // 将NFT转给买家 - _nft.safeTransferFrom(address(this), msg.sender, _tokenId); - // 将ETH转给卖家,多余ETH给买家退款 - payable(_order.owner).transfer(_order.price); - payable(msg.sender).transfer(msg.value-_order.price); - - delete nftList[_nftAddr][_tokenId]; // 删除order - - // 释放Purchase事件 - emit Purchase(msg.sender, _nftAddr, _tokenId, _order.price); - } -``` + ```solidity + // 调整价格: 卖家调整挂单价格 + function update(address _nftAddr, uint256 _tokenId, uint256 _newPrice) public { + require(_newPrice > 0, "Invalid Price"); // NFT价格大于0 + Order storage _order = nftList[_nftAddr][_tokenId]; // 取得Order + require(_order.owner == msg.sender, "Not Owner"); // 必须由持有人发起 + // 声明IERC721接口合约变量 + IERC721 _nft = IERC721(_nftAddr); + require(_nft.ownerOf(_tokenId) == address(this), "Invalid Order"); // NFT在合约中 + + // 调整NFT价格 + _order.price = _newPrice; + + // 释放Update事件 + emit Update(msg.sender, _nftAddr, _tokenId, _newPrice); + } + ``` + +- 购买`purchase()`:买家支付`ETH`购买挂单的`NFT`,并释放`Purchase`事件。参数为`NFT`合约地址`_nftAddr`,`NFT`对应的`_tokenId`。成功后,`ETH`将转给卖家,`NFT`将从`NFTSwap`合约转给买家。 + + ```solidity + // 购买: 买家购买NFT,合约为_nftAddr,tokenId为_tokenId,调用函数时要附带ETH + function purchase(address _nftAddr, uint256 _tokenId) public payable { + Order storage _order = nftList[_nftAddr][_tokenId]; // 取得Order + require(_order.price > 0, "Invalid Price"); // NFT价格大于0 + require(msg.value >= _order.price, "Increase price"); // 购买价格大于标价 + // 声明IERC721接口合约变量 + IERC721 _nft = IERC721(_nftAddr); + require(_nft.ownerOf(_tokenId) == address(this), "Invalid Order"); // NFT在合约中 + + // 将NFT转给买家 + _nft.safeTransferFrom(address(this), msg.sender, _tokenId); + // 将ETH转给卖家 + payable(_order.owner).transfer(_order.price); + // 多余ETH给买家退款 + if (msg.value > _order.price) { + payable(msg.sender).transfer(msg.value - _order.price); + } + + // 释放Purchase事件 + emit Purchase(msg.sender, _nftAddr, _tokenId, _order.price); + + delete nftList[_nftAddr][_tokenId]; // 删除order + } + ``` ## `Remix`实现 ### 1. 部署NFT合约 + 参考 [ERC721](https://github.com/AmazingAng/WTF-Solidity/tree/main/34_ERC721) 教程了解NFT,并部署`WTFApe` NFT合约。 ![部署NFT合约](./img/38-1.png) @@ -189,11 +203,13 @@ contract NFTSwap is IERC721Receiver{ 按照上述方法,将TokenId为 `0` 和 `1` 的NFT都mint给自己,其中`tokenId`为`0`的,我们执行更新购买操作,`tokenId`为`1`的,我们执行下架操作。 ### 2. 部署`NFTSwap`合约 + 部署`NFTSwap`合约。 ![部署`NFTSwap`合约](./img/38-4.png) ### 3. 将要上架的`NFT`授权给`NFTSwap`合约 + 在`WTFApe`合约中调用 `approve()`授权函数,将自己持有的`tokenId`为0的NFT授权给`NFTSwap`合约地址。 `approve(address to, uint tokenId)`方法有2个参数: @@ -202,11 +218,12 @@ contract NFTSwap is IERC721Receiver{ `tokenId`: `tokenId`为NFT的id,本案例中为上述mint的`0`Id。 -![](./img/38-5.png) +![将要上架的`NFT`授权给`NFTSwap`合约](./img/38-5.png) 按照上述方法,同理将`tokenId`为`1`的NFT也授权给`NFTSwap`合约地址。 ### 4. 上架`NFT` + 调用`NFTSwap`合约的`list()`函数,将自己持有的`tokenId`为0的NFT上架到`NFTSwap`,价格设为1 `wei`。 `list(address _nftAddr, uint256 _tokenId, uint256 _price)`方法有3个参数: @@ -217,7 +234,7 @@ contract NFTSwap is IERC721Receiver{ `_price`: `_price`为NFT的价格,本案例中为1 `wei`。 -![](./img/38-6.png) +![上架`NFT`](./img/38-6.png) 按照上述方法,同理将自己持有的`tokenId`为1的NFT上架到`NFTSwap`,价格设为1 `wei`。 @@ -227,9 +244,9 @@ contract NFTSwap is IERC721Receiver{ `nftList`:是一个NFT Order的映射,结构如下: -`nftList[_nftAddr][_tokenId]`: 输入`_nftAddr`和`_tokenId`,返回一个NFT订单。 +`nftList[_nftAddr][_tokenId]`: 输入`_nftAddr`和`_tokenId`,返回一个NFT订单。 -![](./img/38-7.png) +![查看上架NFT](./img/38-7.png) ### 6. 更新`NFT`价格 @@ -245,8 +262,7 @@ contract NFTSwap is IERC721Receiver{ 执行`update`之后,调用`nftList` 查看更新后的价格 -![](./img/38-8.png) - +![更新`NFT`价格](./img/38-8.png) ### 5. 下架NFT @@ -260,12 +276,11 @@ contract NFTSwap is IERC721Receiver{ `_tokenId`: `_tokenId`为NFT的id,本案例中为上述mint的`1`Id。 -![](./img/38-9.png) +![下架NFT](./img/38-9.png) 调用`NFTSwap`合约的`nftList()`函数,可以看到`NFT`已经下架。再次上架需要重新授权。 -![](./img/38-10.png) - +![验证是否下架](./img/38-10.png) **注意下架NFT之后,需要重新从步骤3开始,重新授权和上架NFT之后,才能进行购买** ### 6. 购买`NFT` @@ -282,13 +297,14 @@ contract NFTSwap is IERC721Receiver{ `_wei`: `_wei`为支付的`ETH`数量,本案例中为77 `wei`。 -![](./img/38-11.png) +![购买`NFT`](./img/38-11.png) ### 7. 验证`NFT`持有人改变 购买成功之后,调用`WTFApe`合约的`ownerOf()`函数,可以看到`NFT`持有者发生变化,购买成功! -![](./img/38-12.png) +![验证`NFT`持有人改变](./img/38-12.png) ## 总结 + 这一讲,我们建立了一个零手续费的去中心化`NFT`交易所。`OpenSea`虽然对`NFT`的发展做了很大贡献,但它的缺点也非常明显:高手续费、不发币回馈用户、交易机制容易被钓鱼导致用户资产丢失。目前`Looksrare`和`dydx`等新的`NFT`交易平台正在挑战`OpenSea`的位置,`Uniswap`也在研究新的`NFT`交易所。相信不久的将来,我们会用到更好的`NFT`交易所。 diff --git a/docs/solidity-103/40_ERC1155/ERC1155.sol b/docs/solidity-103/40_ERC1155/ERC1155.sol index 76b67f88..09ba3b37 100644 --- a/docs/solidity-103/40_ERC1155/ERC1155.sol +++ b/docs/solidity-103/40_ERC1155/ERC1155.sol @@ -14,7 +14,7 @@ import "../34_ERC721/IERC165.sol"; */ contract ERC1155 is IERC165, IERC1155, IERC1155MetadataURI { using Address for address; // 使用Address库,用isContract来判断地址是否为合约 - using Strings for uint256; // 使用String库 + using Strings for uint256; // 使用Strings库 // Token名称 string public name; // Token代号 diff --git a/docs/solidity-103/40_ERC1155/readme.md b/docs/solidity-103/40_ERC1155/readme.md index 9797787c..70827379 100644 --- a/docs/solidity-103/40_ERC1155/readme.md +++ b/docs/solidity-103/40_ERC1155/readme.md @@ -250,7 +250,7 @@ import "https://github.com/AmazingAng/WTF-Solidity/blob/main/34_ERC721/IERC165.s */ contract ERC1155 is IERC165, IERC1155, IERC1155MetadataURI { using Address for address; // 使用Address库,用isContract来判断地址是否为合约 - using Strings for uint256; // 使用String库 + using Strings for uint256; // 使用Strings库 // Token名称 string public name; // Token代号 diff --git a/docs/solidity-103/46_ProxyContract/readme.md b/docs/solidity-103/46_ProxyContract/readme.md index e5415c7c..1836886c 100644 --- a/docs/solidity-103/46_ProxyContract/readme.md +++ b/docs/solidity-103/46_ProxyContract/readme.md @@ -115,7 +115,7 @@ fallback() external payable { - `implementation`:占位变量,与`Proxy`合约保持一致,防止插槽冲突。 - `x`:`uint`变量,被设置为`99`。 - `CallSuccess`事件:在调用成功时释放。 -- `increment()`函数:会被`Proxy`合约调用,释放`CallSuccess`事件,并返回一个`uint`,它的`selector`为`0xd09de08a`。如果直接调用`increment()`回返回`100`,但是通过`Proxy`调用它会返回`1`,大家可以想想为什么? +- `increment()`函数:会被`Proxy`合约调用,释放`CallSuccess`事件,并返回一个`uint`,它的`selector`为`0xd09de08a`。如果直接调用`increment()`会返回`100`,但是通过`Proxy`调用它会返回`1`,大家可以想想为什么? ```solidity /** diff --git a/docs/solidity-103/47_Upgrade/readme.md b/docs/solidity-103/47_Upgrade/readme.md index 970b91c5..2e55a6de 100644 --- a/docs/solidity-103/47_Upgrade/readme.md +++ b/docs/solidity-103/47_Upgrade/readme.md @@ -36,11 +36,12 @@ tags: 这个代理合约比[第46讲](https://github.com/AmazingAng/WTF-Solidity/blob/main/46_ProxyContract/readme.md)中的简单。我们没有在它的`fallback()`函数中使用`内联汇编`,而仅仅用了`implementation.delegatecall(msg.data);`。因此,回调函数没有返回值,但足够教学使用了。 它包含`3`个变量: + - `implementation`:逻辑合约地址。 - `admin`:admin地址。 - `words`:字符串,可以通过逻辑合约的函数改变。 -它包含`3`个函数 +它包含`3`个函数: - 构造函数:初始化admin和逻辑合约地址。 - `fallback()`:回调函数,将调用委托给逻辑合约。 @@ -79,7 +80,7 @@ contract SimpleUpgrade { ### 旧逻辑合约 -这个逻辑合约包含`3`个状态变量,与保持代理合约一致,防止插槽冲突。它只有一个函数`foo()`,将代理合约中的`words`的值改为`"old"`。 +这个逻辑合约包含`3`个状态变量,与代理合约保持一致,防止插槽冲突。它只有一个函数`foo()`,将代理合约中的`words`的值改为`"old"`。 ```solidity // 逻辑合约1 @@ -98,7 +99,7 @@ contract Logic1 { ### 新逻辑合约 -这个逻辑合约包含`3`个状态变量,与保持代理合约一致,防止插槽冲突。它只有一个函数`foo()`,将代理合约中的`words`的值改为`"new"`。 +这个逻辑合约包含`3`个状态变量,与代理合约保持一致,防止插槽冲突。它只有一个函数`foo()`,将代理合约中的`words`的值改为`"new"`。 ```solidity // 逻辑合约2 diff --git a/docs/solidity-103/48_TransparentProxy/readme.md b/docs/solidity-103/48_TransparentProxy/readme.md index 3c059ec2..f69f4afa 100644 --- a/docs/solidity-103/48_TransparentProxy/readme.md +++ b/docs/solidity-103/48_TransparentProxy/readme.md @@ -95,7 +95,7 @@ contract TransparentProxy { ### 逻辑合约 -这里的新、旧逻辑合约与[第47讲](https://github.com/AmazingAng/WTF-Solidity/blob/main/47_Upgrade/readme.md)一样。逻辑合约包含`3`个状态变量,与保持代理合约一致,防止插槽冲突;包含一个函数`foo()`,旧逻辑合约会将`words`的值改为`"old"`,新的会改为`"new"`。 +这里的新、旧逻辑合约与[第47讲](https://github.com/AmazingAng/WTF-Solidity/blob/main/47_Upgrade/readme.md)一样。逻辑合约包含`3`个状态变量,与代理合约保持一致,防止插槽冲突;包含一个函数`foo()`,旧逻辑合约会将`words`的值改为`"old"`,新的会改为`"new"`。 ```solidity // 旧逻辑合约 diff --git a/docs/solidity-103/49_UUPS/readme.md b/docs/solidity-103/49_UUPS/readme.md index 14fa0408..1c4a3d63 100644 --- a/docs/solidity-103/49_UUPS/readme.md +++ b/docs/solidity-103/49_UUPS/readme.md @@ -70,7 +70,7 @@ contract UUPSProxy { ### UUPS的逻辑合约 -UUPS的逻辑合约与[第47讲](https://github.com/AmazingAng/WTF-Solidity/blob/main/47_Upgrade/readme.md)中的不同是多了个升级函数。UUPS逻辑合约包含`3`个状态变量,与保持代理合约一致,防止插槽冲突。它包含`2`个 +UUPS的逻辑合约与[第47讲](https://github.com/AmazingAng/WTF-Solidity/blob/main/47_Upgrade/readme.md)中的不同是多了个升级函数。UUPS逻辑合约包含`3`个状态变量,与代理合约保持一致,防止插槽冲突。它包含`2`个 - `upgrade()`:升级函数,将改变逻辑合约地址`implementation`,只能由`admin`调用。 - `foo()`:旧UUPS逻辑合约会将`words`的值改为`"old"`,新的会改为`"new"`。 diff --git a/docs/solidity-103/50_MultisigWallet/MultisigWallet.sol b/docs/solidity-103/50_MultisigWallet/MultisigWallet.sol index 481828f8..71c28d20 100644 --- a/docs/solidity-103/50_MultisigWallet/MultisigWallet.sol +++ b/docs/solidity-103/50_MultisigWallet/MultisigWallet.sol @@ -28,7 +28,7 @@ contract MultisigWallet { function _setupOwners(address[] memory _owners, uint256 _threshold) internal { // threshold没被初始化过 require(threshold == 0, "WTF5000"); - // 多签执行门槛 小于 多签人数 + // 多签执行门槛 小于或等于 多签人数 require(_threshold <= _owners.length, "WTF5001"); // 多签执行门槛至少为1 require(_threshold >= 1, "WTF5002"); diff --git a/docs/solidity-103/50_MultisigWallet/readme.md b/docs/solidity-103/50_MultisigWallet/readme.md index ba4eaf5d..45d2192f 100644 --- a/docs/solidity-103/50_MultisigWallet/readme.md +++ b/docs/solidity-103/50_MultisigWallet/readme.md @@ -43,7 +43,7 @@ Gnosis Safe多签钱包是以太坊最流行的多签钱包,管理近400亿美 - `nonce`:初始为`0`,随着多签合约每笔成功执行的交易递增的值,可以防止签名重放攻击。 - `chainid`:链id,防止不同链的签名重放攻击。 -3. 收集多签签名(链下):将上一步的交易ABI编码并计算哈希,得到交易哈希,然后让多签人签名,并拼接到一起的到打包签名。对ABI编码和哈希不了解的,可以看WTF Solidity极简教程[第27讲](https://github.com/AmazingAng/WTF-Solidity/blob/main/27_ABIEncode/readme.md)和[第28讲](https://github.com/AmazingAng/WTF-Solidity/blob/main/28_Hash/readme.md)。 +3. 收集多签签名(链下):将上一步的交易ABI编码并计算哈希,得到交易哈希,然后让多签人签名,并拼接到一起得到打包签名。对ABI编码和哈希不了解的,可以看WTF Solidity极简教程[第27讲](https://github.com/AmazingAng/WTF-Solidity/blob/main/27_ABIEncode/readme.md)和[第28讲](https://github.com/AmazingAng/WTF-Solidity/blob/main/28_Hash/readme.md)。 ```solidity 交易哈希: 0xc1b055cf8e78338db21407b425114a2e258b0318879327945b661bfdea570e66 @@ -107,7 +107,7 @@ Gnosis Safe多签钱包是以太坊最流行的多签钱包,管理近400亿美 function _setupOwners(address[] memory _owners, uint256 _threshold) internal { // threshold没被初始化过 require(threshold == 0, "WTF5000"); - // 多签执行门槛 小于 多签人数 + // 多签执行门槛 小于或等于 多签人数 require(_threshold <= _owners.length, "WTF5001"); // 多签执行门槛至少为1 require(_threshold >= 1, "WTF5002"); @@ -150,7 +150,7 @@ Gnosis Safe多签钱包是以太坊最流行的多签钱包,管理近400亿美 } ``` -4. `checkSignatures()`:检查签名和交易数据的哈希是否对应,数量是否达到门槛,若否,交易会revert。单个签名长度为65字节,因此打包签名的长度要长于`threshold * 65`。调用了`signatureSplit()`分离出单个签名。这个函数的大致思路: +4. `checkSignatures()`:检查签名和交易数据的哈希是否对应,数量是否达到门槛,若否,交易会revert。单个签名长度为65字节,因此打包签名的长度要长于或等于`threshold * 65`。调用了`signatureSplit()`分离出单个签名。这个函数的大致思路: - 用ecdsa获取签名地址. - 利用 `currentOwner > lastOwner` 确定签名来自不同多签(多签地址递增)。 - 利用`isOwner[currentOwner]`确定签名者为多签持有人。 @@ -286,7 +286,7 @@ Gnosis Safe多签钱包是以太坊最流行的多签钱包,管理近400亿美 多签地址2的签名: 0x6b228b6033c097e220575f826560226a5855112af667e984aceca50b776f4c885e983f1f2155c294c86a905977853c6b1bb630c488502abcc838f9a225c813811c - 讲两个签名拼接到一起,得到打包签名: 0xa3f3e4375f54ad0a8070f5abd64e974b9b84306ac0dd5f59834efc60aede7c84454813efd16923f1a8c320c05f185bd90145fd7a7b741a8d13d4e65a4722687e1b6b228b6033c097e220575f826560226a5855112af667e984aceca50b776f4c885e983f1f2155c294c86a905977853c6b1bb630c488502abcc838f9a225c813811c + 将两个签名拼接到一起,得到打包签名: 0xa3f3e4375f54ad0a8070f5abd64e974b9b84306ac0dd5f59834efc60aede7c84454813efd16923f1a8c320c05f185bd90145fd7a7b741a8d13d4e65a4722687e1b6b228b6033c097e220575f826560226a5855112af667e984aceca50b776f4c885e983f1f2155c294c86a905977853c6b1bb630c488502abcc838f9a225c813811c ``` ![签名](./img/50-5.png) diff --git a/docs/solidity-103/51_ERC4626/readme.md b/docs/solidity-103/51_ERC4626/readme.md index 90062a31..60b66eb4 100644 --- a/docs/solidity-103/51_ERC4626/readme.md +++ b/docs/solidity-103/51_ERC4626/readme.md @@ -47,7 +47,7 @@ tags: ### ERC4626 要点 -ERC4626 标准主要实现了一下几个逻辑: +ERC4626 标准主要实现了以下几个逻辑: 1. ERC20: ERC4626 继承了 ERC20,金库份额就是用 ERC20 代币代表的:用户将特定的 ERC20 基础资产(比如 WETH)存进金库,合约会给他铸造特定数量的金库份额代币;当用户从金库中提取基础资产时,会销毁相应数量的金库份额代币。`asset()` 函数会返回金库的基础资产的代币地址。 2. 存款逻辑:让用户存入基础资产,并铸造相应数量的金库份额。相关函数为 `deposit()` 和 `mint()`。`deposit(uint assets, address receiver)` 函数让用户存入 `assets` 单位的资产,并铸造相应数量的金库份额给 `receiver` 地址。`mint(uint shares, address receiver)` 与它类似,只不过是以将铸造的金库份额作为参数。 @@ -448,6 +448,8 @@ contract ERC4626 is ERC20, IERC4626 { } ``` +当然,本文中的`ERC4626`合约仅是为了教学演示使用,在实际使用时,还需要考虑如`Inflation Attack`, `Rounding Direction`等问题。在生产中,建议使用`openzeppelin`的具体实现。 + ## `Remix`演示 **注意:** 以下运行示例使用了remix中第二个账户,即`0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2`, 来部署合约, 调用合约方法. diff --git a/docs/solidity-103/52_EIP712/readme.md b/docs/solidity-103/52_EIP712/readme.md index a6249560..dc2c4d35 100644 --- a/docs/solidity-103/52_EIP712/readme.md +++ b/docs/solidity-103/52_EIP712/readme.md @@ -25,11 +25,11 @@ tags: 之前我们介绍了 [EIP191 签名标准(personal sign)](https://github.com/AmazingAng/WTF-Solidity/blob/main/37_Signature/readme.md) ,它可以给一段消息签名。但是它过于简单,当签名数据比较复杂时,用户只能看到一串十六进制字符串(数据的哈希),无法核实签名内容是否与预期相符。 -![](./img/52-1.png) +![image1](./img/52-1.png) [EIP712类型化数据签名](https://eips.ethereum.org/EIPS/eip-712)是一种更高级、更安全的签名方法。当支持 EIP712 的 Dapp 请求签名时,钱包会展示签名消息的原始数据,用户可以在验证数据符合预期之后签名。 -![](./img/52-2.png) +![image2](./img/52-2.png) ## EIP712 使用方法 @@ -37,7 +37,7 @@ EIP712 的应用一般包含链下签名(前端或脚本)和链上验证( ### 链下签名 -1. EIP712 签名必须包含一个 `EIP712Domain` 部分,它包含了合约的 name,version(一般约定为 “1”),chainId,和 verifyingContract(验证签名的合约地址)。 +1. EIP712 签名必须包含一个 `EIP712Domain` 部分,它包含了合约的 name,version(一般约定为 “1”),chainId 和 verifyingContract(验证签名的合约地址)。 ```js EIP712Domain: [ @@ -69,6 +69,7 @@ EIP712 的应用一般包含链下签名(前端或脚本)和链上验证( ], }; ``` + 3. 创建一个 `message` 变量,传入要被签名的类型化数据。 ```js @@ -77,7 +78,8 @@ EIP712 的应用一般包含链下签名(前端或脚本)和链上验证( number: "100", }; ``` - ![](./img/52-3.png) + + ![image3](./img/52-3.png) 4. 调用钱包对象的 `signTypedData()` 方法,传入前面步骤中的 `domain`,`types`,和 `message` 变量进行签名(这里使用 `ethersjs v6`)。 @@ -88,7 +90,8 @@ EIP712 的应用一般包含链下签名(前端或脚本)和链上验证( const signature = await signer.signTypedData(domain, types, message); console.log("Signature:", signature); ``` - ![](./img/52-4.png) + + ![image4](./img/52-4.png) ### 链上验证 @@ -182,11 +185,18 @@ contract EIP712Storage { } ``` -## Remix 复现 +## 部署复现 -1. 部署 `EIP712Storage` 合约。 +1. 在 `Remix` 部署 `EIP712Storage` 合约。 + +2. 运行 `eip712storage.html`,根据浏览器的内容安全策略([Content Security Policy](https://github.com/MetaMask/faq/blob/9257d7d52784afa957c12166aff20682cf692ae5/DEVELOPERS.md#requirements-nut_and_bolt))的要求,MetaMask 不能通过打开的本地文件(file:// 协议)与 DApp 通信。 可以使用 Node 静态文件服务器 `http-server` 启动本地服务,在包含 `eip712storage.html` 文件的目录下执行以下命令: + + ```sh + npm install -g http-server + http-server + ``` -2. 运行 `eip712storage.html`,将 `Contract Address` 改为部署的 `EIP712Storage` 合约地址,然后依次点击 `Connect Metamask` 和 `Sign Permit` 按钮签名。签名要使用部署合约的钱包,比如 Remix 测试钱包: + 在浏览器中打开 `http://127.0.0.1:8080` 就可以访问了。 然后将 `Contract Address` 改为部署的 `EIP712Storage` 合约地址,然后依次点击 `Connect Metamask` 和 `Sign Permit` 按钮签名。签名要使用部署合约的钱包,比如 Remix 测试钱包: ```js public_key: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 diff --git a/docs/solidity-103/53_ERC20Permit/readme.md b/docs/solidity-103/53_ERC20Permit/readme.md index f2eec760..8f26af3e 100644 --- a/docs/solidity-103/53_ERC20Permit/readme.md +++ b/docs/solidity-103/53_ERC20Permit/readme.md @@ -46,7 +46,7 @@ EIP-2612 提出了 ERC20Permit,扩展了 ERC20 标准,添加了一个 `permi - `spender` 不能是零地址。 - `deadline` 必须是未来的时间戳。 - - `v`,`r` 和 `s` 必须是 `owner` 对 EIP712 格式的函数参数的有效 `secp256k1` 签名。 + - `v`,`r` 和 `s` 必须是 `owner` 对 EIP712 格式的函数参数的有效 `keccak256` 签名。 - 签名必须使用 `owner` 当前的 nonce。 diff --git a/docs/solidity-103/54_CrossChainBridge/crosschain.js b/docs/solidity-103/54_CrossChainBridge/crosschain.js index b11e9e42..021ad113 100644 --- a/docs/solidity-103/54_CrossChainBridge/crosschain.js +++ b/docs/solidity-103/54_CrossChainBridge/crosschain.js @@ -2,7 +2,9 @@ import { ethers } from "ethers"; // 初始化两条链的provider const providerGoerli = new ethers.JsonRpcProvider("Goerli_Provider_URL"); -const providerSepolia = new ethers.JsonRpcProvider("Sepolia_Provider_URL://eth-sepolia.g.alchemy.com/v2/RgxsjQdKTawszh80TpJ-14Y8tY7cx5W2"); +const providerSepolia = new ethers.JsonRpcProvider( + "Sepolia_Provider_URL://eth-sepolia.g.alchemy.com/v2/RgxsjQdKTawszh80TpJ-14Y8tY7cx5W2", +); // 初始化两条链的signer // privateKey填管理者钱包的私钥 @@ -15,45 +17,55 @@ const contractAddressGoerli = "0xa2950F56e2Ca63bCdbA422c8d8EF9fC19bcF20DD"; const contractAddressSepolia = "0xad20993E1709ed13790b321bbeb0752E50b8Ce69"; const abi = [ - "event Bridge(address indexed user, uint256 amount)", - "function bridge(uint256 amount) public", - "function mint(address to, uint amount) external", + "event Bridge(address indexed user, uint256 amount)", + "function bridge(uint256 amount) public", + "function mint(address to, uint amount) external", ]; // 初始化合约实例 -const contractGoerli = new ethers.Contract(contractAddressGoerli, abi, walletGoerli); -const contractSepolia = new ethers.Contract(contractAddressSepolia, abi, walletSepolia); +const contractGoerli = new ethers.Contract( + contractAddressGoerli, + abi, + walletGoerli, +); +const contractSepolia = new ethers.Contract( + contractAddressSepolia, + abi, + walletSepolia, +); const main = async () => { - try{ - console.log(`开始监听跨链事件`) + try { + console.log(`开始监听跨链事件`); - // 监听chain Sepolia的Bridge事件,然后在Goerli上执行mint操作,完成跨链 - contractSepolia.on("Bridge", async (user, amount) => { - console.log(`Bridge event on Chain Sepolia: User ${user} burned ${amount} tokens`); + // 监听chain Sepolia的Bridge事件,然后在Goerli上执行mint操作,完成跨链 + contractSepolia.on("Bridge", async (user, amount) => { + console.log( + `Bridge event on Chain Sepolia: User ${user} burned ${amount} tokens`, + ); - // 在执行burn操作 - let tx = await contractGoerli.mint(user, amount); - await tx.wait(); + // 在Goerli上执行mint操作 + let tx = await contractGoerli.mint(user, amount); + await tx.wait(); - console.log(`Minted ${amount} tokens to ${user} on Chain Goerli`); - }); + console.log(`Minted ${amount} tokens to ${user} on Chain Goerli`); + }); - // 监听chain Sepolia的Bridge事件,然后在Goerli上执行mint操作,完成跨链 - contractGoerli.on("Bridge", async (user, amount) => { - console.log(`Bridge event on Chain Goerli: User ${user} burned ${amount} tokens`); + // 监听chain Goerli的Bridge事件,然后在Sepolia上执行mint操作,完成跨链 + contractGoerli.on("Bridge", async (user, amount) => { + console.log( + `Bridge event on Chain Goerli: User ${user} burned ${amount} tokens`, + ); - // 在执行burn操作 - let tx = await contractSepolia.mint(user, amount); - await tx.wait(); + // 在Sepolia上执行mint操作 + let tx = await contractSepolia.mint(user, amount); + await tx.wait(); - console.log(`Minted ${amount} tokens to ${user} on Chain Sepolia`); - }); + console.log(`Minted ${amount} tokens to ${user} on Chain Sepolia`); + }); + } catch (e) { + console.log(e); + } +}; - }catch(e){ - console.log(e); - - } -} - -main(); \ No newline at end of file +main(); diff --git a/docs/solidity-103/54_CrossChainBridge/readme.md b/docs/solidity-103/54_CrossChainBridge/readme.md index c3ded8fd..9d40e627 100644 --- a/docs/solidity-103/54_CrossChainBridge/readme.md +++ b/docs/solidity-103/54_CrossChainBridge/readme.md @@ -147,7 +147,7 @@ const main = async () => { contractSepolia.on("Bridge", async (user, amount) => { console.log(`Bridge event on Chain Sepolia: User ${user} burned ${amount} tokens`); - // 在执行burn操作 + // 在Goerli上执行mint操作 let tx = await contractGoerli.mint(user, amount); await tx.wait(); @@ -158,7 +158,7 @@ const main = async () => { contractGoerli.on("Bridge", async (user, amount) => { console.log(`Bridge event on Chain Goerli: User ${user} burned ${amount} tokens`); - // 在执行burn操作 + // 在Sepolia上执行mint操作 let tx = await contractSepolia.mint(user, amount); await tx.wait(); diff --git a/docs/solidity-103/56_DEX/img/56-10.jpg b/docs/solidity-103/56_DEX/img/56-10.jpg new file mode 100644 index 00000000..6b97924d Binary files /dev/null and b/docs/solidity-103/56_DEX/img/56-10.jpg differ diff --git a/docs/solidity-103/56_DEX/img/56-11.jpg b/docs/solidity-103/56_DEX/img/56-11.jpg new file mode 100644 index 00000000..b2a989ac Binary files /dev/null and b/docs/solidity-103/56_DEX/img/56-11.jpg differ diff --git a/docs/solidity-103/56_DEX/img/56-2.jpg b/docs/solidity-103/56_DEX/img/56-2.jpg new file mode 100644 index 00000000..244c0cbb Binary files /dev/null and b/docs/solidity-103/56_DEX/img/56-2.jpg differ diff --git a/docs/solidity-103/56_DEX/img/56-3.jpg b/docs/solidity-103/56_DEX/img/56-3.jpg new file mode 100644 index 00000000..6fad3073 Binary files /dev/null and b/docs/solidity-103/56_DEX/img/56-3.jpg differ diff --git a/docs/solidity-103/56_DEX/img/56-4.jpg b/docs/solidity-103/56_DEX/img/56-4.jpg new file mode 100644 index 00000000..d1129fc0 Binary files /dev/null and b/docs/solidity-103/56_DEX/img/56-4.jpg differ diff --git a/docs/solidity-103/56_DEX/img/56-5.jpg b/docs/solidity-103/56_DEX/img/56-5.jpg new file mode 100644 index 00000000..f6f73152 Binary files /dev/null and b/docs/solidity-103/56_DEX/img/56-5.jpg differ diff --git a/docs/solidity-103/56_DEX/img/56-6.jpg b/docs/solidity-103/56_DEX/img/56-6.jpg new file mode 100644 index 00000000..e30fce01 Binary files /dev/null and b/docs/solidity-103/56_DEX/img/56-6.jpg differ diff --git a/docs/solidity-103/56_DEX/img/56-7.jpg b/docs/solidity-103/56_DEX/img/56-7.jpg new file mode 100644 index 00000000..593d3f32 Binary files /dev/null and b/docs/solidity-103/56_DEX/img/56-7.jpg differ diff --git a/docs/solidity-103/56_DEX/img/56-8.jpg b/docs/solidity-103/56_DEX/img/56-8.jpg new file mode 100644 index 00000000..17496725 Binary files /dev/null and b/docs/solidity-103/56_DEX/img/56-8.jpg differ diff --git a/docs/solidity-103/56_DEX/img/56-9.jpg b/docs/solidity-103/56_DEX/img/56-9.jpg new file mode 100644 index 00000000..8475035b Binary files /dev/null and b/docs/solidity-103/56_DEX/img/56-9.jpg differ diff --git a/docs/solidity-103/56_DEX/readme.md b/docs/solidity-103/56_DEX/readme.md index 5c1b1a29..399d6a0d 100644 --- a/docs/solidity-103/56_DEX/readme.md +++ b/docs/solidity-103/56_DEX/readme.md @@ -67,7 +67,7 @@ contract SimpleSwap is ERC20 { // 代币储备量 uint public reserve0; uint public reserve1; - + // 构造器,初始化代币地址 constructor(IERC20 _token0, IERC20 _token1) ERC20("SimpleSwap", "SS") { token0 = _token0; @@ -85,11 +85,11 @@ contract SimpleSwap is ERC20 { 首先,我们需要实现添加流动性的功能。当用户向代币池添加流动性时,合约要记录添加的LP份额。根据 Uniswap V2,LP份额如下计算: 1. 代币池被首次添加流动性时,LP份额 $\Delta{L}$ 由添加代币数量乘积的平方根决定: - + $$\Delta{L}=\sqrt{\Delta{x} *\Delta{y}}$$ -1. 非首次添加流动性时,LP份额由添加代币数量占池子代币储备量的比例决定(两个代币的比例取更小的那个): - +2. 非首次添加流动性时,LP份额由添加代币数量占池子代币储备量的比例决定(两个代币的比例取更小的那个): + $$\Delta{L}=L*\min{(\frac{\Delta{x}}{x}, \frac{\Delta{y}}{y})}$$ 因为 `SimpleSwap` 合约继承了 ERC20 代币标准,在计算好LP份额后,可以将份额以代币形式铸造给用户。 @@ -131,7 +131,7 @@ function addLiquidity(uint amount0Desired, uint amount1Desired) public returns(u // 给流动性提供者铸造LP代币,代表他们提供的流动性 _mint(msg.sender, liquidity); - + emit Mint(msg.sender, amount0Desired, amount1Desired); } ``` @@ -139,6 +139,7 @@ function addLiquidity(uint amount0Desired, uint amount1Desired) public returns(u 接下来,我们需要实现移除流动性的功能。当用户从池子中移除流动性 $\Delta{L}$ 时,合约要销毁LP份额代币,并按比例将代币返还给用户。返还代币的计算公式如下: $$\Delta{x}={\frac{\Delta{L}}{L} * x}$$ + $$\Delta{y}={\frac{\Delta{L}}{L} * y}$$ 下面的 `removeLiquidity()` 函数实现移除流动性的功能,主要步骤如下: @@ -149,7 +150,7 @@ $$\Delta{y}={\frac{\Delta{L}}{L} * y}$$ 4. 销毁LP份额。 5. 将相应的代币转账给用户。 6. 更新储备量。 -5. 释放 `Burn` 事件。 +7. 释放 `Burn` 事件。 ```solidity // 移除流动性,销毁LP,转出代币 @@ -228,7 +229,7 @@ function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) public pur function swap(uint amountIn, IERC20 tokenIn, uint amountOutMin) external returns (uint amountOut, IERC20 tokenOut){ require(amountIn > 0, 'INSUFFICIENT_OUTPUT_AMOUNT'); require(tokenIn == token0 || tokenIn == token1, 'INVALID_TOKEN'); - + uint balance0 = token0.balanceOf(address(this)); uint balance1 = token1.balanceOf(address(this)); @@ -278,7 +279,7 @@ contract SimpleSwap is ERC20 { // 代币储备量 uint public reserve0; uint public reserve1; - + // 事件 event Mint(address indexed sender, uint amount0, uint amount1); event Burn(address indexed sender, uint amount0, uint amount1); @@ -343,7 +344,7 @@ contract SimpleSwap is ERC20 { // 给流动性提供者铸造LP代币,代表他们提供的流动性 _mint(msg.sender, liquidity); - + emit Mint(msg.sender, amount0Desired, amount1Desired); } @@ -391,7 +392,7 @@ contract SimpleSwap is ERC20 { function swap(uint amountIn, IERC20 tokenIn, uint amountOutMin) external returns (uint amountOut, IERC20 tokenOut){ require(amountIn > 0, 'INSUFFICIENT_OUTPUT_AMOUNT'); require(tokenIn == token0 || tokenIn == token1, 'INVALID_TOKEN'); - + uint balance0 = token0.balanceOf(address(this)); uint balance1 = token1.balanceOf(address(this)); @@ -427,18 +428,32 @@ contract SimpleSwap is ERC20 { ## Remix 复现 1. 部署两个ERC20代币合约(token0 和 token1),并记录它们的合约地址。 + + ![创建ERC20token](img/56-2.jpg) 2. 部署 `SimpleSwap` 合约,并将上面的代币地址填入。 + + TOKEN地址传入token地址 3. 调用两个ERC20代币的`approve()`函数,分别给 `SimpleSwap` 合约授权 1000 单位代币。 + + token0授权token1授权 4. 调用 `SimpleSwap` 合约的 `addLiquidity()` 函数给交易所添加流动性,token0 和 token1 分别添加 100 单位。 + + 调用addLiquidityreserve 5. 调用 `SimpleSwap` 合约的 `balanceOf()` 函数查看用户的LP份额,这里应该为 100。($\sqrt{100*100}=100$) + + ![balanceOf](img/56-9.jpg) 6. 调用 `SimpleSwap` 合约的 `swap()` 函数进行代币交易,用 100 单位的 token0。 + + ![swap](img/56-10.jpg) 7. 调用 `SimpleSwap` 合约的 `reserve0` 和 `reserve1` 函数查看合约中的代币储备粮,应为 200 和 50。上一步我们利用 100 单位的 token0 交换了 50 单位的 token 1($\frac{100*100}{100+100}=50$)。 + + ![reserveAfter](img/56-11.jpg) ## 总结 diff --git a/docs/solidity-104/S01_ReentrancyAttack/readme.md b/docs/solidity-104/S01_ReentrancyAttack/readme.md index 3f688cbc..c1fd0cdd 100644 --- a/docs/solidity-104/S01_ReentrancyAttack/readme.md +++ b/docs/solidity-104/S01_ReentrancyAttack/readme.md @@ -158,6 +158,8 @@ contract Attack { 4. 调用`Bank`合约的`getBalance()`函数,发现余额已被提空。 5. 调用`Attack`合约的`getBalance()`函数,可以看到余额变为`21 ETH`,重入攻击成功。 +当然,不仅仅`ETH`转账会触发重入攻击,`ERC721`和`ERC1155`的`safeTransfer()`和`safeTransferFrom()`安全转账函数,还有`ERC777`的`callback`函数,都可能会引发重入攻击。所以这更多的是一个宏观上的设计问题,而不仅仅局限于ETH转账本身。 + ## 预防办法 目前主要有两种办法来预防可能的重入攻击漏洞: 检查-影响-交互模式(checks-effect-interaction)和重入锁。 diff --git a/docs/solidity-104/S02_SelectorClash/SelectorClash.sol b/docs/solidity-104/S02_SelectorClash/SelectorClash.sol index 59d2aad6..7c400261 100644 --- a/docs/solidity-104/S02_SelectorClash/SelectorClash.sol +++ b/docs/solidity-104/S02_SelectorClash/SelectorClash.sol @@ -10,8 +10,8 @@ contract SelectorClash { solved = true; } - function executeCrossChainTx(bytes memory _method, bytes memory _bytes) public returns(bool success){ - (success, ) = address(this).call(abi.encodePacked(bytes4(keccak256(abi.encodePacked(_method, "(bytes,bytes,uint64)"))), abi.encode(_bytes))); + function executeCrossChainTx(bytes memory _method, bytes memory _bytes, bytes memory _bytes1, uint64 _num) public returns(bool success){ + (success, ) = address(this).call(abi.encodePacked(bytes4(keccak256(abi.encodePacked(_method, "(bytes,bytes,uint64)"))), abi.encode(_bytes, _bytes1, _num))); } function secretSlector() external pure returns(bytes4){ diff --git a/docs/solidity-104/S05_Overflow/readme.md b/docs/solidity-104/S05_Overflow/readme.md index 134ad239..a172078b 100644 --- a/docs/solidity-104/S05_Overflow/readme.md +++ b/docs/solidity-104/S05_Overflow/readme.md @@ -75,7 +75,7 @@ contract Token { ## 预防办法 -1. Solidity `0.8.0` 之前的版本,在合约中引用 [Safemath 库](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/math/SafeMath.sol),在整型溢出时报错。 +1. Solidity `0.8.0` 之前的版本,在合约中引用 [Safemath 库](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v4.9/contracts/utils/math/SafeMath.sol),在整型溢出时报错。 2. Solidity `0.8.0` 之后的版本内置了 `Safemath`,因此几乎不存在这类问题。开发者有时会为了节省gas使用 `unchecked` 关键字在代码块中临时关闭整型溢出检测,这时要确保不存在整型溢出漏洞。 diff --git a/docs/solidity-104/S06_SignatureReplay/readme.md b/docs/solidity-104/S06_SignatureReplay/readme.md index e1fc0d77..f989daed 100644 --- a/docs/solidity-104/S06_SignatureReplay/readme.md +++ b/docs/solidity-104/S06_SignatureReplay/readme.md @@ -157,10 +157,21 @@ contract SigReplay is ERC20 { } ``` +3. 对于由用户输入`signature`的场景,需要检验`signature`的长度,确保其长度为`65bytes`,否则也会产生签名重放问题。 + + ```solidity + function mint(address to, uint amount, bytes memory signature) public { + require(signature.length == 65, "Invalid signature length"); + ... + } + ``` + ## 总结 -这一讲,我们介绍了智能合约中的签名重放漏洞,并介绍了两个预防方法: +这一讲,我们介绍了智能合约中的签名重放漏洞,并介绍了三个预防方法: 1. 将使用过的签名记录下来,防止二次使用。 -2. 将 `nonce` 和 `chainid` 包含到签名消息中。 \ No newline at end of file +2. 将 `nonce` 和 `chainid` 包含到签名消息中。 + +3. 对于由用户输入`signature`的场景,需要检验`signature`的长度,确保其长度为`65bytes`,否则也会产生签名重放问题。 diff --git a/docs/solidity-104/S08_ContractCheck/readme.md b/docs/solidity-104/S08_ContractCheck/readme.md index 93aba60e..8bf21cab 100644 --- a/docs/solidity-104/S08_ContractCheck/readme.md +++ b/docs/solidity-104/S08_ContractCheck/readme.md @@ -25,21 +25,21 @@ tags: 很多 freemint 的项目为了限制科学家(程序员)会用到 `isContract()` 方法,希望将调用者 `msg.sender` 限制为外部账户(EOA),而非合约。这个函数利用 `extcodesize` 获取该地址所存储的 `bytecode` 长度(runtime),若大于0,则判断为合约,否则就是EOA(用户)。 ```solidity - // 利用 extcodesize 检查是否为合约 - function isContract(address account) public view returns (bool) { - // extcodesize > 0 的地址一定是合约地址 - // 但是合约在构造函数时候 extcodesize 为0 - uint size; - assembly { - size := extcodesize(account) - } - return size > 0; +// 利用 extcodesize 检查是否为合约 +function isContract(address account) public view returns (bool) { + // extcodesize > 0 的地址一定是合约地址 + // 但是合约在构造函数时候 extcodesize 为0 + uint size; + assembly { + size := extcodesize(account) } + return size > 0; +} ``` 这里有一个漏洞,就是在合约在被创建的时候,`runtime bytecode` 还没有被存储到地址上,因此 `bytecode` 长度为0。也就是说,如果我们将逻辑写在合约的构造函数 `constructor` 中的话,就可以绕过 `isContract()` 检查。 -![](./img/S08-1.png) +![image1](./img/S08-1.png) ## 漏洞例子 @@ -109,9 +109,9 @@ contract NotContract { ## 预防办法 -你可以使用 `(tx.origin == msg.sender)` 来检测调用者是否为合约。如果调用者为 EOA,那么`tx.origin`和`msg.sender`相等;如果它们俩不相等,调用者为合约。 +你可以使用 `(tx.origin == msg.sender)` 来检测调用者是否为合约。如果调用者为 EOA,那么`tx.origin`和`msg.sender`相等;如果它们俩不相等,调用者为合约。在[eip-3074](https://eips.ethereum.org/EIPS/eip-3074)中,这样检查合约的方式,会失效。 -``` +```solidity function realContract(address account) public view returns (bool) { return (tx.origin == msg.sender); } @@ -119,4 +119,4 @@ function realContract(address account) public view returns (bool) { ## 总结 -这一讲,我们介绍了合约长度检查可以被绕过的漏洞,并介绍预防的方法。如果一个地址的 `extcodesize > 0`,则该地址一定为合约;但如果 `extcodesize = 0`,该地址既可能为 `EOA`,也可能为正在创建状态的合约。 \ No newline at end of file +这一讲,我们介绍了合约长度检查可以被绕过的漏洞,并介绍预防的方法。如果一个地址的 `extcodesize > 0`,则该地址一定为合约;但如果 `extcodesize = 0`,该地址既可能为 `EOA`,也可能为正在创建状态的合约。 diff --git a/docs/solidity-104/S09_DoS/readme.md b/docs/solidity-104/S09_DoS/readme.md index f0308bb1..e8d210bb 100644 --- a/docs/solidity-104/S09_DoS/readme.md +++ b/docs/solidity-104/S09_DoS/readme.md @@ -117,7 +117,7 @@ contract Attack { 2. 合约不会出乎意料的自毁。 3. 合约不会进入无限循环。 4. `require` 和 `assert` 的参数设定正确。 -5. 退款时,让用户从合约自行领取(push),而非批量发送给用户(pull)。 +5. 退款时,让用户从合约自行领取(pull),而非批量发送给用户(push)。 6. 确保回调函数不会影响正常合约运行。 7. 确保当合约的参与者(例如 `owner`)永远缺席时,合约的主要业务仍能顺利运行。 diff --git a/docs/solidity-104/S10_Honeypot/readme.md b/docs/solidity-104/S10_Honeypot/readme.md index e53eaa6b..52ce7ead 100644 --- a/docs/solidity-104/S10_Honeypot/readme.md +++ b/docs/solidity-104/S10_Honeypot/readme.md @@ -100,7 +100,7 @@ contract HoneyPot is ERC20, Ownable { 2. 调用 `mint()` 函数,给自己铸造 `100000` 枚貔貅币。 ![](./img/S10-3.png) -3. 进入 [uniswap](https://app.uniswap.org/#/add/v2/ETH) 交易所,为貔貅币创造流动性(v2),提供 `10000`貔貅币。和 `0.1` ETH。 +3. 进入 [uniswap](https://app.uniswap.org/#/add/v2/ETH) 交易所,为貔貅币创造流动性(v2),提供 `10000`貔貅币和 `0.1` ETH。 ![](./img/S10-4.png) 4. 出售 `100` 貔貅币,能够操作成功。 @@ -132,7 +132,7 @@ contract HoneyPot is ERC20, Ownable { 4. 仔细检查项目的官网和社交媒体。 -5. 只投资你了解的项目,做好研究(DYOR。 +5. 只投资你了解的项目,做好研究(DYOR)。 6. 使用tenderly、phalcon分叉模拟卖出貔貅,如果失败则确定是貔貅代币。 diff --git a/docs/solidity-104/S13_UncheckedCall/readme.md b/docs/solidity-104/S13_UncheckedCall/readme.md index c9f3de97..5eab30c8 100644 --- a/docs/solidity-104/S13_UncheckedCall/readme.md +++ b/docs/solidity-104/S13_UncheckedCall/readme.md @@ -125,4 +125,4 @@ contract Attack { ## 总结 -我们将介绍未检查低级调用的漏洞及其预防方法。以太坊的低级调用(call, delegatecall, staticcall, send)在失败时会返回一个布尔值 false,但不会让整个交易回滚。如果开发者没有对它进行检查的话,则会发生意外。 \ No newline at end of file +这一讲,我们介绍了未检查低级调用的漏洞及其预防方法。以太坊的低级调用(call, delegatecall, staticcall, send)在失败时会返回一个布尔值 false,但不会让整个交易回滚。如果开发者没有对它进行检查的话,则会发生意外。 \ No newline at end of file diff --git a/docs/solidity-104/S15_OracleManipulation/readme.md b/docs/solidity-104/S15_OracleManipulation/readme.md index fc5161ff..8db1b5f5 100644 --- a/docs/solidity-104/S15_OracleManipulation/readme.md +++ b/docs/solidity-104/S15_OracleManipulation/readme.md @@ -185,7 +185,7 @@ contract OracleTest is Test { // 给自己账户 1000000 BUSD uint busdAmount = 1_000_000 * 10e18; deal(BUSD, alice, busdAmount); - // 2. 用busd买weth,推高顺时价格 + // 2. 用busd买weth,推高瞬时价格 vm.prank(alice); busd.transfer(address(this), busdAmount); swapBUSDtoWETH(busdAmount, 1); diff --git a/docs/solidity-104/S15_OracleManipulation/test/Oracle.t.sol b/docs/solidity-104/S15_OracleManipulation/test/Oracle.t.sol index 04cc1471..5764d591 100644 --- a/docs/solidity-104/S15_OracleManipulation/test/Oracle.t.sol +++ b/docs/solidity-104/S15_OracleManipulation/test/Oracle.t.sol @@ -32,7 +32,7 @@ contract OracleTest is Test { // 给自己账户 1000000 BUSD uint busdAmount = 1_000_000 * 10e18; deal(BUSD, alice, busdAmount); - // 2. 用busd买weth,推高顺时价格 + // 2. 用busd买weth,推高瞬时价格 vm.prank(alice); busd.transfer(address(this), busdAmount); swapBUSDtoWETH(busdAmount, 1);