# 智能合約solidity開發基礎實戰教學文件 [TOC] ###### tags: `Dapp` `區塊鏈` ## 智能合約硬核 :::spoiler * [深入智能合約 ABI](https://medium.com/taipei-ethereum-meetup/ethereum-%E6%99%BA%E8%83%BD%E5%90%88%E7%B4%84%E9%96%8B%E7%99%BC%E7%AD%86%E8%A8%98-%E6%B7%B1%E5%85%A5%E6%99%BA%E8%83%BD%E5%90%88%E7%B4%84-abi-268ececb70ae) ![](https://i.imgur.com/sozOu2H.png =x300) * [Ethereum 開發整體脈絡](https://medium.com/fukuball-murmur/ethereum-%E9%96%8B%E7%99%BC%E7%AD%86%E8%A8%98-2-1-ethereum-%E9%96%8B%E7%99%BC%E5%B7%A5%E5%85%B7%E6%A6%82%E5%BF%B5-834b611d11ac) ![](https://i.imgur.com/v1X19wH.png =x300) * [以太坊區塊鏈智能合約基礎介紹](https://medium.com/@daniel.mars622/%E4%BB%A5%E5%A4%AA%E5%9D%8A%E5%8D%80%E5%A1%8A%E9%8F%88%E6%99%BA%E8%83%BD%E5%90%88%E7%B4%84%E5%9F%BA%E7%A4%8E%E4%BB%8B%E7%B4%B9-b7317347a612) [RESTful API](https://ithelp.ithome.com.tw/articles/10251579?sc=pt) [RESTful API與MVC名詞介紹](https://ithelp.ithome.com.tw/articles/10191925) [Blocto 錢包sdk](https://docs.blocto.app/blocto-sdk/overview) [程式語法參考資料](https://ithelp.ithome.com.tw/articles/10203645) [solidity遊戲教學](https://cryptozombies.io/en/lesson/2/chapter/3) --- ![](https://i.imgur.com/N5MGjnn.png) ![](https://i.imgur.com/xeHUt9w.png) ![](https://i.imgur.com/OX0sgvV.png) ::: :::spoiler ### global variables ![](https://i.imgur.com/vGd8GPD.png) ::: --- :::spoiler ### 修飾字 * 繼承了public(function/variable)部分 ![](https://i.imgur.com/zfER61W.png) ![](https://i.imgur.com/SKYOIcx.png) ![](https://i.imgur.com/D2sOuPj.png =x200) ![](https://i.imgur.com/Vg5q11e.png) ![](https://i.imgur.com/EONdYyo.png) ![](https://i.imgur.com/QssmmYD.png) ---- #### 修飾字 ![](https://i.imgur.com/MrX1tm9.png) ![](https://i.imgur.com/y1G8LDG.png) ![](https://i.imgur.com/hKAolsZ.png) ![](https://i.imgur.com/oiD1Ifa.png) ![](https://i.imgur.com/F7P8095.png) ![](https://i.imgur.com/fOsMzX9.png) ![](https://i.imgur.com/b8qhJ8I.png) ![](https://i.imgur.com/5R69lGb.png) * 結論---> * static-->母程式可使用 子程式不可 * public-->母程式可 子程式可 其他皆可 * private-->母程式可 子程式不可 其他不可 * internal-->母程式可 子程式可 其他不可 * 這與多數物件導向語言的 protected 相似,可以在合約內部使用,包含了繼承的合約,但無法由外部呼叫 * external-->母程式不可 子程式不可 其他可 * 要在參數帶上 calldata * 由於只開放給外部使用,比起同時要開放給內部使用的 public 還要==省 Gas==。 ![](https://i.imgur.com/9xnZBXb.png) * 參考資料---> * [Solidity函数view,pure,constant的用法](https://www.programminghunter.com/article/7171459237/) * [Solidity修飾詞 view,pure,payable](https://ithelp.ithome.com.tw/articles/10219510) * 程式碼驗證---> ```solidity= pragma solidity ^0.5.7; contract test{   //属性 uint p; address _owner;   //构造函数 function car() public{ p=100; _owner=msg.sender; } //public的话可以外部调用 function get() public returns(uint){ return p; } //view,当不返回存粹的值或者是变量的时候,比如msg.sender使用 function get1() view public returns(address){ return msg.sender } //pure,返回存粹的值使用,123,'hello' function get2() pure public returns(uint){ return 123 } //constant, 返回带有状态变量的值,_owner function get3() constant public returns(address){ return _owner } } ``` ::: ##### view * view --->gas optimization(不改變區塊鏈上任何值 ---> View functions don't cost gas 只讀data) * view 會告訴 web3.js 這個function 要query自己的 local ethereum node,不用在區塊鏈上每個node上run產生transaction * external view --->gas optimization(read-only) * internal view --->若internal有個非view的function call view function,還是會在每個node上跑--->依舊會產生gas ```solidity= pragma solidity >=0.5.0 <0.6.0; import "./zombiefeeding.sol"; contract ZombieHelper is ZombieFeeding { modifier aboveLevel(uint _level, uint _zombieId) { require(zombies[_zombieId].level >= _level); _; } function changeName(uint _zombieId, string calldata _newName) external aboveLevel(2, _zombieId) { require(msg.sender == zombieToOwner[_zombieId]); zombies[_zombieId].name = _newName; } function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) { require(msg.sender == zombieToOwner[_zombieId]); zombies[_zombieId].dna = _newDna; } function getZombiesByOwner(address _owner) external view returns (uint[] memory) { } } ``` ##### pure * 不寫也不讀data進來 ##### payable * 讓函式可以收以太幣 * 程式碼驗證1---> ```solidity= contract OnlineStore { function buySomething() external payable {//有加payable的function才可以送錢過去 // Check to make sure 0.001 ether was sent to the function call: require(msg.value == 0.001 ether); // msg.value是看有多少ether送到contract裡 // If so, some logic to transfer the digital item to the caller of the function: transferThing(msg.sender); } } ``` * 程式碼驗證2---> ```solidity= pragma solidity >=0.5.0 <0.6.0; import "./zombiefeeding.sol"; contract ZombieHelper is ZombieFeeding { uint levelUpFee = 0.001 ether; modifier aboveLevel(uint _level, uint _zombieId) { require(zombies[_zombieId].level >= _level); _; } function levelUp(uint _zombieId)external payable { require(msg.value == levelUpFee); zombies[_zombieId].level++; } function changeName(uint _zombieId, string calldata _newName) external aboveLevel(2, _zombieId) { require(msg.sender == zombieToOwner[_zombieId]); zombies[_zombieId].name = _newName; } function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) { require(msg.sender == zombieToOwner[_zombieId]); zombies[_zombieId].dna = _newDna; } function getZombiesByOwner(address _owner) external view returns(uint[] memory) { uint[] memory result = new uint[](ownerZombieCount[_owner]); uint counter = 0; for (uint i = 0; i < zombies.length; i++) { if (zombieToOwner[i] == _owner) { result[counter] = i; counter++; } } return result; } } ``` ##### withdraws * 當轉 ether 到 contract 後,ether會 store 在 contract 裡 且 trapped --->必須要加一個function 去 ==withdraw== trap 在 contract 裡面的ether * 程式碼驗證---> ```solidity= contract GetPaid is Ownable { function withdraw() external onlyOwner { address payable _owner = address(uint160(owner())); _owner.transfer(address(this).balance); }// _owner (uint160)--->cast to "address payable" //this--->當前的合約,可轉換為合約的地址 //address(this).balance--->為當前contract中總ether //會將contract中總ether傳到_owner中 } ``` ```solidity= uint itemFee = 0.001 ether;//若msg.sender送過多錢,理論只要送itemfee就好 msg.sender.transfer(msg.value - itemFee); //msg.value--->呼叫訊息的人先前總共錯誤打賞多少以太幣 //用transfer()函式去轉回(msg.value-itemFee)的錢 ``` ---- #### function ##### constructor() * 第一次執行且只執行一次的function ##### modifier() * 程式碼驗證---> ```solidity= pragma solidity >=0.5.0 <0.6.0; import "./zombiefeeding.sol"; contract ZombieHelper is ZombieFeeding { modifier aboveLevel(uint _level, uint _zombieId) { require(zombies[_zombieId].level >= _level);//檢查有無滿足 _;//else } function changeName(uint _zombieId, string calldata _newName) external aboveLevel(2, _zombieId){ //aboveLevel這個modifier,連帶寫在function之後 //calldata類似external用法 require(msg.sender == zombieToOwner[_zombieId]); zombies[_zombieId].name = _newName; } function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId){ require(msg.sender == zombieToOwner[_zombieId]);//確定deploy smart contract的是_zombieId本人 zombies[_zombieId].dna = _newDna; } } ``` ---- #### variables * smart contract 上 variables 可以存在 * storage * memory ##### storage * variable which store permanently on the blockchain * State variables (variables declared outside of functions) are by default storage and written permanently to the blockchain * 類似hard disk ##### memory * variable that are temporary, and are erased between external function calls to your contract. * variables declared inside functions are memory and will disappear when the function call ends * 類似RAM * 程式碼驗證 1---> ```solidity= function getArray() external pure returns(uint[] memory) { // Instantiate a new array in memory with a length of 3 uint[] memory values = new uint[](3); //array存在memory中為fixed size array // Put some values to it values[0] = 1; values[1] = 2; values[2] = 3; return values; } ``` * 程式碼驗證 2---> ```solidity= pragma solidity >=0.5.0 <0.6.0; import "./zombiefeeding.sol"; contract ZombieHelper is ZombieFeeding { modifier aboveLevel(uint _level, uint _zombieId) { require(zombies[_zombieId].level >= _level); _; } function changeName(uint _zombieId, string calldata _newName) external aboveLevel(2, _zombieId) { require(msg.sender == zombieToOwner[_zombieId]); zombies[_zombieId].name = _newName; } function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) { require(msg.sender == zombieToOwner[_zombieId]); zombies[_zombieId].dna = _newDna; } function getZombiesByOwner(address _owner) external view returns(uint[] memory) { uint[] memory result = new uint[](ownerZombieCount[_owner]);//這個result array大小為owner所擁有的zombie個數 return result; } } ``` ```solidity= pragma solidity >=0.5.0 <0.6.0; import "./zombiefeeding.sol"; contract ZombieHelper is ZombieFeeding { modifier aboveLevel(uint _level, uint _zombieId) { require(zombies[_zombieId].level >= _level); _; } function changeName(uint _zombieId, string calldata _newName) external aboveLevel(2, _zombieId) { require(msg.sender == zombieToOwner[_zombieId]); zombies[_zombieId].name = _newName; } function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) { require(msg.sender == zombieToOwner[_zombieId]); zombies[_zombieId].dna = _newDna; } function getZombiesByOwner(address _owner) external view returns(uint[] memory) { uint[] memory result = new uint[](ownerZombieCount[_owner]);//result[counter++]這個array uint counter=0; for(uint i=0;i<zombies.length;i++) { if(zombieToOwner[i]==_owner) { result[counter]=i;//counter計算個數總共多少個zombie's ID==_owner , result這個array存"zombieToOwner[i]==_owner"的第幾個zombie index counter++; } } return result; } } ``` ###### code examples ```solidity= contract SandwichFactory { struct Sandwich { string name; string status; } Sandwich[] sandwiches; function eatSandwich(uint _index) public { // Sandwich mySandwich = sandwiches[_index]; // ^ Seems pretty straightforward, but solidity will give you a warning // telling you that you should explicitly declare `storage` or `memory` here. // So instead, you should declare with the `storage` keyword, like: Sandwich storage mySandwich = sandwiches[_index]; // ...in which case `mySandwich` is a pointer to `sandwiches[_index]` // in storage, and... mySandwich.status = "Eaten!"; // ...this will permanently change `sandwiches[_index]` on the blockchain. // If you just want a copy, you can use `memory`: Sandwich memory anotherSandwich = sandwiches[_index + 1]; // ...in which case `anotherSandwich` will simply be a copy of the // data in memory, and... anotherSandwich.status = "Eaten!"; // ...will just modify the temporary variable and have no effect // on `sandwiches[_index + 1]`. But you can do this: sandwiches[_index + 1] = anotherSandwich; // ...if you want to copy the changes back into blockchain storage. } } ``` * zombiefactory.sol ```solidity= pragma solidity >=0.5.0 <0.6.0; contract ZombieFactory { event NewZombie(uint zombieId, string name, uint dna); uint dnaDigits = 16; uint dnaModulus = 10 ** dnaDigits; struct Zombie { string name; uint dna; } Zombie[] public zombies; mapping (uint => address) public zombieToOwner; mapping (address => uint) ownerZombieCount; function _createZombie(string memory _name, uint _dna) private { uint id = zombies.push(Zombie(_name, _dna)) - 1; zombieToOwner[id] = msg.sender; ownerZombieCount[msg.sender]++; emit NewZombie(id, _name, _dna); } function _generateRandomDna(string memory _str) private view returns (uint) { uint rand = uint(keccak256(abi.encodePacked(_str))); return rand % dnaModulus; } function createRandomZombie(string memory _name) public { require(ownerZombieCount[msg.sender] == 0); uint randDna = _generateRandomDna(_name); _createZombie(_name, randDna); } } ``` * ZombieFeeding.sol ```solidity= pragma solidity >=0.5.0 <0.6.0; import "./zombiefactory.sol"; contract ZombieFeeding is ZombieFactory { function feedAndMultiply(uint _zombieId,uint _targetDna)public { require(zombieToOwner[_zombieId]==msg.sender);//檢查為smart contract本人 Zombie storage myZombie=zombies[_zombieId]; } } ``` ---- * 2個paused不會collide(因為1個private/1個public) ![](https://i.imgur.com/NyfrtGW.png) ---- #### Operators ![](https://i.imgur.com/tWew2g6.png) ---- #### Units * uint<-->uint256 1 eth == 1 wei; 1 eth == 1e9 gwei; //10 ** 9 1 eth == 1e18 ether; //10 ** 18 * [Gas Used/Gas limit](https://ithelp.ithome.com.tw/articles/10217483) * 若想提早處理交易,可提高Gas price,礦工會依照Gas price高低先後處理 * Gas Used>>Gas limit && 如果你不想在 Gas 上花費太多,降低 Gas Limit 不會有多大幫助。您必須包含足夠的 Gas 來涵蓋您使用的計算資源,否則您的交易將因 Out of Gas 而失敗 * 一般標準交易的 Gas Limit 為21000 * 交易手續費(Tx Fee) * 交易手續費(Tx Fee)<= Gas Limit * Gas Price(先以Gas limit計算,若未超過Gas limit則以Gas Used計算) * Eth交易手續費則為 21,000(Gas Limit)*20 Gwei(Gas Price)=420,000 Gwei,因此,交易手續費 Tx Fee=420,000 *0.000000001Eth=0.00042Eth * 參考資料: * [Gas](https://zombit.info/%E5%88%B0%E5%BA%95%E4%BB%80%E9%BA%BC%E6%98%AFgas%E3%80%81gas-limit%E3%80%81gas-price%EF%BC%9F/) ---- #### Struct * 一般來講uint size變小 不會省多少gas * 但在struct裡 gas fee費用---> uint8 < uint16 < uint32 * "==Struct packing"== more tightly--->to save gas ##### example ```solidity= struct NormalStruct { uint a; uint b; uint c; } struct MiniMe { uint32 a; uint32 b; uint c; } // `mini` will cost less gas than `normal` because of struct packing NormalStruct normal = NormalStruct(10, 20, 30); MiniMe mini = MiniMe(10, 20, 30); ``` * 上面例子下面的struct total data size 也較少 * uint<--->uint256 * 上面例子下面的struct==cluster identical data type(uint size相同的綁在一起)== * ex: uint32 a uint32 b 寫在一起--->storage space更加減少 ##### code examples ```solidity= pragma solidity >=0.5.0 <0.6.0; contract ZombieFactory { uint dnaDigits = 16; uint dnaModulus = 10 ** dnaDigits; struct Zombie { string name; uint dna; } Zombie[] public zombies;//zombie struct存成叫zombies的array function _createZombie(string memory _name, uint _dna) private { zombies.push(Zombie(_name, _dna));//將新zombie push到Zombies後面 } function _generateRandomDna(string memory _str) private view returns (uint) { uint rand = uint(keccak256(abi.encodePacked(_str)));//keccak256 hash algo can convert _str(string) inato rand(int) return rand % dnaModulus;//回傳16位數字的randomNum } function createRandomZombie(string memory _name) public { uint randDna = _generateRandomDna(_name); _createZombie(_name, randDna);//將新計算好的_name, randDna push到zombies struct後面 } } ``` ```solidity= pragma solidity >=0.5.0 <0.6.0; import "./ownable.sol"; contract ZombieFactory is Ownable { event NewZombie(uint zombieId, string name, uint dna); uint dnaDigits = 16; uint dnaModulus = 10 ** dnaDigits; uint cooldownTime = 1 days; struct Zombie { string name; uint dna; uint32 level; uint32 readyTime; } Zombie[] public zombies; mapping (uint => address) public zombieToOwner; mapping (address => uint) ownerZombieCount; function _createZombie(string memory _name, uint _dna) internal { uint id = zombies.push(Zombie(_name, _dna,1,uint32(now+cooldownTime))) - 1;// uint32(now_cooldownTime) is necessary because now returns a uint256 by default. So we need to explicitly convert it to a uint32. //zombies這個struct有4個參數分別傳入要一一對應 zombieToOwner[id] = msg.sender; ownerZombieCount[msg.sender]++; emit NewZombie(id, _name, _dna); } function _generateRandomDna(string memory _str) private view returns (uint) { uint rand = uint(keccak256(abi.encodePacked(_str))); return rand % dnaModulus;//回傳最大小於16位數 } function createRandomZombie(string memory _name) public { require(ownerZombieCount[msg.sender] == 0);// makes sure each user can't own more than one zombie: uint randDna = _generateRandomDna(_name); randDna = randDna - randDna % 100; _createZombie(_name, randDna); } } ``` ##### Passing structs as arguments ```solidity= function _triggerCooldown(Zombie storage _zombie)internal{ uint32 _zombie.readyTime=uint32(now + cooldownTime); } function _isReady(Zombie storage _zombie) internal view returns (bool) {//參數傳入struct _zombie return (_zombie.readyTime <= now);//return true or false } ``` ```solidity= struct Zombie { string name; uint dna; uint32 level; uint32 readyTime; } Zombie[] public zombies; ``` ---- #### Mapping ![](https://i.imgur.com/BJCw7U3.png) ##### Msg.sender(就是 call address ==deploy智能合約的人==) * the address of the person(smart contract) who called the function * 只要有人call function必定會有對應Msg.sender ```solidity= pragma solidity >=0.5.0 <0.6.0; contract ZombieFactory { event NewZombie(uint zombieId, string name, uint dna); uint dnaDigits = 16; uint dnaModulus = 10 ** dnaDigits; struct Zombie { string name; uint dna; } Zombie[] public zombies; mapping (uint => address) public zombieToOwner; mapping (address => uint) ownerZombieCount; function _createZombie(string memory _name, uint _dna) private { uint id = zombies.push(Zombie(_name, _dna)) - 1; zombieToOwner[id]=msg.sender; ownerZombieCount[msg.sender]++;//call function的counter++ emit NewZombie(id, _name, _dna); } function _generateRandomDna(string memory _str) private view returns (uint) { uint rand = uint(keccak256(abi.encodePacked(_str))); return rand % dnaModulus; } function createRandomZombie(string memory _name) public { uint randDna = _generateRandomDna(_name); _createZombie(_name, randDna); } } ``` * 用solidity寫智能合約常做狀態檢查 ![](https://i.imgur.com/GqZG4Rt.png) * 在 transferOwnership 的時候合約需要檢查交易發起人是否為 owner , 若不是則執行 throw。throw 會使整個交易終止,並讓合約狀態回到交易執行前的狀態,並取走“所有”交易手續費,及 msg.sender 會被抽走 gasLimit * gasPrice 這麼多 ether。 * 補充:若是一筆沒有遇到 throw 的正常交易, 則會被抽走 gasUsed * gasPrice 這麼多 ether。 * ICO 很常會也一些奇怪的投標規則。像是會限制投標時間,或是 gasPrice,或是個人投標上限等等。如果你沒有遵守這些規則,那你發出去的交易會失敗,還會被抽走所有的交易手續費,而通常都不少錢,因為很多投標需要大量的 gas,使 gasLimit 不能設太低。像我只是搞錯了投標時間,提早發出了投標交易,就被抽走這麼多手續費,實在是叫我情何以堪。所以需要不同的狀態檢查機制(require,assert,revert),能夠分辨是簡單的錯誤,或是較嚴重的系統錯誤,兩者會顯示不同的錯誤,抽不同的手續費。 #### Gas 的錯誤處理 ##### require * require 的用途就是進行條件判斷,如果條件不成立,會將剩餘的 Gas 歸還,並將合約狀態回復,也是最常使用到的錯誤處理方式。 * 概念有點類似下方的 JavaScript 範例,針對輸入值做驗證,不成立就return來終止運行: ![](https://i.imgur.com/0qpXpqe.png) * require 範例,寫在修飾詞中,並判斷是否為合約部署者: ![](https://i.imgur.com/jQuOsBx.png) * [require revert assert 三大Gas錯誤處理語法 和 gas/ico 關聯](https://medium.com/taipei-ethereum-meetup/%E6%AF%94%E8%BC%83-require-assert-%E5%92%8C-revert-%E5%8F%8A%E5%85%B6%E9%81%8B%E4%BD%9C%E6%96%B9%E5%BC%8F-30c24d534ce4) * 程式碼驗證1---> ```solidity= pragma solidity >=0.5.0 <0.6.0; contract ZombieFactory { event NewZombie(uint zombieId, string name, uint dna); uint dnaDigits = 16; uint dnaModulus = 10 ** dnaDigits; struct Zombie { string name; uint dna; } Zombie[] public zombies; mapping (uint => address) public zombieToOwner; mapping (address => uint) ownerZombieCount; function _createZombie(string memory _name, uint _dna) private { uint id = zombies.push(Zombie(_name, _dna)) - 1; zombieToOwner[id] = msg.sender; ownerZombieCount[msg.sender]++; emit NewZombie(id, _name, _dna); } function _generateRandomDna(string memory _str) private view returns (uint) { uint rand = uint(keccak256(abi.encodePacked(_str))); return rand % dnaModulus; } function createRandomZombie(string memory _name) public { require(ownerZombieCount[msg.sender]==0);//make sure this function only gets executed one time per user, when they create their first zombie. //otherwise return error uint randDna = _generateRandomDna(_name); _createZombie(_name, randDna); } } ``` ---- * 程式碼驗證2---> ```solidity= function _triggerCooldown(Zombie storage _zombie) internal { _zombie.readyTime = uint32(now + cooldownTime); } function _isReady(Zombie storage _zombie) internal view returns (bool) { return (_zombie.readyTime <= now); } function feedAndMultiply(uint _zombieId, uint _targetDna, string memory _species) internal { require(msg.sender == zombieToOwner[_zombieId]); Zombie storage myZombie = zombies[_zombieId]; require(_isReady(myZombie));//檢查myZombie是否為true //是就結束 _targetDna = _targetDna % dnaModulus; uint newDna = (myZombie.dna + _targetDna) / 2; if (keccak256(abi.encodePacked(_species)) == keccak256(abi.encodePacked("kitty"))) { newDna = newDna - newDna % 100 + 99; } _createZombie("NoName", newDna); _triggerCooldown(myZombie); } ``` ---- ##### revert * 跟 require 十分接近但沒有條件判斷,當呼叫時會歸還剩餘的 Gas 並回復合約狀態。 ![](https://i.imgur.com/61RGaXd.png) ##### assert * 跟 require 一樣做判斷,但當條件不成立時,會將 Gas 全數消耗,不會返回任何的 Gas ,並回復合約狀態。通常用來處理較嚴重的錯誤,如:程式 bug ,為最不該被經常使用到的錯誤處理。 ![](https://i.imgur.com/Cu8H6vx.png) ---- #### event(事件) * event name(宣告name這個事件) * emit(呼叫name這個事件)--->fire name 這個事件 * ==event== ---> gas cost 比 storage 低 ![](https://i.imgur.com/a2WfQk0.png) ![](https://i.imgur.com/74N1JAj.png) [solidity events和web3.js合作達到監聽事件](https://me.tryblockchain.org/blockchain-solidity-event.html) ##### code examples ```solidity= pragma solidity >=0.5.0 <0.6.0; contract ZombieFactory { event NewZombie(uint zombieId, string name, uint dna);//宣告非同步事件 uint dnaDigits = 16; uint dnaModulus = 10 ** dnaDigits; struct Zombie { string name; uint dna; } Zombie[] public zombies; function _createZombie(string memory _name, uint _dna) private { uint id = zombies.push(Zombie(_name, _dna)) - 1;//-1--->因為push進去zombies array的 index 要 -1 emit NewZombie(id,_name,_dna);// and fire event here(呼叫event) } function _generateRandomDna(string memory _str) private view returns (uint) { uint rand = uint(keccak256(abi.encodePacked(_str))); return rand % dnaModulus; } function createRandomZombie(string memory _name) public { uint randDna = _generateRandomDna(_name); _createZombie(_name, randDna); } } ``` #### Inheritance ---- #### Import ---- #### Interface(接口) * your contract can interact with any other contract on the Ethereum blockchain, as long they expose those functions as public or external. * [interface用法](https://blog.syhlion.tw/2021/09/2021-13th-ithome30day-day14-solidity-interfaces/) * 規則 * 不能繼承其他contract,但可以繼承其他interface * interface內所有function必須為external * 不能宣告constructor * 不能宣告state variable ##### code examples ```solidity= pragma solidity >=0.5.0 <0.6.0; import "./zombiefactory.sol"; contract KittyInterface {//creat an interface function getKitty(uint256 _id) external view returns ( bool isGestating, bool isReady, uint256 cooldownIndex, uint256 nextActionAt, uint256 siringWithId, uint256 birthTime, uint256 matronId, uint256 sireId, uint256 generation, uint256 genes ); } contract ZombieFeeding is ZombieFactory { address ckAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d; // Initialize kittyContract here using `ckAddress` from above function feedAndMultiply(uint _zombieId, uint _targetDna) public { require(msg.sender == zombieToOwner[_zombieId]); Zombie storage myZombie = zombies[_zombieId]; _targetDna = _targetDna % dnaModulus; uint newDna = (myZombie.dna + _targetDna) / 2; _createZombie("NoName", newDna); } KittyInterface kittyContract= KittyInterface(ckAddress); } ``` ---- ```solidity= pragma solidity >=0.5.0 <0.6.0; import "./zombiefactory.sol"; contract KittyInterface { function getKitty(uint256 _id) external view returns ( bool isGestating, bool isReady, uint256 cooldownIndex, uint256 nextActionAt, uint256 siringWithId, uint256 birthTime, uint256 matronId, uint256 sireId, uint256 generation, uint256 genes ); } contract ZombieFeeding is ZombieFactory { address ckAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d; KittyInterface kittyContract = KittyInterface(ckAddress); //設立一個interface kittyContract function feedAndMultiply(uint _zombieId, uint _targetDna) public { require(msg.sender == zombieToOwner[_zombieId]); Zombie storage myZombie = zombies[_zombieId]; _targetDna = _targetDna % dnaModulus;//dnaModulus-->10**16 //所以為了使_targetDna小於16位數 uint newDna = (myZombie.dna + _targetDna) / 2; _createZombie("NoName", newDna); } function feedOnKitty(uint _zombieId,uint _kittyId) public { uint kittyDna;//declare a uint (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId);//取得只有一個回傳值genes到kittyDna feedAndMultiply(_zombieId, kittyDna); } } ``` ---- ```solidity= pragma solidity >=0.5.0 <0.6.0; import "./zombiefactory.sol"; contract KittyInterface { function getKitty(uint256 _id) external view returns ( bool isGestating, bool isReady, uint256 cooldownIndex, uint256 nextActionAt, uint256 siringWithId, uint256 birthTime, uint256 matronId, uint256 sireId, uint256 generation, uint256 genes ); } contract ZombieFeeding is ZombieFactory { address ckAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d; KittyInterface kittyContract = KittyInterface(ckAddress); // Modify function definition here: function feedAndMultiply(uint _zombieId, uint _targetDna,string memory _species) public { require(msg.sender == zombieToOwner[_zombieId]); Zombie storage myZombie = zombies[_zombieId]; _targetDna = _targetDna % dnaModulus; uint newDna = (myZombie.dna + _targetDna) / 2; if(keccak256(abi.encodePacked(_species))==keccak256(abi.encodePacked("kitty")))//若最後傳入species為kitty { newDna=newDna-newDna%100+99;//newDna最後兩位換99 } _createZombie("NoName", newDna); } function feedOnKitty(uint _zombieId, uint _kittyId) public { uint kittyDna; (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId); // And modify function call here: feedAndMultiply(_zombieId, kittyDna,"kitty"); } } ``` ```solidity= pragma solidity >=0.5.0 <0.6.0; import "./zombiefactory.sol"; contract KittyInterface { function getKitty(uint256 _id) external view returns ( bool isGestating, bool isReady, uint256 cooldownIndex, uint256 nextActionAt, uint256 siringWithId, uint256 birthTime, uint256 matronId, uint256 sireId, uint256 generation, uint256 genes ); } contract ZombieFeeding is ZombieFactory { KittyInterface kittyContract;//don't hardcode into the kittyInterface function setKittyContractAddress(address _address)external { kittyContract=KittyInterface(_address); } function feedAndMultiply(uint _zombieId, uint _targetDna, string memory _species) public { require(msg.sender == zombieToOwner[_zombieId]); Zombie storage myZombie = zombies[_zombieId]; _targetDna = _targetDna % dnaModulus; uint newDna = (myZombie.dna + _targetDna) / 2; if (keccak256(abi.encodePacked(_species)) == keccak256(abi.encodePacked("kitty"))) { newDna = newDna - newDna % 100 + 99; } _createZombie("NoName", newDna); } function feedOnKitty(uint _zombieId, uint _kittyId) public { uint kittyDna; (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId); feedAndMultiply(_zombieId, kittyDna, "kitty"); } } ``` #### Random number generation via keccak256 ```solidity= // Generate a random number between 1 and 100: uint randNonce = 0;//Nonce必須獨一無二 uint random = uint(keccak256(abi.encodePacked(now, msg.sender, randNonce))) % 100;// "pack" the 3 inputs and use keccak to convert them to a random hash //Next, it would convert that hash to a uint, //and then use % 100 to take only the last 2 digits. //This will give us a totally random number between 0 and 99. //now-->timestamp(時間戳) msg.sender-->玩合約的人 randNonce++; uint random2 = uint(keccak256(abi.encodePacked(now, msg.sender, randNonce))) % 100; ``` * [How can I securely generate a random number in my smart contract?](https://ethereum.stackexchange.com/questions/191/how-can-i-securely-generate-a-random-number-in-my-smart-contract) * use ==oracle== (a secure way to pull data in from outside of Ethereum) to access a random number function from outside of the Ethereum blockchain * 程式碼驗證 1 ---> ```solidity= pragma solidity >=0.5.0 <0.6.0; import "./zombiehelper.sol"; contract ZombieAttack is ZombieHelper { uint randNonce = 0; function randMod(uint _modulus) internal returns(uint) { randNonce++; return uint(keccak256(abi.encodePacked(now, msg.sender, randNonce))) % _modulus; } } ``` ---- ### ERC-721 ---- ### Contract security enhancements: Overflows and Underflows * uint256(2**256夠大 , 很難 overflow 或 underflow) * uint8 * (二進位最大就11111111--->decimal:256 ) * 11111111 加 1 --->overflow-->(全歸0) * 0 減 1 --->underflow-->(變 11111110--->decimal:255) * 程式碼驗證 1---> ```solidity= using SafeMath for uint256; uint256 a = 5; uint256 b = a.add(3); // 5 + 3 = 8 uint256 c = a.mul(2); // 5 * 2 = 10 ``` ---- ### comments(natspec) * 程式碼驗證 1---> ```solidity= /// @title A contract for basic math operations /// @author H4XF13LD MORRIS 💯💯😎💯💯 /// @notice For now, this contract just adds a multiply function contract Math { /// @notice Multiplies 2 numbers together /// @param x the first uint. /// @param y the second uint. /// @return z the product of (x * y) /// @dev This function does not currently check for overflows function multiply(uint x, uint y) returns (uint z) { // This is just a normal comment, and won't get picked up by natspec z = x * y; } } @title and @author are straightforward. @notice explains to a user what the contract / function does. @dev is for explaining extra details to developers. @param and @return are for describing what each parameter and return value of a function are for. ``` ---- --- ##### Reference [What the hack is Memory and Storage in Solidity?](https://medium.com/coinmonks/what-the-hack-is-memory-and-storage-in-solidity-6b9e62577305) [什麼時候用 storage,什麼時候該用 memory?](https://medium.com/taipei-ethereum-meetup/solidity-weekly-9-beae006cee2) [layer 2](https://www.blocktempo.com/the-state-of-eth2-june-2020/)